'commit'
This commit is contained in:
182
dsLightRag/JiMeng/Kit/JmImg2VideoUtil.py
Normal file
182
dsLightRag/JiMeng/Kit/JmImg2VideoUtil.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from JiMeng.Kit.JmCommon import JmCommon
|
||||||
|
from JiMeng.Kit.JmErrorCode import JmErrorCode
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger('JmImg2Video')
|
||||||
|
|
||||||
|
class JmImg2Video(JmCommon):
|
||||||
|
req_key = "jimeng_vgfm_i2v_l20"
|
||||||
|
action = "CVSync2AsyncSubmitTask"
|
||||||
|
default_max_retries = 10000 # 最大查询次数
|
||||||
|
default_retry_interval = 3000 # 查询间隔(毫秒)
|
||||||
|
|
||||||
|
def __init__(self, max_retries=None, retry_interval=None):
|
||||||
|
"""
|
||||||
|
初始化JmImg2Video实例
|
||||||
|
|
||||||
|
:param max_retries: 最大查询次数,默认使用类的默认值
|
||||||
|
:param retry_interval: 查询间隔(毫秒),默认使用类的默认值
|
||||||
|
"""
|
||||||
|
self.max_retries = max_retries if max_retries is not None else self.default_max_retries
|
||||||
|
self.retry_interval = retry_interval if retry_interval is not None else self.default_retry_interval
|
||||||
|
|
||||||
|
def create_video_task(self, image_urls, prompt):
|
||||||
|
"""
|
||||||
|
根据首帧图片和分镜头脚本生成视频任务
|
||||||
|
|
||||||
|
:param image_urls: 图片URL列表
|
||||||
|
:param prompt: 提示词(分镜头脚本)
|
||||||
|
:return: 任务ID
|
||||||
|
:raises Exception: 任务创建失败时抛出异常
|
||||||
|
"""
|
||||||
|
# 创建请求体
|
||||||
|
req = {
|
||||||
|
"req_key": self.req_key,
|
||||||
|
"image_urls": image_urls,
|
||||||
|
"prompt": prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_body = self.do_request(
|
||||||
|
method="POST",
|
||||||
|
query_list={},
|
||||||
|
body=json.dumps(req).encode('utf-8'),
|
||||||
|
action=self.action
|
||||||
|
)
|
||||||
|
result = json.loads(response_body)
|
||||||
|
code = result.get("code")
|
||||||
|
|
||||||
|
if not JmErrorCode.is_success(code):
|
||||||
|
error_msg = f"提交任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
# 获取任务ID
|
||||||
|
task_id = result.get("data", {}).get("task_id")
|
||||||
|
if not task_id:
|
||||||
|
error_msg = "提交任务成功但未返回任务ID"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
logger.info(f"任务创建成功,任务ID: {task_id}")
|
||||||
|
return task_id
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建视频任务异常: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def query_task_status(self, task_id):
|
||||||
|
"""
|
||||||
|
根据任务ID查询视频生成状态
|
||||||
|
|
||||||
|
:param task_id: 视频生成任务ID
|
||||||
|
:return: 字典,包含任务状态信息
|
||||||
|
- status: 'completed' 表示完成, 'processing' 表示处理中, 'failed' 表示失败
|
||||||
|
- video_url: 视频URL,如果任务完成
|
||||||
|
- error_msg: 错误信息,如果任务失败
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = self.query_task_result(task_id)
|
||||||
|
code = result.get("code")
|
||||||
|
|
||||||
|
if not JmErrorCode.is_success(code):
|
||||||
|
error_msg = f"查询任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error_msg": error_msg
|
||||||
|
}
|
||||||
|
|
||||||
|
data = result.get("data", {})
|
||||||
|
if data and data.get("video_url"):
|
||||||
|
video_url = data.get("video_url")
|
||||||
|
logger.info(f"任务已完成,视频地址: {video_url}")
|
||||||
|
return {
|
||||||
|
"status": "completed",
|
||||||
|
"video_url": video_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.info(f"任务处理中,任务ID: {task_id}")
|
||||||
|
return {
|
||||||
|
"status": "processing"
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"查询任务状态异常: {str(e)}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"status": "failed",
|
||||||
|
"error_msg": str(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
def wait_for_task_completion(self, task_id, download_path=None):
|
||||||
|
"""
|
||||||
|
等待任务完成并可选地下载视频
|
||||||
|
|
||||||
|
:param task_id: 视频生成任务ID
|
||||||
|
:param download_path: 视频下载路径,如果为None则不下载
|
||||||
|
:return: 视频URL,如果任务成功完成
|
||||||
|
:raises Exception: 任务失败或超时
|
||||||
|
"""
|
||||||
|
query_retry_count = 0
|
||||||
|
|
||||||
|
while query_retry_count < self.max_retries:
|
||||||
|
status_info = self.query_task_status(task_id)
|
||||||
|
|
||||||
|
if status_info["status"] == "completed":
|
||||||
|
video_url = status_info["video_url"]
|
||||||
|
if download_path:
|
||||||
|
logger.info(f"开始下载视频到: {download_path}")
|
||||||
|
self.download_file(video_url, download_path)
|
||||||
|
logger.info(f"视频已下载完成")
|
||||||
|
return video_url
|
||||||
|
elif status_info["status"] == "failed":
|
||||||
|
raise Exception(f"任务失败: {status_info.get('error_msg', '未知错误')}")
|
||||||
|
else:
|
||||||
|
logger.info(f"任务处理中,等待{self.retry_interval}毫秒后重试...")
|
||||||
|
time.sleep(self.retry_interval / 1000)
|
||||||
|
query_retry_count += 1
|
||||||
|
|
||||||
|
error_msg = f"任务查询超时,已达到最大查询次数: {self.max_retries}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
# 示例用法
|
||||||
|
image_urls = [
|
||||||
|
"https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/Text2Img.jpg"
|
||||||
|
]
|
||||||
|
prompt = """
|
||||||
|
|镜号|运镜|画面内容|
|
||||||
|
| :---: | :---: | --- |
|
||||||
|
|1|固定镜头|穿着黄色安全帽、反光背心的工人背着小女孩,左手抱着小熊玩偶,两人脸上洋溢着幸福的笑容,背景是老旧居民楼和湿漉漉的路面。|
|
||||||
|
|2|推镜头|镜头从两人全身慢慢推近至两人上半身,聚焦两人亲密温馨的神态。|
|
||||||
|
|3|平移镜头|镜头从两人正面平移至侧面,展现工人的朴实与小女孩的可爱。|
|
||||||
|
|4|拉镜头|镜头逐渐拉远,展现两人在居民楼间道路上的整体场景。|
|
||||||
|
|5|固定镜头|定格在两人互动的画面,突出亲情的温暖氛围。|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 创建实例
|
||||||
|
img2video = JmImg2Video(max_retries=100, retry_interval=2000)
|
||||||
|
|
||||||
|
# 创建视频任务
|
||||||
|
task_id = img2video.create_video_task(image_urls, prompt)
|
||||||
|
print(f"任务ID: {task_id}")
|
||||||
|
|
||||||
|
# 等待任务完成并下载视频
|
||||||
|
mp4_file_name = "image2video.mp4"
|
||||||
|
save_video_path = fr"D:\dsWork\dsProject\dsLightRag\JiMeng\{mp4_file_name}"
|
||||||
|
video_url = img2video.wait_for_task_completion(task_id, save_video_path)
|
||||||
|
print(f"视频生成完成,地址: {video_url}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"程序执行异常: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Binary file not shown.
@@ -1,107 +0,0 @@
|
|||||||
import json
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from JiMeng.Kit.JmCommon import JmCommon
|
|
||||||
from JiMeng.Kit.JmErrorCode import JmErrorCode
|
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
logger = logging.getLogger('JmImg2Video')
|
|
||||||
|
|
||||||
class JmImg2Video(JmCommon):
|
|
||||||
req_key = "jimeng_vgfm_i2v_l20"
|
|
||||||
action = "CVSync2AsyncSubmitTask"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def submit_image_to_video_task(image_urls, prompt):
|
|
||||||
"""
|
|
||||||
提交图生视频任务(使用图片URL)
|
|
||||||
|
|
||||||
:param image_urls: 图片URL列表
|
|
||||||
:param prompt: 提示词
|
|
||||||
:return: 任务结果
|
|
||||||
:raises Exception: 异常信息
|
|
||||||
"""
|
|
||||||
# 创建请求体
|
|
||||||
req = {
|
|
||||||
"req_key": JmImg2Video.req_key,
|
|
||||||
"image_urls": image_urls,
|
|
||||||
"prompt": prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
response_body = JmCommon.do_request(
|
|
||||||
method="POST",
|
|
||||||
query_list={}, # 添加空字典作为query_list参数
|
|
||||||
body=json.dumps(req).encode('utf-8'),
|
|
||||||
action=JmImg2Video.action
|
|
||||||
)
|
|
||||||
return json.loads(response_body)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
# 参考: https://www.volcengine.com/docs/85621/1544774
|
|
||||||
image_urls = [
|
|
||||||
"https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/Text2Img.jpg"
|
|
||||||
]
|
|
||||||
prompt = """
|
|
||||||
|镜号|运镜|画面内容|
|
|
||||||
| :---: | :---: | --- |
|
|
||||||
|1|固定镜头|穿着黄色安全帽、反光背心的工人背着小女孩,左手抱着小熊玩偶,两人脸上洋溢着幸福的笑容,背景是老旧居民楼和湿漉漉的路面。|
|
|
||||||
|2|推镜头|镜头从两人全身慢慢推近至两人上半身,聚焦两人亲密温馨的神态。|
|
|
||||||
|3|平移镜头|镜头从两人正面平移至侧面,展现工人的朴实与小女孩的可爱。|
|
|
||||||
|4|拉镜头|镜头逐渐拉远,展现两人在居民楼间道路上的整体场景。|
|
|
||||||
|5|固定镜头|定格在两人互动的画面,突出亲情的温暖氛围。|
|
|
||||||
"""
|
|
||||||
|
|
||||||
mp4_file_name = "image2video.mp4"
|
|
||||||
submit_result = JmImg2Video.submit_image_to_video_task(image_urls, prompt)
|
|
||||||
logger.info(f"提交结果: {submit_result}")
|
|
||||||
code = submit_result.get("code")
|
|
||||||
if not JmErrorCode.is_success(code):
|
|
||||||
logger.error(f"提交任务失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 获取任务ID
|
|
||||||
task_id = submit_result.get("data", {}).get("task_id")
|
|
||||||
logger.info(f"任务ID: {task_id}")
|
|
||||||
|
|
||||||
# 检查任务是不是已经结束
|
|
||||||
query_retry_count = 0
|
|
||||||
max_query_retries = 10000 # 最大查询次数
|
|
||||||
query_retry_interval = 3000 # 查询间隔(毫秒)
|
|
||||||
|
|
||||||
while query_retry_count < max_query_retries:
|
|
||||||
result = JmCommon.query_task_result(task_id)
|
|
||||||
logger.info(f"查询结果: {result}")
|
|
||||||
|
|
||||||
code = result.get("code")
|
|
||||||
if not JmErrorCode.is_success(code):
|
|
||||||
logger.error(f"查询失败: 错误码={code}, 错误信息={JmErrorCode.get_message_by_code(code)}")
|
|
||||||
break
|
|
||||||
|
|
||||||
data = result.get("data")
|
|
||||||
if data and data.get("video_url"):
|
|
||||||
video_url = data.get("video_url")
|
|
||||||
logger.info(f"视频地址: {video_url}")
|
|
||||||
|
|
||||||
# 下载视频
|
|
||||||
save_video_path = fr"D:\dsWork\dsProject\dsLightRag\JiMeng\{mp4_file_name}"
|
|
||||||
logger.info("开始下载视频...")
|
|
||||||
JmCommon.download_file(video_url, save_video_path)
|
|
||||||
logger.info(f"视频已下载到: {save_video_path}")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.info(f"任务处理中,等待{query_retry_interval}毫秒后重试...")
|
|
||||||
time.sleep(query_retry_interval / 1000)
|
|
||||||
query_retry_count += 1
|
|
||||||
|
|
||||||
if query_retry_count >= max_query_retries:
|
|
||||||
logger.error(f"任务查询超时,已达到最大查询次数: {max_query_retries}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"程序执行异常: {str(e)}", exc_info=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@@ -3,11 +3,10 @@ import logging
|
|||||||
import fastapi
|
import fastapi
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from openai import AsyncOpenAI
|
|
||||||
|
|
||||||
from Config import Config
|
|
||||||
from JiMeng.Kit.JmTxt2ImgUtil import JmTxt2Img
|
|
||||||
from JiMeng.Kit.FenJingTouGenerator import FenJingTouGenerator # 导入分镜头生成器
|
from JiMeng.Kit.FenJingTouGenerator import FenJingTouGenerator # 导入分镜头生成器
|
||||||
|
from JiMeng.Kit.JmImg2VideoUtil import JmImg2Video # 导入视频生成工具
|
||||||
|
from JiMeng.Kit.JmTxt2ImgUtil import JmTxt2Img
|
||||||
|
|
||||||
# 创建路由路由器
|
# 创建路由路由器
|
||||||
router = APIRouter(prefix="/api/jimeng", tags=["即梦"])
|
router = APIRouter(prefix="/api/jimeng", tags=["即梦"])
|
||||||
@@ -15,12 +14,6 @@ router = APIRouter(prefix="/api/jimeng", tags=["即梦"])
|
|||||||
# 配置日志
|
# 配置日志
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# 初始化异步 OpenAI 客户端
|
|
||||||
client = AsyncOpenAI(
|
|
||||||
api_key=Config.ALY_LLM_API_KEY,
|
|
||||||
base_url=Config.ALY_LLM_BASE_URL
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/prompt_input")
|
@router.post("/prompt_input")
|
||||||
async def prompt_input(request: fastapi.Request):
|
async def prompt_input(request: fastapi.Request):
|
||||||
@@ -87,3 +80,68 @@ async def generate_fenjingtou(request: fastapi.Request):
|
|||||||
logger.error(f"分镜头脚本生成失败: {str(e)}")
|
logger.error(f"分镜头脚本生成失败: {str(e)}")
|
||||||
raise HTTPException(status_code=500, detail=f"分镜头脚本生成失败: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"分镜头脚本生成失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/create_video_task")
|
||||||
|
async def create_video_task(request: fastapi.Request):
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
image_url = data.get("image_url")
|
||||||
|
prompt = data.get("prompt")
|
||||||
|
|
||||||
|
if not image_url:
|
||||||
|
raise HTTPException(status_code=400, detail="缺少图片地址参数")
|
||||||
|
if not prompt:
|
||||||
|
raise HTTPException(status_code=400, detail="缺少分镜头脚本参数")
|
||||||
|
|
||||||
|
logger.info(f"收到视频任务创建请求,图片地址: {image_url},分镜头脚本: {prompt}")
|
||||||
|
|
||||||
|
# 创建视频生成实例并调用方法
|
||||||
|
img2video = JmImg2Video()
|
||||||
|
task_id = img2video.create_video_task([image_url], prompt)
|
||||||
|
|
||||||
|
logger.info(f"视频任务创建成功,任务ID: {task_id}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": {
|
||||||
|
"task_id": task_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except HTTPException as e:
|
||||||
|
logger.error(f"请求参数错误: {str(e.detail)}")
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"视频任务创建失败: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"视频任务创建失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/query_video_task")
|
||||||
|
async def query_video_task(request: fastapi.Request):
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
task_id = data.get("task_id")
|
||||||
|
|
||||||
|
if not task_id:
|
||||||
|
raise HTTPException(status_code=400, detail="缺少任务ID参数")
|
||||||
|
|
||||||
|
logger.info(f"收到视频任务查询请求,任务ID: {task_id}")
|
||||||
|
|
||||||
|
# 创建视频生成实例并调用方法
|
||||||
|
img2video = JmImg2Video()
|
||||||
|
status_info = img2video.query_task_status(task_id)
|
||||||
|
|
||||||
|
logger.info(f"视频任务查询成功,状态: {status_info['status']}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"code": 200,
|
||||||
|
"message": "成功",
|
||||||
|
"data": status_info
|
||||||
|
}
|
||||||
|
except HTTPException as e:
|
||||||
|
logger.error(f"请求参数错误: {str(e.detail)}")
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"视频任务查询失败: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"视频任务查询失败: {str(e)}")
|
||||||
|
|
||||||
|
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>首帧预览</title>
|
<title>二、分镜头脚本生成</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -164,7 +164,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>首帧预览与分镜头生成</h1>
|
<h1>二、分镜头脚本生成</h1>
|
||||||
|
|
||||||
<!-- 图片预览区域 -->
|
<!-- 图片预览区域 -->
|
||||||
<div class="section image-section">
|
<div class="section image-section">
|
||||||
@@ -334,7 +334,25 @@
|
|||||||
alert('生成分镜头时出错: ' + error.message);
|
alert('生成分镜头时出错: ' + error.message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 制作视频按钮
|
||||||
|
const createVideoBtn = document.getElementById('createVideoBtn');
|
||||||
|
createVideoBtn.addEventListener('click', function() {
|
||||||
|
// 显示遮罩动画
|
||||||
|
mask.classList.add('active');
|
||||||
|
maskText.textContent = '正在制作视频,请稍候...';
|
||||||
|
|
||||||
|
// 模拟视频制作过程(2秒后完成)
|
||||||
|
setTimeout(function() {
|
||||||
|
// 隐藏遮罩动画
|
||||||
|
mask.classList.remove('active');
|
||||||
|
alert('视频制作成功!');
|
||||||
|
// 这里可以添加视频播放或下载的逻辑
|
||||||
|
// window.location.href = 'video_player.html';
|
||||||
|
}, 2000);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>智能视频生成 - 首帧创作</title>
|
<title>一、首帧制作</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -329,7 +329,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>智能视频生成 - 首帧创作</h1>
|
<h1>一、首帧制作</h1>
|
||||||
|
|
||||||
<form id="promptForm">
|
<form id="promptForm">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -659,13 +659,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// ... existing code ...
|
|
||||||
const mask = document.getElementById('mask');
|
const mask = document.getElementById('mask');
|
||||||
const maskText = document.getElementById('maskText');
|
const maskText = document.getElementById('maskText');
|
||||||
|
|
||||||
// ... existing code ...
|
|
||||||
|
|
||||||
// 新增函数:调用 API 生成图片
|
|
||||||
function generateImage(prompt) {
|
function generateImage(prompt) {
|
||||||
// 显示遮罩动画
|
// 显示遮罩动画
|
||||||
mask.classList.add('active');
|
mask.classList.add('active');
|
||||||
|
Reference in New Issue
Block a user