AI / app.py
jyujiaf's picture
Upload app.py
811d571 verified
import os
import json
import base64
import requests
from datetime import datetime
from flask import Flask, request, jsonify, render_template_string
from threading import Lock
app = Flask(__name__)
moderation_data = {}
data_lock = Lock()
NVIDIA_API_KEY = os.environ.get("NVIDIA_API_KEY", "")
NVIDIA_ENDPOINT = "https://integrate.api.nvidia.com/v1/chat/completions"
MODEL_NAME = "qwen/qwen3.5-397b-a17b"
SYSTEM_PROMPT = """你是一个图片内容审核AI助手。请分析用户提供的图片内容,判断是否包含以下违规内容:
1. 色情/低俗内容
2. 暴力/血腥内容
3. 政治敏感内容
4. 恐怖/惊悚内容
5. 赌博/诈骗内容
6. 其他违反法律法规的内容
请基于图片分析结果,严格判断:
- 如果图片内容安全,不包含任何违规元素,返回 "TRUE"
- 如果图片包含任何违规内容或无法确定安全性,返回 "FALSE"
注意:只返回TRUE或FALSE,不要返回其他内容。"""
HTML_TEMPLATE = """
<!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>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
h1 { color: white; text-align: center; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
.container { background: white; border-radius: 15px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
.upload-section { text-align: center; padding: 30px; border: 2px dashed #ccc; border-radius: 10px; }
.upload-section:hover { border-color: #667eea; background: #f8f9ff; }
input[type="file"] { display: none; }
.upload-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 40px;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 18px;
font-weight: bold;
transition: transform 0.2s;
}
.upload-btn:hover { transform: scale(1.05); }
#preview { max-width: 300px; margin: 20px auto; display: none; }
#preview img { max-width: 100%; border-radius: 10px; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
.loading { text-align: center; padding: 30px; display: none; }
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.result {
margin-top: 20px;
padding: 25px;
border-radius: 10px;
display: none;
text-align: center;
}
.result h2 { margin: 10px 0; }
.result.passed { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; }
.result.rejected { background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white; }
.action-btns { display: flex; gap: 15px; justify-content: center; margin-top: 15px; }
.btn { padding: 12px 30px; border: none; border-radius: 25px; cursor: pointer; font-size: 16px; font-weight: bold; }
.btn-approve { background: #11998e; color: white; }
.btn-reject { background: #eb3349; color: white; }
.btn:hover { opacity: 0.9; transform: scale(1.05); }
.history { margin-top: 30px; }
.history h2 { color: #333; border-bottom: 2px solid #667eea; padding-bottom: 10px; }
.history-item {
background: #f8f9fa;
padding: 15px;
margin: 10px 0;
border-radius: 10px;
display: flex;
align-items: center;
gap: 15px;
}
.history-item img { width: 80px; height: 80px; object-fit: cover; border-radius: 8px; }
.history-item .info { flex: 1; }
.status-badge { padding: 5px 15px; border-radius: 20px; font-size: 14px; font-weight: bold; }
.status-ai-pass { background: #11998e; color: white; }
.status-ai-reject { background: #eb3349; color: white; }
.status-pending { background: #f39c12; color: white; }
.status-approved { background: #3498db; color: white; }
.status-rejected { background: #9b59b6; color: white; }
.api-docs { margin-top: 30px; background: #f8f9fa; padding: 20px; border-radius: 10px; }
.api-docs h3 { color: #333; }
.api-docs code { background: #e9ecef; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
</style>
</head>
<body>
<h1>🖼️ 图片审核系统</h1>
<div class="container">
<div class="upload-section">
<label class="upload-btn">📁 选择图片审核</label>
<input type="file" id="fileInput" accept="image/*">
<div id="preview"></div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>🤖 AI正在审核中,请稍候...</p>
</div>
<div class="result" id="result"></div>
</div>
<div class="history">
<h2>📋 审核记录</h2>
<div id="historyList"></div>
</div>
<div class="api-docs">
<h3>🔌 API 接口文档</h3>
<p><strong>审核图片:</strong> <code>POST /api/moderate</code></p>
<p><strong>人工通过:</strong> <code>POST /api/approve/{id}</code></p>
<p><strong>人工拒绝:</strong> <code>POST /api/reject/{id}</code></p>
<p><strong>查看详情:</strong> <code>GET /api/status/{id}</code></p>
</div>
</div>
<script>
let currentId = null;
document.getElementById('fileInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
const base64 = event.target.result.split(',')[1];
document.getElementById('preview').innerHTML = '<img src="' + event.target.result + '">';
document.getElementById('preview').style.display = 'block';
document.getElementById('result').style.display = 'none';
document.getElementById('loading').style.display = 'block';
try {
const response = await fetch('/api/moderate', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({image: base64})
});
const data = await response.json();
if (data.error) {
alert('审核失败: ' + data.error);
} else {
currentId = data.id;
showResult(data);
}
} catch (err) {
alert('审核失败: ' + err);
} finally {
document.getElementById('loading').style.display = 'none';
}
};
reader.readAsDataURL(file);
});
function showResult(data) {
const resultDiv = document.getElementById('result');
resultDiv.style.display = 'block';
resultDiv.className = 'result ' + data.status;
if (data.ai_result === 'TRUE') {
resultDiv.innerHTML = '<h2>✅ AI审核通过</h2><p>图片内容安全</p>';
} else {
resultDiv.innerHTML = '<h2>❌ AI审核未通过</h2><p>等待人工复审</p>' +
'<div class="action-btns">' +
'<button class="btn btn-approve" onclick="approve()">✓ 人工通过</button>' +
'<button class="btn btn-reject" onclick="reject()">✗ 人工拒绝</button></div>';
}
loadHistory();
}
async function approve() {
await fetch('/api/approve/' + currentId, {method: 'POST'});
showResult({status: 'approved', ai_result: 'MANUAL_APPROVE'});
}
async function reject() {
await fetch('/api/reject/' + currentId, {method: 'POST'});
showResult({status: 'rejected', ai_result: 'MANUAL_REJECT'});
}
async function loadHistory() {
const response = await fetch('/api/history');
const data = await response.json();
const list = document.getElementById('historyList');
if (data.items && data.items.length > 0) {
list.innerHTML = data.items.map(item => `
<div class="history-item">
<img src="${item.image}">
<div class="info">
<div><strong>AI结果:</strong> <span class="status-badge status-${item.ai_result === 'TRUE' ? 'ai-pass' : 'ai-reject'}">${item.ai_result}</span></div>
<div><strong>最终状态:</strong> <span class="status-badge status-${item.status}">${item.status_text}</span></div>
<div><small>${item.time}</small></div>
</div>
</div>
`).join('');
} else {
list.innerHTML = '<p style="text-align:center;color:#666;">暂无审核记录</p>';
}
}
loadHistory();
</script>
</body>
</html>
"""
def encode_image_to_base64(image_data):
try:
return base64.b64decode(image_data)
except:
return None
def call_nvidia_api(image_base64):
if not NVIDIA_API_KEY:
return None, "API密钥未配置,请在Space设置中添加NVIDIA_API_KEY环境变量"
headers = {
"Authorization": f"Bearer {NVIDIA_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": MODEL_NAME,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": SYSTEM_PROMPT},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_base64}"},
},
],
}
],
"max_tokens": 10,
"thinking": {"type": "off"},
}
try:
response = requests.post(
NVIDIA_ENDPOINT, headers=headers, json=payload, timeout=120
)
if response.status_code == 200:
result = response.json()
content = result["choices"][0]["message"]["content"].strip().upper()
return content, None
else:
return None, f"API错误: {response.status_code} - {response.text}"
except Exception as e:
return None, str(e)
@app.route("/")
def index():
return render_template_string(HTML_TEMPLATE)
@app.route("/api/moderate", methods=["POST"])
def moderate():
data = request.json
image_data = data.get("image", "")
if not image_data:
return jsonify({"error": "没有图片数据"}), 400
image_bytes = encode_image_to_base64(image_data)
if not image_bytes:
return jsonify({"error": "图片格式错误"}), 400
ai_result, error = call_nvidia_api(image_data)
if error:
return jsonify({"error": error}), 500
with data_lock:
import uuid
item_id = str(uuid.uuid4())[:8]
status = "ai-pass" if ai_result == "TRUE" else "ai-reject"
moderation_data[item_id] = {
"id": item_id,
"image": f"data:image/jpeg;base64,{image_data}",
"ai_result": ai_result,
"status": status,
"final_status": None,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
return jsonify({"id": item_id, "ai_result": ai_result, "status": status})
@app.route("/api/approve/<item_id>", methods=["POST"])
def approve(item_id):
with data_lock:
if item_id in moderation_data:
moderation_data[item_id]["status"] = "approved"
moderation_data[item_id]["final_status"] = "approved"
return jsonify({"success": True, "status": "approved"})
return jsonify({"error": "未找到记录"}), 404
@app.route("/api/reject/<item_id>", methods=["POST"])
def reject(item_id):
with data_lock:
if item_id in moderation_data:
moderation_data[item_id]["status"] = "rejected"
moderation_data[item_id]["final_status"] = "rejected"
return jsonify({"success": True, "status": "rejected"})
return jsonify({"error": "未找到记录"}), 404
@app.route("/api/status/<item_id>", methods=["GET"])
def get_status(item_id):
with data_lock:
if item_id in moderation_data:
item = moderation_data[item_id]
return jsonify(
{
"id": item["id"],
"ai_result": item["ai_result"],
"status": item["final_status"]
if item["final_status"]
else item["status"],
"time": item["time"],
}
)
return jsonify({"error": "未找到记录"}), 404
@app.route("/api/history", methods=["GET"])
def history():
with data_lock:
items = list(moderation_data.values())[-20:][::-1]
status_text = {
"ai-pass": "AI通过",
"ai-reject": "待复审",
"approved": "人工通过",
"rejected": "人工拒绝",
}
return jsonify(
{
"items": [
{
"id": item["id"],
"image": item["image"],
"ai_result": item["ai_result"],
"status": item["final_status"]
if item["final_status"]
else item["status"],
"status_text": status_text.get(
item["final_status"]
if item["final_status"]
else item["status"],
"未知",
),
"time": item["time"],
}
for item in items
]
}
)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 7860))
app.run(host="0.0.0.0", port=port)