This commit is contained in:
@@ -16,5 +16,6 @@ urlpatterns = [
|
|||||||
path('api/projects/<int:project_id>/', judge_views.project_detail_api, name='judge_project_detail_api'),
|
path('api/projects/<int:project_id>/', judge_views.project_detail_api, name='judge_project_detail_api'),
|
||||||
path('api/score/submit/', judge_views.submit_score, name='judge_submit_score'),
|
path('api/score/submit/', judge_views.submit_score, name='judge_submit_score'),
|
||||||
path('api/upload/', judge_views.upload_audio, name='judge_api_upload'),
|
path('api/upload/', judge_views.upload_audio, name='judge_api_upload'),
|
||||||
|
path('api/upload/url/', judge_views.upload_audio_url, name='judge_api_upload_url'),
|
||||||
path('api/ai/<str:task_id>/delete/', judge_views.delete_ai_task, name='judge_delete_ai_task'),
|
path('api/ai/<str:task_id>/delete/', judge_views.delete_ai_task, name='judge_delete_ai_task'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -509,6 +509,93 @@ def upload_audio(request):
|
|||||||
logger.error(f"Upload error: {e}")
|
logger.error(f"Upload error: {e}")
|
||||||
return JsonResponse({'success': False, 'message': str(e)})
|
return JsonResponse({'success': False, 'message': str(e)})
|
||||||
|
|
||||||
|
@judge_required
|
||||||
|
@csrf_exempt
|
||||||
|
def upload_audio_url(request):
|
||||||
|
"""
|
||||||
|
处理 URL 上传音频的 API
|
||||||
|
通过给定的音频 URL 直接进行处理,无需上传文件
|
||||||
|
"""
|
||||||
|
role = request.session.get('judge_role')
|
||||||
|
if role not in ['judge', 'guest']:
|
||||||
|
return JsonResponse({'success': False, 'message': 'Permission denied'})
|
||||||
|
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'message': 'Method not allowed'})
|
||||||
|
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return JsonResponse({'success': False, 'message': 'Invalid JSON'})
|
||||||
|
|
||||||
|
audio_url = data.get('url')
|
||||||
|
project_id = data.get('project_id')
|
||||||
|
|
||||||
|
if not audio_url or not project_id:
|
||||||
|
return JsonResponse({'success': False, 'message': 'Missing url or project_id'})
|
||||||
|
|
||||||
|
# 验证 URL 格式
|
||||||
|
if not audio_url.startswith(('http://', 'https://')):
|
||||||
|
return JsonResponse({'success': False, 'message': 'Invalid URL format'})
|
||||||
|
|
||||||
|
judge_id = request.session['judge_id']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 验证权限
|
||||||
|
user = WeChatUser.objects.get(id=judge_id)
|
||||||
|
project = Project.objects.get(id=project_id)
|
||||||
|
|
||||||
|
enrollment = CompetitionEnrollment.objects.filter(
|
||||||
|
user=user,
|
||||||
|
role=role,
|
||||||
|
competition=project.competition
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not enrollment:
|
||||||
|
return JsonResponse({'success': False, 'message': 'No permission for this project'})
|
||||||
|
|
||||||
|
# 创建任务记录,使用 URL 作为 file_url
|
||||||
|
service = AliyunTingwuService()
|
||||||
|
|
||||||
|
task = TranscriptionTask.objects.create(
|
||||||
|
project=project,
|
||||||
|
file_url=audio_url,
|
||||||
|
status=TranscriptionTask.Status.PENDING
|
||||||
|
)
|
||||||
|
|
||||||
|
# 调用 Tingwu 服务
|
||||||
|
try:
|
||||||
|
tingwu_response = service.create_transcription_task(audio_url)
|
||||||
|
|
||||||
|
if 'Data' in tingwu_response and isinstance(tingwu_response['Data'], dict):
|
||||||
|
task_id = tingwu_response['Data'].get('TaskId')
|
||||||
|
else:
|
||||||
|
task_id = tingwu_response.get('TaskId')
|
||||||
|
|
||||||
|
if task_id:
|
||||||
|
task.task_id = task_id
|
||||||
|
task.status = TranscriptionTask.Status.PROCESSING
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
log_audit(request, 'UPLOAD_AUDIO_URL', f"Task {task.id}", 'SUCCESS')
|
||||||
|
return JsonResponse({'success': True, 'task_id': task.id, 'file_url': audio_url})
|
||||||
|
else:
|
||||||
|
task.status = TranscriptionTask.Status.FAILED
|
||||||
|
task.error_message = "No TaskId returned"
|
||||||
|
task.save()
|
||||||
|
return JsonResponse({'success': False, 'message': 'Failed to create Tingwu task'})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
task.status = TranscriptionTask.Status.FAILED
|
||||||
|
task.error_message = str(e)
|
||||||
|
task.save()
|
||||||
|
return JsonResponse({'success': False, 'message': f'Tingwu Error: {e}'})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Upload URL error: {e}")
|
||||||
|
return JsonResponse({'success': False, 'message': str(e)})
|
||||||
|
|
||||||
@judge_required
|
@judge_required
|
||||||
def ai_manage(request):
|
def ai_manage(request):
|
||||||
# Contestants cannot access AI manage
|
# Contestants cannot access AI manage
|
||||||
|
|||||||
@@ -204,6 +204,21 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">选择上传方式</label>
|
||||||
|
<div class="flex space-x-4 mb-3">
|
||||||
|
<label class="inline-flex items-center">
|
||||||
|
<input type="radio" name="uploadType" value="file" checked class="form-radio text-blue-600" onchange="toggleUploadType()">
|
||||||
|
<span class="ml-2 text-sm text-gray-700">文件上传</span>
|
||||||
|
</label>
|
||||||
|
<label class="inline-flex items-center">
|
||||||
|
<input type="radio" name="uploadType" value="url" class="form-radio text-blue-600" onchange="toggleUploadType()">
|
||||||
|
<span class="ml-2 text-sm text-gray-700">URL 上传</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文件上传 -->
|
||||||
|
<div id="fileUploadSection">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">选择文件 (支持mp3/mp4, ≤50MB)</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">选择文件 (支持mp3/mp4, ≤50MB)</label>
|
||||||
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md hover:border-blue-400 transition-colors cursor-pointer" onclick="document.getElementById('fileInput').click()">
|
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md hover:border-blue-400 transition-colors cursor-pointer" onclick="document.getElementById('fileInput').click()">
|
||||||
<div class="space-y-1 text-center">
|
<div class="space-y-1 text-center">
|
||||||
@@ -211,7 +226,7 @@
|
|||||||
<div class="flex text-sm text-gray-600">
|
<div class="flex text-sm text-gray-600">
|
||||||
<label for="fileInput" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-600 hover:text-blue-500 focus-within:outline-none">
|
<label for="fileInput" class="relative cursor-pointer bg-white rounded-md font-medium text-blue-600 hover:text-blue-500 focus-within:outline-none">
|
||||||
<span>点击上传</span>
|
<span>点击上传</span>
|
||||||
<input id="fileInput" name="fileInput" type="file" class="sr-only" multiple accept=".mp3,.mp4" required onchange="updateFileName(this)">
|
<input id="fileInput" name="fileInput" type="file" class="sr-only" multiple accept="audio/mpeg,audio/mp4,audio/*,.mp3,.mp4" onchange="updateFileName(this)">
|
||||||
</label>
|
</label>
|
||||||
<p class="pl-1">或拖拽文件到这里</p>
|
<p class="pl-1">或拖拽文件到这里</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,6 +236,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- URL 上传 -->
|
||||||
|
<div id="urlUploadSection" style="display: none;">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">音频 URL 地址</label>
|
||||||
|
<input type="url" id="audioUrlInput" class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md p-3" placeholder="https://example.com/audio.mp3">
|
||||||
|
<p class="text-xs text-gray-500 mt-1">支持 MP3、MP4 等音频/视频格式的直链</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="uploadProgressContainer" style="display: none;" class="bg-gray-50 p-3 rounded-md">
|
<div id="uploadProgressContainer" style="display: none;" class="bg-gray-50 p-3 rounded-md">
|
||||||
<div class="flex justify-between text-xs text-gray-600 mb-1">
|
<div class="flex justify-between text-xs text-gray-600 mb-1">
|
||||||
<span id="uploadStatusText">准备上传...</span>
|
<span id="uploadStatusText">准备上传...</span>
|
||||||
@@ -304,6 +326,30 @@ function updateFileName(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 切换文件上传和URL上传的显示
|
||||||
|
* 根据用户选择显示对应的输入区域
|
||||||
|
*/
|
||||||
|
function toggleUploadType() {
|
||||||
|
const uploadType = document.querySelector('input[name="uploadType"]:checked').value;
|
||||||
|
const fileUploadSection = document.getElementById('fileUploadSection');
|
||||||
|
const urlUploadSection = document.getElementById('urlUploadSection');
|
||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const urlInput = document.getElementById('audioUrlInput');
|
||||||
|
|
||||||
|
if (uploadType === 'file') {
|
||||||
|
fileUploadSection.style.display = 'block';
|
||||||
|
urlUploadSection.style.display = 'none';
|
||||||
|
fileInput.required = true;
|
||||||
|
urlInput.required = false;
|
||||||
|
} else {
|
||||||
|
fileUploadSection.style.display = 'none';
|
||||||
|
urlUploadSection.style.display = 'block';
|
||||||
|
fileInput.required = false;
|
||||||
|
urlInput.required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeModal(id) {
|
function closeModal(id) {
|
||||||
const modal = document.getElementById(id);
|
const modal = document.getElementById(id);
|
||||||
modal.classList.remove('active');
|
modal.classList.remove('active');
|
||||||
@@ -602,10 +648,10 @@ async function uploadFiles(e) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const projectSelect = document.getElementById('uploadProjectSelect');
|
const projectSelect = document.getElementById('uploadProjectSelect');
|
||||||
const fileInput = document.getElementById('fileInput');
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const audioUrlInput = document.getElementById('audioUrlInput');
|
||||||
const projectId = projectSelect.value;
|
const projectId = projectSelect.value;
|
||||||
const files = fileInput.files;
|
const files = fileInput.files;
|
||||||
|
const uploadType = document.querySelector('input[name="uploadType"]:checked').value;
|
||||||
if (files.length === 0) return;
|
|
||||||
|
|
||||||
const progressBar = document.getElementById('uploadProgressBar');
|
const progressBar = document.getElementById('uploadProgressBar');
|
||||||
const statusText = document.getElementById('uploadStatusText');
|
const statusText = document.getElementById('uploadStatusText');
|
||||||
@@ -614,6 +660,57 @@ async function uploadFiles(e) {
|
|||||||
|
|
||||||
container.style.display = 'block';
|
container.style.display = 'block';
|
||||||
|
|
||||||
|
if (uploadType === 'url') {
|
||||||
|
// URL 上传模式
|
||||||
|
const audioUrl = audioUrlInput.value.trim();
|
||||||
|
if (!audioUrl) {
|
||||||
|
alert('请输入音频 URL');
|
||||||
|
container.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusText.innerText = '正在处理 URL...';
|
||||||
|
progressBar.style.width = '30%';
|
||||||
|
percentText.innerText = '30%';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/judge/api/upload/url/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: audioUrl,
|
||||||
|
project_id: projectId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
progressBar.style.width = '100%';
|
||||||
|
percentText.innerText = '100%';
|
||||||
|
statusText.innerText = '上传成功!';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
closeModal('uploadModal');
|
||||||
|
container.style.display = 'none';
|
||||||
|
window.location.href = "{% url 'judge_ai_manage' %}";
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
alert('上传失败: ' + result.message);
|
||||||
|
container.style.display = 'none';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('上传出错: ' + err);
|
||||||
|
container.style.display = 'none';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件上传模式 (原有用 XMLHttpRequest)
|
||||||
|
if (files.length === 0) return;
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
if (file.size > 50 * 1024 * 1024) {
|
if (file.size > 50 * 1024 * 1024) {
|
||||||
|
|||||||
Reference in New Issue
Block a user