Compare commits
2 Commits
6361b7a522
...
860253bf40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860253bf40 | ||
|
|
1a30da74cf |
9
backend/.env.example
Normal file
9
backend/.env.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Aliyun OSS Configuration
|
||||||
|
ALIYUN_ACCESS_KEY_ID=LTAI5tE62GW8MKyoEaotzxXk
|
||||||
|
ALIYUN_ACCESS_KEY_SECRET=Zdzqo1fgj57DxxioXOotNKhJdSfVQW
|
||||||
|
ALIYUN_OSS_ENDPOINT=https://oss-cn-shanghai.aliyuncs.com
|
||||||
|
ALIYUN_OSS_BUCKET_NAME=tangledup-ai-staging
|
||||||
|
ALIYUN_OSS_INTERNAL_ENDPOINT=https://oss-cn-shanghai-internal.aliyuncs.com
|
||||||
|
|
||||||
|
# Aliyun Tingwu Configuration
|
||||||
|
ALIYUN_TINGWU_APP_KEY=
|
||||||
0
backend/ai_services/__init__.py
Normal file
0
backend/ai_services/__init__.py
Normal file
11
backend/ai_services/admin.py
Normal file
11
backend/ai_services/admin.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import ModelAdmin
|
||||||
|
from unfold.admin import ModelAdmin as UnfoldModelAdmin
|
||||||
|
from .models import TranscriptionTask
|
||||||
|
|
||||||
|
@admin.register(TranscriptionTask)
|
||||||
|
class TranscriptionTaskAdmin(UnfoldModelAdmin):
|
||||||
|
list_display = ['id', 'status', 'task_id', 'created_at']
|
||||||
|
list_filter = ['status', 'created_at']
|
||||||
|
search_fields = ['id', 'task_id', 'transcription', 'summary']
|
||||||
|
readonly_fields = ['id', 'created_at', 'updated_at', 'task_id']
|
||||||
5
backend/ai_services/apps.py
Normal file
5
backend/ai_services/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AiServicesConfig(AppConfig):
|
||||||
|
name = 'ai_services'
|
||||||
34
backend/ai_services/migrations/0001_initial.py
Normal file
34
backend/ai_services/migrations/0001_initial.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-03-11 05:11
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TranscriptionTask',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('file_url', models.URLField(max_length=1024, verbose_name='文件链接')),
|
||||||
|
('task_id', models.CharField(blank=True, max_length=100, null=True, verbose_name='听悟任务ID')),
|
||||||
|
('status', models.CharField(choices=[('PENDING', '等待中'), ('PROCESSING', '处理中'), ('SUCCEEDED', '成功'), ('FAILED', '失败')], default='PENDING', max_length=20, verbose_name='状态')),
|
||||||
|
('transcription', models.TextField(blank=True, null=True, verbose_name='逐字稿')),
|
||||||
|
('summary', models.TextField(blank=True, null=True, verbose_name='AI总结')),
|
||||||
|
('error_message', models.TextField(blank=True, null=True, verbose_name='错误信息')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '转写任务',
|
||||||
|
'verbose_name_plural': '转写任务',
|
||||||
|
'ordering': ['-created_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 6.0.1 on 2026-03-11 05:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ai_services', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='transcriptiontask',
|
||||||
|
name='evaluation',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='AI评语'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='transcriptiontask',
|
||||||
|
name='score',
|
||||||
|
field=models.IntegerField(blank=True, help_text='基于转写内容的评分', null=True, verbose_name='AI评分'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
backend/ai_services/migrations/__init__.py
Normal file
0
backend/ai_services/migrations/__init__.py
Normal file
35
backend/ai_services/models.py
Normal file
35
backend/ai_services/models.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import uuid
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
class TranscriptionTask(models.Model):
|
||||||
|
class Status(models.TextChoices):
|
||||||
|
PENDING = 'PENDING', _('等待中')
|
||||||
|
PROCESSING = 'PROCESSING', _('处理中')
|
||||||
|
SUCCEEDED = 'SUCCEEDED', _('成功')
|
||||||
|
FAILED = 'FAILED', _('失败')
|
||||||
|
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
file_url = models.URLField(verbose_name=_('文件链接'), max_length=1024)
|
||||||
|
task_id = models.CharField(verbose_name=_('听悟任务ID'), max_length=100, blank=True, null=True)
|
||||||
|
status = models.CharField(
|
||||||
|
verbose_name=_('状态'),
|
||||||
|
max_length=20,
|
||||||
|
choices=Status.choices,
|
||||||
|
default=Status.PENDING
|
||||||
|
)
|
||||||
|
transcription = models.TextField(verbose_name=_('逐字稿'), blank=True, null=True)
|
||||||
|
summary = models.TextField(verbose_name=_('AI总结'), blank=True, null=True)
|
||||||
|
score = models.IntegerField(verbose_name=_('AI评分'), blank=True, null=True, help_text=_('基于转写内容的评分'))
|
||||||
|
evaluation = models.TextField(verbose_name=_('AI评语'), blank=True, null=True)
|
||||||
|
error_message = models.TextField(verbose_name=_('错误信息'), blank=True, null=True)
|
||||||
|
created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('转写任务')
|
||||||
|
verbose_name_plural = _('转写任务')
|
||||||
|
ordering = ['-created_at']
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.id} - {self.get_status_display()}"
|
||||||
8
backend/ai_services/serializers.py
Normal file
8
backend/ai_services/serializers.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import TranscriptionTask
|
||||||
|
|
||||||
|
class TranscriptionTaskSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = TranscriptionTask
|
||||||
|
fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at']
|
||||||
|
read_only_fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at']
|
||||||
115
backend/ai_services/services.py
Normal file
115
backend/ai_services/services.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import oss2
|
||||||
|
from aliyunsdkcore.client import AcsClient
|
||||||
|
from aliyunsdkcore.acs_exception.exceptions import ClientException, ServerException
|
||||||
|
# 尝试导入最新的 API 版本,如果有问题可能需要调整
|
||||||
|
try:
|
||||||
|
from aliyunsdktingwu.request.v20230930 import CreateTaskRequest, GetTaskInfoRequest
|
||||||
|
except ImportError:
|
||||||
|
# Fallback or error handling if version differs
|
||||||
|
pass
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class AliyunTingwuService:
|
||||||
|
def __init__(self):
|
||||||
|
self.access_key_id = settings.ALIYUN_ACCESS_KEY_ID
|
||||||
|
self.access_key_secret = settings.ALIYUN_ACCESS_KEY_SECRET
|
||||||
|
self.oss_bucket_name = settings.ALIYUN_OSS_BUCKET_NAME
|
||||||
|
self.oss_endpoint = settings.ALIYUN_OSS_ENDPOINT
|
||||||
|
self.tingwu_app_key = settings.ALIYUN_TINGWU_APP_KEY
|
||||||
|
self.region_id = "cn-beijing" # 听悟服务主要在北京
|
||||||
|
|
||||||
|
# 初始化 OSS Bucket
|
||||||
|
if self.access_key_id and self.access_key_secret and self.oss_endpoint:
|
||||||
|
auth = oss2.Auth(self.access_key_id, self.access_key_secret)
|
||||||
|
self.bucket = oss2.Bucket(auth, self.oss_endpoint, self.oss_bucket_name)
|
||||||
|
else:
|
||||||
|
self.bucket = None
|
||||||
|
logger.warning("Aliyun OSS configuration missing.")
|
||||||
|
|
||||||
|
# 初始化听悟 Client
|
||||||
|
if self.access_key_id and self.access_key_secret:
|
||||||
|
self.client = AcsClient(
|
||||||
|
self.access_key_id,
|
||||||
|
self.access_key_secret,
|
||||||
|
self.region_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.client = None
|
||||||
|
logger.warning("Aliyun AccessKey configuration missing.")
|
||||||
|
|
||||||
|
def upload_to_oss(self, file_obj, file_name):
|
||||||
|
"""
|
||||||
|
上传文件到 OSS 并返回带签名的 URL (有效期 3 小时)
|
||||||
|
"""
|
||||||
|
if not self.bucket:
|
||||||
|
raise Exception("OSS Client not initialized")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 上传文件
|
||||||
|
# file_obj 应该是打开的文件对象或字节流
|
||||||
|
self.bucket.put_object(file_name, file_obj)
|
||||||
|
|
||||||
|
# 生成签名 URL,有效期 3 小时 (3600 * 3)
|
||||||
|
url = self.bucket.sign_url('GET', file_name, 3600 * 3)
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"OSS Upload failed: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def create_transcription_task(self, file_url, language="cn"):
|
||||||
|
"""
|
||||||
|
创建听悟转写任务
|
||||||
|
"""
|
||||||
|
if not self.client:
|
||||||
|
raise Exception("Tingwu Client not initialized")
|
||||||
|
|
||||||
|
request = CreateTaskRequest.CreateTaskRequest()
|
||||||
|
request.set_AppKey(self.tingwu_app_key)
|
||||||
|
|
||||||
|
# 配置 Input
|
||||||
|
input_param = {
|
||||||
|
"FileUrl": file_url,
|
||||||
|
"SourceLanguage": language,
|
||||||
|
"TaskKey": "transcription_task"
|
||||||
|
}
|
||||||
|
request.set_Input(json.dumps(input_param))
|
||||||
|
|
||||||
|
# 配置 Parameters (开启自动章节和摘要)
|
||||||
|
parameters = {
|
||||||
|
"Transcoding": {
|
||||||
|
"TargetAudioFormat": "mp3"
|
||||||
|
},
|
||||||
|
"AutoChaptersEnabled": True,
|
||||||
|
"SummarizationEnabled": True
|
||||||
|
}
|
||||||
|
request.set_Parameters(json.dumps(parameters))
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.client.do_action_with_exception(request)
|
||||||
|
return json.loads(response)
|
||||||
|
except (ClientException, ServerException) as e:
|
||||||
|
logger.error(f"Tingwu CreateTask failed: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_task_info(self, task_id):
|
||||||
|
"""
|
||||||
|
查询任务状态和结果
|
||||||
|
"""
|
||||||
|
if not self.client:
|
||||||
|
raise Exception("Tingwu Client not initialized")
|
||||||
|
|
||||||
|
request = GetTaskInfoRequest.GetTaskInfoRequest()
|
||||||
|
request.set_TaskId(task_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.client.do_action_with_exception(request)
|
||||||
|
return json.loads(response)
|
||||||
|
except (ClientException, ServerException) as e:
|
||||||
|
logger.error(f"Tingwu GetTaskInfo failed: {e}")
|
||||||
|
raise e
|
||||||
3
backend/ai_services/tests.py
Normal file
3
backend/ai_services/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
11
backend/ai_services/urls.py
Normal file
11
backend/ai_services/urls.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
from .views import TranscriptionTaskViewSet, tingwu_callback
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r'transcriptions', TranscriptionTaskViewSet)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('callback/', tingwu_callback, name='tingwu-callback'),
|
||||||
|
path('', include(router.urls)),
|
||||||
|
]
|
||||||
181
backend/ai_services/views.py
Normal file
181
backend/ai_services/views.py
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
from rest_framework import viewsets, status
|
||||||
|
from rest_framework.decorators import action, api_view, permission_classes, parser_classes
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from django.conf import settings
|
||||||
|
from .models import TranscriptionTask
|
||||||
|
from .serializers import TranscriptionTaskSerializer
|
||||||
|
from .services import AliyunTingwuService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([AllowAny])
|
||||||
|
def tingwu_callback(request):
|
||||||
|
"""
|
||||||
|
处理阿里云听悟的回调消息
|
||||||
|
"""
|
||||||
|
data = request.data
|
||||||
|
logger.info(f"收到听悟回调: {data}")
|
||||||
|
|
||||||
|
# 1. 处理连通性测试消息
|
||||||
|
# 格式: {"Code": "0", "Data": {"Test": "..."}, "Message": "success", "RequestId": "..."}
|
||||||
|
if isinstance(data, dict) and 'Data' in data and 'Test' in data['Data']:
|
||||||
|
logger.info("收到听悟连通性测试请求")
|
||||||
|
return Response({'message': 'success'}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# 2. 处理任务完成消息 (根据实际文档或后续调试完善)
|
||||||
|
# 通常会包含 TaskId 和 Status
|
||||||
|
# 注意:阿里云听悟回调的结构可能在 Header 或 Body 中不同,需根据实际情况调整
|
||||||
|
# 这里是一个通用的处理逻辑
|
||||||
|
task_id = data.get('TaskId')
|
||||||
|
task_status = data.get('Status')
|
||||||
|
|
||||||
|
if task_id:
|
||||||
|
try:
|
||||||
|
task = TranscriptionTask.objects.filter(task_id=task_id).first()
|
||||||
|
if task:
|
||||||
|
if task_status == 'COMPLETE':
|
||||||
|
logger.info(f"任务 {task_id} 完成,等待下一次查询刷新")
|
||||||
|
# 可以在这里直接调用 get_task_info 刷新数据,但要注意超时
|
||||||
|
elif task_status == 'FAILED':
|
||||||
|
task.status = TranscriptionTask.Status.FAILED
|
||||||
|
task.error_message = data.get('StatusText', 'Callback reported failure')
|
||||||
|
task.save()
|
||||||
|
else:
|
||||||
|
logger.warning(f"回调收到未知任务ID: {task_id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理回调异常: {e}")
|
||||||
|
|
||||||
|
return Response({'message': 'success'}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
class TranscriptionTaskViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = TranscriptionTask.objects.all()
|
||||||
|
serializer_class = TranscriptionTaskSerializer
|
||||||
|
parser_classes = (MultiPartParser, FormParser)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
上传音频文件并创建听悟转写任务
|
||||||
|
"""
|
||||||
|
file_obj = request.FILES.get('file')
|
||||||
|
if not file_obj:
|
||||||
|
return Response({'error': '未提供文件'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
service = AliyunTingwuService()
|
||||||
|
if not service.bucket or not service.client:
|
||||||
|
return Response({'error': '阿里云服务未配置'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. 上传文件到 OSS
|
||||||
|
file_extension = file_obj.name.split('.')[-1]
|
||||||
|
file_name = f"transcription/{uuid.uuid4()}.{file_extension}"
|
||||||
|
|
||||||
|
# 使用服务上传
|
||||||
|
oss_url = service.upload_to_oss(file_obj, file_name)
|
||||||
|
|
||||||
|
# 2. 创建数据库记录
|
||||||
|
task_record = TranscriptionTask.objects.create(
|
||||||
|
file_url=oss_url,
|
||||||
|
status=TranscriptionTask.Status.PENDING
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. 调用听悟接口创建任务
|
||||||
|
try:
|
||||||
|
tingwu_response = service.create_transcription_task(oss_url)
|
||||||
|
task_id = tingwu_response.get('TaskId')
|
||||||
|
|
||||||
|
if task_id:
|
||||||
|
task_record.task_id = task_id
|
||||||
|
task_record.status = TranscriptionTask.Status.PROCESSING
|
||||||
|
task_record.save()
|
||||||
|
else:
|
||||||
|
task_record.status = TranscriptionTask.Status.FAILED
|
||||||
|
task_record.error_message = "未能获取 TaskId"
|
||||||
|
task_record.save()
|
||||||
|
return Response({'error': '未能获取 TaskId'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
task_record.status = TranscriptionTask.Status.FAILED
|
||||||
|
task_record.error_message = str(e)
|
||||||
|
task_record.save()
|
||||||
|
logger.error(f"创建听悟任务失败: {e}")
|
||||||
|
return Response({'error': f"创建听悟任务失败: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
serializer = self.get_serializer(task_record)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理上传请求失败: {e}")
|
||||||
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
@action(detail=True, methods=['get'])
|
||||||
|
def refresh_status(self, request, pk=None):
|
||||||
|
"""
|
||||||
|
刷新任务状态并获取结果
|
||||||
|
"""
|
||||||
|
task = self.get_object()
|
||||||
|
|
||||||
|
# 如果任务已经完成或失败,直接返回当前状态
|
||||||
|
if task.status in [TranscriptionTask.Status.SUCCEEDED, TranscriptionTask.Status.FAILED]:
|
||||||
|
serializer = self.get_serializer(task)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
if not task.task_id:
|
||||||
|
return Response({'error': '任务ID不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
service = AliyunTingwuService()
|
||||||
|
try:
|
||||||
|
result = service.get_task_info(task.task_id)
|
||||||
|
task_status = result.get('TaskStatus')
|
||||||
|
|
||||||
|
if task_status == 'COMPLETE':
|
||||||
|
task.status = TranscriptionTask.Status.SUCCEEDED
|
||||||
|
|
||||||
|
# 解析结果
|
||||||
|
task_result = result.get('Result', {})
|
||||||
|
|
||||||
|
# 提取逐字稿
|
||||||
|
sentences = task_result.get('Transcription', {}).get('Sentences', [])
|
||||||
|
full_text = " ".join([s.get('Text', '') for s in sentences])
|
||||||
|
task.transcription = full_text
|
||||||
|
|
||||||
|
# 提取总结
|
||||||
|
# 总结结果结构可能因配置不同而异,这里尝试获取摘要
|
||||||
|
summarization = task_result.get('Summarization', {})
|
||||||
|
# 听悟的总结通常在 Summarization.Text 或类似字段
|
||||||
|
# 如果是章节摘要,可能在 Chapters 中
|
||||||
|
# 假设是全文摘要
|
||||||
|
if 'Text' in summarization:
|
||||||
|
task.summary = summarization['Text']
|
||||||
|
elif 'Headline' in summarization:
|
||||||
|
task.summary = summarization['Headline']
|
||||||
|
else:
|
||||||
|
# 尝试从章节摘要中提取
|
||||||
|
chapters = task_result.get('Chapters', [])
|
||||||
|
summary_parts = []
|
||||||
|
for chapter in chapters:
|
||||||
|
if 'Headline' in chapter:
|
||||||
|
summary_parts.append(chapter['Headline'])
|
||||||
|
if 'Summary' in chapter:
|
||||||
|
summary_parts.append(chapter['Summary'])
|
||||||
|
task.summary = "\n".join(summary_parts)
|
||||||
|
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
elif task_status == 'FAILED':
|
||||||
|
task.status = TranscriptionTask.Status.FAILED
|
||||||
|
task.error_message = result.get('TaskStatusText', 'Unknown error')
|
||||||
|
task.save()
|
||||||
|
|
||||||
|
# 其他状态 (PENDING, RUNNING) 不做更改
|
||||||
|
|
||||||
|
serializer = self.get_serializer(task)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"刷新任务状态失败: {e}")
|
||||||
|
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
@@ -12,10 +12,14 @@ https://docs.djangoproject.com/en/6.0/ref/settings/
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Load .env file
|
||||||
|
load_dotenv(BASE_DIR / '.env')
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||||
@@ -48,6 +52,7 @@ INSTALLED_APPS = [
|
|||||||
'shop',
|
'shop',
|
||||||
'community',
|
'community',
|
||||||
'competition',
|
'competition',
|
||||||
|
'ai_services',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -348,6 +353,17 @@ UNFOLD = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "AI 听悟",
|
||||||
|
"separator": True,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"title": "转写与总结任务",
|
||||||
|
"icon": "record_voice_over",
|
||||||
|
"link": reverse_lazy("admin:ai_services_transcriptiontask_changelist"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "系统配置",
|
"title": "系统配置",
|
||||||
"separator": True,
|
"separator": True,
|
||||||
@@ -390,3 +406,11 @@ LOGGING = {
|
|||||||
'level': 'INFO',
|
'level': 'INFO',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 阿里云配置
|
||||||
|
ALIYUN_ACCESS_KEY_ID = os.environ.get('ALIYUN_ACCESS_KEY_ID', '')
|
||||||
|
ALIYUN_ACCESS_KEY_SECRET = os.environ.get('ALIYUN_ACCESS_KEY_SECRET', '')
|
||||||
|
ALIYUN_OSS_BUCKET_NAME = os.environ.get('ALIYUN_OSS_BUCKET_NAME', '')
|
||||||
|
ALIYUN_OSS_ENDPOINT = os.environ.get('ALIYUN_OSS_ENDPOINT', 'oss-cn-shanghai.aliyuncs.com')
|
||||||
|
ALIYUN_OSS_INTERNAL_ENDPOINT = os.environ.get('ALIYUN_OSS_INTERNAL_ENDPOINT', '')
|
||||||
|
ALIYUN_TINGWU_APP_KEY = os.environ.get('ALIYUN_TINGWU_APP_KEY', '') # 听悟AppKey
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ urlpatterns = [
|
|||||||
path('api/', include('shop.urls')),
|
path('api/', include('shop.urls')),
|
||||||
path('api/community/', include('community.urls')),
|
path('api/community/', include('community.urls')),
|
||||||
path('api/competition/', include('competition.urls')),
|
path('api/competition/', include('competition.urls')),
|
||||||
|
path('api/ai/', include('ai_services.urls')),
|
||||||
|
|
||||||
# Swagger文档路由
|
# Swagger文档路由
|
||||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
|
|||||||
@@ -24,3 +24,7 @@ django-filter
|
|||||||
django-admin-sortable2
|
django-admin-sortable2
|
||||||
openpyxl
|
openpyxl
|
||||||
|
|
||||||
|
aliyun-python-sdk-core==2.16.0
|
||||||
|
aliyun-python-sdk-tingwu==1.0.7
|
||||||
|
oss2==2.19.1
|
||||||
|
python-dotenv
|
||||||
|
|||||||
Reference in New Issue
Block a user