diff --git a/diagnose/ch.jpg b/ch.jpg similarity index 100% rename from diagnose/ch.jpg rename to ch.jpg diff --git a/diagnose/admin.py b/diagnose/admin.py index 46df67d..af7ee9b 100644 --- a/diagnose/admin.py +++ b/diagnose/admin.py @@ -1,9 +1,12 @@ from django.contrib import admin # Register your models here. -from . models import Papers +from . models import Papers, OpenAIDiagnose @admin.register(Papers) - class PaperAdmin(admin.ModelAdmin): - list_display = ('id', 'uuid', 'status', 'created_at', 'updated_at') \ No newline at end of file + list_display = ('id', 'uuid', 'status', 'created_at', 'updated_at') + +@admin.register(OpenAIDiagnose) +class OpenAIDiagnoseAdmin(admin.ModelAdmin): + list_display = ('id', 'uuid', 'status', 'diagnose_type', 'created_at', 'updated_at') \ No newline at end of file diff --git a/diagnose/db_views.py b/diagnose/db_views.py new file mode 100644 index 0000000..2f3cac7 --- /dev/null +++ b/diagnose/db_views.py @@ -0,0 +1,69 @@ + + +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from authentication.authentication import CustomTokenAuthentication +import uuid +import os +from django.conf import settings +from django.http import JsonResponse +from rest_framework import status +from .models import OpenAIDiagnose +from .openai_dia import openai_diagnoser_asyna_wraper +from django.core.exceptions import ObjectDoesNotExist +import json + +class MyProtectedUploadDiagnose(APIView): + authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证 + permission_classes = [IsAuthenticated] # 需要用户认证才能访问 + + def post(self, request): + user = request.user + uuid_str = str(uuid.uuid4()) + save_file_path = os.path.join(settings.BASE_DIR, 'upload_file', user.username, uuid_str) + + os.makedirs(os.path.dirname(save_file_path), exist_ok=True) + + # 获取上传的文件 + uploaded_file = request.FILES.get('file') + if not uploaded_file: + return JsonResponse({"error": "No file uploaded."}, status=status.HTTP_400_BAD_REQUEST) + + # 保存文件 + try: + with open(save_file_path, 'wb+') as destination: + for chunk in uploaded_file.chunks(): + destination.write(chunk) + except: + return JsonResponse({"error": "Error saving file."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + obj = OpenAIDiagnose.objects.create(uuid=uuid_str, user=user, status='P') + obj.save() + user.subject_usage_count = user.subject_usage_count - 1 + user.save() + openai_diagnoser_asyna_wraper.delay(uuid_str, user.username, "") + + return JsonResponse({"message": "File uploaded successfully.", "uuid": uuid_str}, status=status.HTTP_200_OK) + + +class MyProtectedDiagnoseCheck(APIView): + authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证 + permission_classes = [IsAuthenticated] # 需要用户认证才能访问 + + def post(self, request): + user = request.user + # 处理 POST 请求,更新用户的联系信息 + try: + data = json.loads(request.body) + uuid_str = data.get("uuid") + except json.JSONDecodeError: + return JsonResponse({"error": "Invalid JSON format"}, status=400) + try: + # print(uuid_str) + obj = OpenAIDiagnose.objects.get(uuid=uuid_str, user=user) # 只获取一个对象 + if obj.status == 'D': + return JsonResponse({"status": "done"}) + else: + return JsonResponse({"status": "processing"}) + except ObjectDoesNotExist: + return JsonResponse({"error_code": "2004"}, status=404) \ No newline at end of file diff --git a/diagnose/dp_views.py b/diagnose/dp_views.py index 6971e82..faa5eb3 100644 --- a/diagnose/dp_views.py +++ b/diagnose/dp_views.py @@ -1,14 +1,9 @@ # -*- coding: utf-8 -*- import os -from openai import OpenAI -import markdown2 -import pdfkit from django.conf import settings from django.http import JsonResponse, FileResponse -from celery import shared_task import uuid -from django.utils import timezone from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from authentication.authentication import CustomTokenAuthentication @@ -16,6 +11,8 @@ from django.core.exceptions import ObjectDoesNotExist from .models import Papers from ec_user.models import SchoolInfo import json + +from .openai_gen import paper_gen_async_wraper, convert_to_pdf # ============== PYdoc =================== # from docx import Document # import pypandoc @@ -57,101 +54,6 @@ class DpArguments(): -def generate_html_paper(md_result, file_path_name): - # 写到md中 - md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8') - md_file.write(md_result) - md_file.close() - - # 从md中读出来再写到html中 - md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8') - md_text = md_read_file.read() - md_read_file.close() - html = markdown2.markdown(md_text) - html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8') - html_write_file.write( - """ - - - """+html) - html_write_file.close() - -def convert_to_pdf(file_path_name): - # =============== pdfkit ================ - # 从html中读出来再写到pdf中 - f = open(f'{file_path_name}.html', 'r', encoding='utf-8') - content = f.read() - f.close() - config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_PATH) - pdfkit.from_string(content, f'{file_path_name}.pdf', configuration=config) - -@shared_task -def async_wraper(file_path_name, uuid_str, subject, textbook_version, start_grade, \ - start_semester, start_chapter, end_grade, end_semester, end_chapter, student): - - # client = OpenAI( - # api_key = settings.OPAI_API_KEY, - # base_url = settings.OPAI_BASE_URL, - # ) - # completion = client.chat.completions.create( - # model = "deepseek-r1-250120", # your model endpoint ID - # messages = [ - # {"role": "system", "content": "你是教学助手"}, - # # {"role": "user", - # # "content": f"请根据以下信息生成一份检测卷:学科为{subject},教材版本是{textbook_version},\ - # # 检测范围从{start_grade}年级{start_semester}学期{start_chapter}章\ - # # 到{end_grade}年级{end_semester}学期{end_chapter}章,学生年级为{student.get('grade')},\ - # # 年龄为{student.get('age')},所在学校是{student.get('school')}。\ - # # 根据答题所需时间一个半小时设置题量。题目有梯度。\ - # # 试卷题目为{subject}{textbook_version}检测卷。只生成题目,不输出答案及说明等其他内容。" - # # }, - # {"role": "user", "content": f"""请严格按照以下要求生成一份{subject}检测卷: - # 【试卷信息】 - # 1. 标题:**{subject}{textbook_version}综合检测卷** - # 2. 检测范围:{start_grade}年级{start_semester}学期第{start_chapter}章 至 - # {end_grade}年级{end_semester}学期第{end_chapter}章 - # 3. 适用对象:{student.get('school')} {student.get('grade')}年级学生(年龄{student.get('age')}) - # 【命题要求】 - # 1. 题型结构: - # - 按难度梯度分为基础题(60%)、中档题(30%)、提高题(10%) - # - 根据学科特点设计合理的题型(如数学可设置选择题/填空题/计算题/应用题,语文可设置阅读理解/文言文/写作等) - # - 同一知识点不重复考查 - # 2. 题量控制: - # - 总题数控制在18-22题(以优秀学生完成时间约75分钟为标准) - # - 选择题不超过5题,填空题不超过6题 - # - 需包含至少1道综合应用题(理科)或材料分析题(文科) - # 3. 内容要求: - # - 严格按照指定章节范围命题 - # - 题目表述清晰无歧义,数字单位标注完整 - # - 禁止出现需图片辅助的题目 - # - 数学类题目需给出必要公式位置(如"(提示:可用公式____)") - # 【输出格式】 - # 仅输出以下内容: - # 1. 试卷标题(加粗居中) - # 2. 考试说明(含总分值100分、考试时长、姓名班级填写处) - # 3. 分模块的题目内容(标注题型、分值和题号) - # 4. "——以下为题目区域——" 分隔线 - # 禁止包含: - # - 答案及解析 - # - 评分标准 - # - 额外说明文字 - # - markdown格式及特殊符号"""} - # ], - # ) - # generate_html_paper(completion.choices[0].message.content, file_path_name) - import time - time.sleep(30) - generate_html_paper(""" # Test md""", file_path_name) - try: - obj = Papers.objects.get(uuid=uuid_str) # 只获取一个对象 - obj.status = 'D' - obj.updated_at = timezone.now() - obj.save() - except ObjectDoesNotExist: - print("warning: record not found") - class MyProtectedGeneratePaper(APIView): authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证 permission_classes = [IsAuthenticated] # 需要用户认证才能访问 @@ -197,7 +99,7 @@ class MyProtectedGeneratePaper(APIView): obj.save() user.subject_usage_count = user.subject_usage_count - 1 user.save() - async_wraper.delay(file_path_name, uuid_str, \ + paper_gen_async_wraper.delay(file_path_name, uuid_str, \ dp_args.subject, dp_args.textbook_version, dp_args.start_grade, dp_args.start_semester, dp_args.start_chapter, \ dp_args.end_grade, dp_args.end_semester, dp_args.end_chapter, dp_args.student) return JsonResponse({"status": "success", "uuid": uuid_str}) diff --git a/diagnose/migrations/0003_openaidiagnose.py b/diagnose/migrations/0003_openaidiagnose.py new file mode 100644 index 0000000..be4e31c --- /dev/null +++ b/diagnose/migrations/0003_openaidiagnose.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.19 on 2025-02-28 15:39 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('diagnose', '0002_papers_created_at_papers_updated_at'), + ] + + operations = [ + migrations.CreateModel( + name='OpenAIDiagnose', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.CharField(max_length=100)), + ('status', models.CharField(choices=[('C', '任务创建'), ('P', '生成中'), ('D', '已完成')], default='C', max_length=1)), + ('created_at', models.DateTimeField(auto_now_add=True, null=True)), + ('updated_at', models.DateTimeField(auto_now=True, null=True)), + ('diagnose_type', models.CharField(choices=[('M', '大模型试卷诊断'), ('S', '学校试卷诊断'), ('H', '作业诊断'), ('N', '课堂笔记诊断')], default='M', max_length=1)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/diagnose/models.py b/diagnose/models.py index c4bc214..92b6db8 100644 --- a/diagnose/models.py +++ b/diagnose/models.py @@ -16,4 +16,27 @@ class Papers(models.Model): updated_at = models.DateTimeField(auto_now=True,null=True, blank=True) # 更新时间 def __str__(self): - return self.user.username + '的试卷' \ No newline at end of file + return self.user.name + '的试卷' + +class OpenAIDiagnose(models.Model): + STATUS_CHOICES = ( + ('C', '任务创建'), + ('P', '生成中'), + ('D', '已完成') + ) + DIAGNOSE_TYPE = ( + ('M', '大模型试卷诊断'), # big model + ('S', '学校试卷诊断'), # school + ('H', '作业诊断'), # homework + ('N', '课堂笔记诊断') # notebook + ) + uuid = models.CharField(max_length=100) # 一次分析唯一标识符 + # 定义一个与EcUser一对多关联的外键 + user = models.ForeignKey(EcUser, on_delete=models.CASCADE) # 当EcUser被删除时,关联的Papers也会被删除 + status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='C') # 默认值设置为'C' + created_at = models.DateTimeField(auto_now_add=True,null=True, blank=True) # 创建时间 + updated_at = models.DateTimeField(auto_now=True,null=True, blank=True) # 更新时间 + diagnose_type = models.CharField(max_length=1, choices=DIAGNOSE_TYPE, default='M') # 默认值设置为'M' + + def __str__(self): + return self.user.name + '的诊断' \ No newline at end of file diff --git a/diagnose/openai_dia.py b/diagnose/openai_dia.py new file mode 100644 index 0000000..6ceab20 --- /dev/null +++ b/diagnose/openai_dia.py @@ -0,0 +1,90 @@ +import os +from openai import OpenAI +import base64 +from PIL import Image +from celery import shared_task +from django.conf import settings +import markdown2 +from django.utils import timezone +from .models import OpenAIDiagnose +from django.core.exceptions import ObjectDoesNotExist + + +def get_image_type(image_path): + with Image.open(image_path) as img: + return img.format # 返回图片的格式,例如 'PNG' 或 'JPEG' + +def encode_image(image_path): + with open(image_path, "rb") as image_file: + return base64.b64encode(image_file.read()).decode('utf-8') +# 将图片转为Base64编码 + +def generate_html_paper(md_result, file_path_name): + # 写到md中 + md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8') + md_file.write(md_result) + md_file.close() + + # 从md中读出来再写到html中 + md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8') + md_text = md_read_file.read() + md_read_file.close() + html = markdown2.markdown(md_text) + html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8') + html_write_file.write( + """ + + + """+html) + html_write_file.close() + +@shared_task +def openai_diagnoser_asyna_wraper(uuid_str: str, username: str, diagnose_type: str): + client = OpenAI( + api_key = settings.OPAI_IMG_API_KEY, + base_url = settings.OPAI_IMG_BASE_URL + ) + # 需要传给大模型的图片 + # image_path = "ch.jpg" + image_path = os.path.join(settings.BASE_DIR, 'upload_file', username, uuid_str) + base64_image = encode_image(image_path) + image_type = get_image_type(image_path) + image_type = image_type.lower() + # print("===> image type:" + image_type) + # Image input: + response = client.chat.completions.create( + model="doubao-1-5-vision-pro-32k-250115", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "图中是几个题目,帮我分析一下,我是否做对了", + }, + { + "type": "image_url", + "image_url": { + # 需要注意:传入Base64编码前需要增加前缀 data:image/{图片格式};base64,{Base64编码}: + # PNG图片:"url": f"data:image/png;base64,{base64_image}" + # JEPG图片:"url": f"data:image/jpeg;base64,{base64_image}" + # WEBP图片:"url": f"data:image/webp;base64,{base64_image}" + "url": f"data:image/{image_type};base64,{base64_image}" + }, + }, + ], + } + ], + ) + + # print(response.choices[0].message.content) + generate_html_paper(response.choices[0].message.content, image_path) + try: + obj = OpenAIDiagnose.objects.get(uuid=uuid_str) # 只获取一个对象 + obj.status = 'D' + obj.updated_at = timezone.now() + obj.save() + except ObjectDoesNotExist: + print("warning: record not found") \ No newline at end of file diff --git a/diagnose/openai_gen.py b/diagnose/openai_gen.py new file mode 100644 index 0000000..7e2d283 --- /dev/null +++ b/diagnose/openai_gen.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +from openai import OpenAI +import markdown2 +import pdfkit +from django.conf import settings +from celery import shared_task +from django.utils import timezone +from django.core.exceptions import ObjectDoesNotExist +from .models import Papers + +# ============== PYdoc =================== +# from docx import Document +# import pypandoc +# pypandoc.download_pandoc() + +# ================ weasyprint =========== +# 库用不了 +# from weasyprint import HTML +# HTML(string=html).write_pdf("output.pdf") + + + +def generate_html_paper(md_result, file_path_name): + # 写到md中 + md_file = open(f'{file_path_name}.md', 'w', encoding='utf-8') + md_file.write(md_result) + md_file.close() + + # 从md中读出来再写到html中 + md_read_file = open(f'{file_path_name}.md', 'r', encoding='utf-8') + md_text = md_read_file.read() + md_read_file.close() + html = markdown2.markdown(md_text) + html_write_file = open(f'{file_path_name}.html', 'w', encoding='utf-8') + html_write_file.write( + """ + + + """+html) + html_write_file.close() + +def convert_to_pdf(file_path_name): + # =============== pdfkit ================ + # 从html中读出来再写到pdf中 + f = open(f'{file_path_name}.html', 'r', encoding='utf-8') + content = f.read() + f.close() + config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_PATH) + pdfkit.from_string(content, f'{file_path_name}.pdf', configuration=config) + +@shared_task +def paper_gen_async_wraper(file_path_name, uuid_str, subject, textbook_version, start_grade, \ + start_semester, start_chapter, end_grade, end_semester, end_chapter, student): + + # client = OpenAI( + # api_key = settings.OPAI_API_KEY, + # base_url = settings.OPAI_BASE_URL, + # ) + # completion = client.chat.completions.create( + # model = "deepseek-r1-250120", # your model endpoint ID + # messages = [ + # {"role": "system", "content": "你是教学助手"}, + # # {"role": "user", + # # "content": f"请根据以下信息生成一份检测卷:学科为{subject},教材版本是{textbook_version},\ + # # 检测范围从{start_grade}年级{start_semester}学期{start_chapter}章\ + # # 到{end_grade}年级{end_semester}学期{end_chapter}章,学生年级为{student.get('grade')},\ + # # 年龄为{student.get('age')},所在学校是{student.get('school')}。\ + # # 根据答题所需时间一个半小时设置题量。题目有梯度。\ + # # 试卷题目为{subject}{textbook_version}检测卷。只生成题目,不输出答案及说明等其他内容。" + # # }, + # {"role": "user", "content": f"""请严格按照以下要求生成一份{subject}检测卷: + # 【试卷信息】 + # 1. 标题:**{subject}{textbook_version}综合检测卷** + # 2. 检测范围:{start_grade}年级{start_semester}学期第{start_chapter}章 至 + # {end_grade}年级{end_semester}学期第{end_chapter}章 + # 3. 适用对象:{student.get('school')} {student.get('grade')}年级学生(年龄{student.get('age')}) + # 【命题要求】 + # 1. 题型结构: + # - 按难度梯度分为基础题(60%)、中档题(30%)、提高题(10%) + # - 根据学科特点设计合理的题型(如数学可设置选择题/填空题/计算题/应用题,语文可设置阅读理解/文言文/写作等) + # - 同一知识点不重复考查 + # 2. 题量控制: + # - 总题数控制在18-22题(以优秀学生完成时间约75分钟为标准) + # - 选择题不超过5题,填空题不超过6题 + # - 需包含至少1道综合应用题(理科)或材料分析题(文科) + # 3. 内容要求: + # - 严格按照指定章节范围命题 + # - 题目表述清晰无歧义,数字单位标注完整 + # - 禁止出现需图片辅助的题目 + # - 数学类题目需给出必要公式位置(如"(提示:可用公式____)") + # 【输出格式】 + # 仅输出以下内容: + # 1. 试卷标题(加粗居中) + # 2. 考试说明(含总分值100分、考试时长、姓名班级填写处) + # 3. 分模块的题目内容(标注题型、分值和题号) + # 4. "——以下为题目区域——" 分隔线 + # 禁止包含: + # - 答案及解析 + # - 评分标准 + # - 额外说明文字 + # - markdown格式及特殊符号"""} + # ], + # ) + # generate_html_paper(completion.choices[0].message.content, file_path_name) + import time + time.sleep(30) + generate_html_paper(""" # Test md""", file_path_name) + try: + obj = Papers.objects.get(uuid=uuid_str) # 只获取一个对象 + obj.status = 'D' + obj.updated_at = timezone.now() + obj.save() + except ObjectDoesNotExist: + print("warning: record not found") \ No newline at end of file diff --git a/diagnose/openaitest.py b/diagnose/openaitest.py deleted file mode 100644 index de0c8d5..0000000 --- a/diagnose/openaitest.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -from openai import OpenAI -import base64 - -client = OpenAI( - api_key = 'd04d386a-7c67-4927-8251-171a236583a6', - base_url = "https://ark.cn-beijing.volces.com/api/v3", -) -def encode_image(image_path): - with open(image_path, "rb") as image_file: - return base64.b64encode(image_file.read()).decode('utf-8') -# 将图片转为Base64编码 - -# 需要传给大模型的图片 -image_path = "ch.jpg" -base64_image = encode_image(image_path) -# Image input: -response = client.chat.completions.create( - model="doubao-1-5-vision-pro-32k-250115", - messages=[ - { - "role": "user", - "content": [ - { - "type": "text", - "text": "图中是几个语文题目,帮我分析一下,我是否做对了", - }, - { - "type": "image_url", - "image_url": { - # 需要注意:传入Base64编码前需要增加前缀 data:image/{图片格式};base64,{Base64编码}: - # PNG图片:"url": f"data:image/png;base64,{base64_image}" - # JEPG图片:"url": f"data:image/jpeg;base64,{base64_image}" - # WEBP图片:"url": f"data:image/webp;base64,{base64_image}" - "url": f"data:image/jpg;base64,{base64_image}" - }, - }, - ], - } - ], -) - -print(response.choices[0].message.content) \ No newline at end of file diff --git a/diagnose/upload_views.py b/diagnose/upload_views.py deleted file mode 100644 index beef3df..0000000 --- a/diagnose/upload_views.py +++ /dev/null @@ -1,36 +0,0 @@ - - -from rest_framework.views import APIView -from rest_framework.permissions import IsAuthenticated -from authentication.authentication import CustomTokenAuthentication -import uuid -import os -from django.conf import settings -from django.http import JsonResponse -from rest_framework import status - -class MyProtectedUploadfile(APIView): - authentication_classes = [CustomTokenAuthentication] # 使用自定义的 Token 认证 - permission_classes = [IsAuthenticated] # 需要用户认证才能访问 - - def post(self, request): - user = request.user - uuid_str = str(uuid.uuid4()) - save_file_path = os.path.join(settings.BASE_DIR, 'upload_file', user.username, uuid_str) - - os.makedirs(os.path.dirname(save_file_path), exist_ok=True) - - # 获取上传的文件 - uploaded_file = request.FILES.get('file') - if not uploaded_file: - return JsonResponse({"error": "No file uploaded."}, status=status.HTTP_400_BAD_REQUEST) - - # 保存文件 - try: - with open(save_file_path, 'wb+') as destination: - for chunk in uploaded_file.chunks(): - destination.write(chunk) - except: - return JsonResponse({"error": "Error saving file."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - - return JsonResponse({"message": "File uploaded successfully."}, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/diagnose/urls.py b/diagnose/urls.py index c86332a..cab528b 100644 --- a/diagnose/urls.py +++ b/diagnose/urls.py @@ -1,11 +1,12 @@ from django.urls import path from .dp_views import MyProtectedGeneratePaper, MyProtectedGenerateCheck, MyProtectedDownloadPaper -from .upload_views import MyProtectedUploadfile +from .db_views import MyProtectedUploadDiagnose, MyProtectedDiagnoseCheck urlpatterns = [ path('', MyProtectedGeneratePaper.as_view(), name='api_generate_paper'), path('check/', MyProtectedGenerateCheck.as_view(), name='api_generate_check'), path('download/', MyProtectedDownloadPaper.as_view(), name='api_download_paper'), - path('upload/', MyProtectedUploadfile.as_view(), name='api_upload_file') + path('upload/', MyProtectedUploadDiagnose.as_view(), name='api_upload_file'), + path('upload_diagnose_check/', MyProtectedDiagnoseCheck.as_view(), name='api_diagnose_file') ] diff --git a/educheck/settings.py b/educheck/settings.py index a682123..6db9728 100644 --- a/educheck/settings.py +++ b/educheck/settings.py @@ -172,6 +172,9 @@ ERROR_CODE_MAP = { OPAI_API_KEY = 'e119bd6c-a22a-404e-a002-6d72a2cea65d' OPAI_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3' +OPAI_IMG_BASE_URL = 'https://ark.cn-beijing.volces.com/api/v3' +OPAI_IMG_API_KEY = 'd04d386a-7c67-4927-8251-171a236583a6' + WKHTMLTOPDF_PATH = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe' # settings.py