Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
chenge
commited on
Commit
·
8c18698
1
Parent(s):
83bbd08
增加图片功能
Browse files
app.py
CHANGED
@@ -837,8 +837,8 @@ class Gradio_Events:
|
|
837 |
|
838 |
@staticmethod
|
839 |
def handle_image_upload(files, state_value):
|
840 |
-
"""处理图片上传"""
|
841 |
-
logger.info(f"handle_image_upload called with files: {files}")
|
842 |
|
843 |
if not files:
|
844 |
# 没有文件时重置为默认状态
|
@@ -851,48 +851,79 @@ class Gradio_Events:
|
|
851 |
|
852 |
# 显示上传中状态
|
853 |
logger.info("Upload in progress...")
|
854 |
-
# 这里可以添加一个临时的loading状态,但由于返回限制,我们直接进行处理
|
855 |
|
856 |
try:
|
857 |
# 处理上传的文件
|
858 |
uploaded_images = []
|
859 |
image_file_paths = []
|
860 |
|
|
|
|
|
|
|
|
|
861 |
for i, file_info in enumerate(files):
|
862 |
logger.info(f"Processing file {i}: {file_info}, type: {type(file_info)}")
|
863 |
|
|
|
|
|
864 |
if isinstance(file_info, dict):
|
865 |
-
#
|
866 |
file_path = file_info.get('name') or file_info.get('path')
|
867 |
logger.info(f"Extracted path from dict: {file_path}")
|
868 |
-
|
869 |
# 如果直接是文件路径
|
870 |
file_path = file_info
|
871 |
logger.info(f"Direct file path: {file_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
872 |
|
873 |
if file_path:
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
|
878 |
-
|
879 |
-
|
880 |
-
|
881 |
-
|
882 |
-
|
883 |
-
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
|
|
|
|
|
|
|
|
|
|
890 |
|
891 |
# 替换而不是追加图片(修复累积bug)
|
892 |
state_value["uploaded_images"] = uploaded_images
|
893 |
state_value["image_file_paths"] = image_file_paths
|
894 |
|
895 |
-
logger.info(f"Successfully uploaded {len(uploaded_images)} images")
|
896 |
|
897 |
# 显示状态指示器,显示图片数量
|
898 |
return (
|
@@ -962,11 +993,11 @@ css = """
|
|
962 |
opacity: 0;
|
963 |
transition: opacity 0.2s;
|
964 |
}
|
965 |
-
#chatbot .chatbot-chat .chatbot-chat-
|
966 |
visibility: visible;
|
967 |
opacity: 1;
|
968 |
}
|
969 |
-
#chatbot .chatbot-chat .chatbot-chat-
|
970 |
visibility: visible;
|
971 |
opacity: 1;
|
972 |
}
|
@@ -1081,6 +1112,34 @@ css = """
|
|
1081 |
background: linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%);
|
1082 |
}
|
1083 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1084 |
/* 响应式图片展示 */
|
1085 |
@media (max-width: 768px) {
|
1086 |
.image-gallery img {
|
@@ -1103,6 +1162,237 @@ css = """
|
|
1103 |
.image-gallery img {
|
1104 |
animation: imageLoad 0.3s ease;
|
1105 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1106 |
"""
|
1107 |
|
1108 |
|
@@ -1120,7 +1410,7 @@ def logo():
|
|
1120 |
ms.Span("dots.vlm1.inst")
|
1121 |
|
1122 |
|
1123 |
-
with gr.Blocks(css=css, fill_width=True) as demo:
|
1124 |
state = gr.State({
|
1125 |
"conversations_history": {},
|
1126 |
"conversations": [],
|
@@ -1524,8 +1814,8 @@ with gr.Blocks(css=css, fill_width=True) as demo:
|
|
1524 |
}""") as suggestion:
|
1525 |
with ms.Slot("children"):
|
1526 |
with antdx.Sender(placeholder=get_text(
|
1527 |
-
"Enter Prompt",
|
1528 |
-
"
|
1529 |
with ms.Slot("actions"):
|
1530 |
# 停止生成按钮
|
1531 |
with antd.Button(
|
|
|
837 |
|
838 |
@staticmethod
|
839 |
def handle_image_upload(files, state_value):
|
840 |
+
"""处理图片上传 - 支持拖拽和粘贴功能"""
|
841 |
+
logger.info(f"handle_image_upload called with files: {files}, type: {type(files)}")
|
842 |
|
843 |
if not files:
|
844 |
# 没有文件时重置为默认状态
|
|
|
851 |
|
852 |
# 显示上传中状态
|
853 |
logger.info("Upload in progress...")
|
|
|
854 |
|
855 |
try:
|
856 |
# 处理上传的文件
|
857 |
uploaded_images = []
|
858 |
image_file_paths = []
|
859 |
|
860 |
+
# 确保files是列表格式
|
861 |
+
if not isinstance(files, list):
|
862 |
+
files = [files] if files else []
|
863 |
+
|
864 |
for i, file_info in enumerate(files):
|
865 |
logger.info(f"Processing file {i}: {file_info}, type: {type(file_info)}")
|
866 |
|
867 |
+
file_path = None
|
868 |
+
|
869 |
if isinstance(file_info, dict):
|
870 |
+
# 如果是文件信息字典(Gradio上传格式)
|
871 |
file_path = file_info.get('name') or file_info.get('path')
|
872 |
logger.info(f"Extracted path from dict: {file_path}")
|
873 |
+
elif isinstance(file_info, str):
|
874 |
# 如果直接是文件路径
|
875 |
file_path = file_info
|
876 |
logger.info(f"Direct file path: {file_path}")
|
877 |
+
elif hasattr(file_info, 'name') and hasattr(file_info, 'read'):
|
878 |
+
# 如果是文件对象(拖拽/粘贴可能产生)
|
879 |
+
logger.info(f"File object detected: {file_info.name if hasattr(file_info, 'name') else 'unnamed'}")
|
880 |
+
# 对于文件对象,我们需要特殊处理
|
881 |
+
try:
|
882 |
+
if hasattr(file_info, 'name'):
|
883 |
+
file_path = file_info.name
|
884 |
+
else:
|
885 |
+
# 创建临时文件名
|
886 |
+
import tempfile
|
887 |
+
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
|
888 |
+
if hasattr(file_info, 'read'):
|
889 |
+
tmp_file.write(file_info.read())
|
890 |
+
file_path = tmp_file.name
|
891 |
+
logger.info(f"Created temporary file: {file_path}")
|
892 |
+
except Exception as file_error:
|
893 |
+
logger.error(f"Error processing file object: {str(file_error)}")
|
894 |
+
continue
|
895 |
+
else:
|
896 |
+
logger.warning(f"Unknown file format: {type(file_info)}")
|
897 |
+
continue
|
898 |
|
899 |
if file_path:
|
900 |
+
try:
|
901 |
+
# 保存文件路径
|
902 |
+
image_file_paths.append(file_path)
|
903 |
+
logger.info(f"Added to image_file_paths: {file_path}")
|
904 |
+
|
905 |
+
# 使用PIL加载图片
|
906 |
+
image = Image.open(file_path)
|
907 |
+
logger.info(f"Loaded image with size: {image.size}")
|
908 |
+
|
909 |
+
# 可选:调整图片大小以节省带宽
|
910 |
+
if max(image.size) > 1024:
|
911 |
+
ratio = 1024 / max(image.size)
|
912 |
+
new_size = tuple(int(dim * ratio) for dim in image.size)
|
913 |
+
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
914 |
+
logger.info(f"Resized image to: {new_size}")
|
915 |
+
|
916 |
+
uploaded_images.append(image)
|
917 |
+
|
918 |
+
except Exception as img_error:
|
919 |
+
logger.error(f"Error processing image {file_path}: {str(img_error)}")
|
920 |
+
continue
|
921 |
|
922 |
# 替换而不是追加图片(修复累积bug)
|
923 |
state_value["uploaded_images"] = uploaded_images
|
924 |
state_value["image_file_paths"] = image_file_paths
|
925 |
|
926 |
+
logger.info(f"Successfully uploaded {len(uploaded_images)} images via drag/paste/upload")
|
927 |
|
928 |
# 显示状态指示器,显示图片数量
|
929 |
return (
|
|
|
993 |
opacity: 0;
|
994 |
transition: opacity 0.2s;
|
995 |
}
|
996 |
+
#chatbot .chatbot-chat .chatbot-chat-message:last-child .chatbot-chat-message-footer {
|
997 |
visibility: visible;
|
998 |
opacity: 1;
|
999 |
}
|
1000 |
+
#chatbot .chatbot-chat .chatbot-chat-message:hover .chatbot-chat-message-footer {
|
1001 |
visibility: visible;
|
1002 |
opacity: 1;
|
1003 |
}
|
|
|
1112 |
background: linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%);
|
1113 |
}
|
1114 |
|
1115 |
+
/* 拖拽区域样式 */
|
1116 |
+
.drop-zone {
|
1117 |
+
position: relative;
|
1118 |
+
transition: all 0.3s ease;
|
1119 |
+
}
|
1120 |
+
|
1121 |
+
.drop-zone.drag-over {
|
1122 |
+
background: linear-gradient(135deg, #e6f7ff 0%, #d6f7ff 100%);
|
1123 |
+
border: 2px dashed #1890ff;
|
1124 |
+
border-radius: 8px;
|
1125 |
+
}
|
1126 |
+
|
1127 |
+
.drop-zone.drag-over::before {
|
1128 |
+
content: "释放以上传图片";
|
1129 |
+
position: absolute;
|
1130 |
+
top: 50%;
|
1131 |
+
left: 50%;
|
1132 |
+
transform: translate(-50%, -50%);
|
1133 |
+
background: rgba(24, 144, 255, 0.9);
|
1134 |
+
color: white;
|
1135 |
+
padding: 12px 24px;
|
1136 |
+
border-radius: 6px;
|
1137 |
+
font-size: 16px;
|
1138 |
+
font-weight: 500;
|
1139 |
+
z-index: 1000;
|
1140 |
+
pointer-events: none;
|
1141 |
+
}
|
1142 |
+
|
1143 |
/* 响应式图片展示 */
|
1144 |
@media (max-width: 768px) {
|
1145 |
.image-gallery img {
|
|
|
1162 |
.image-gallery img {
|
1163 |
animation: imageLoad 0.3s ease;
|
1164 |
}
|
1165 |
+
|
1166 |
+
/* 粘贴提示样式 */
|
1167 |
+
.paste-hint {
|
1168 |
+
position: fixed;
|
1169 |
+
top: 20px;
|
1170 |
+
right: 20px;
|
1171 |
+
background: rgba(24, 144, 255, 0.9);
|
1172 |
+
color: white;
|
1173 |
+
padding: 8px 16px;
|
1174 |
+
border-radius: 6px;
|
1175 |
+
font-size: 14px;
|
1176 |
+
z-index: 1001;
|
1177 |
+
opacity: 0;
|
1178 |
+
transform: translateY(-10px);
|
1179 |
+
transition: all 0.3s ease;
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
.paste-hint.show {
|
1183 |
+
opacity: 1;
|
1184 |
+
transform: translateY(0);
|
1185 |
+
}
|
1186 |
+
"""
|
1187 |
+
|
1188 |
+
# 添加JavaScript代码来处理拖拽和粘贴
|
1189 |
+
drag_and_paste_js = """
|
1190 |
+
<script>
|
1191 |
+
(function() {
|
1192 |
+
let isInitialized = false;
|
1193 |
+
|
1194 |
+
function initializeDragAndPaste() {
|
1195 |
+
if (isInitialized) return;
|
1196 |
+
isInitialized = true;
|
1197 |
+
|
1198 |
+
console.log('Initializing drag and paste functionality...');
|
1199 |
+
|
1200 |
+
// 创建粘贴提示元素
|
1201 |
+
const pasteHint = document.createElement('div');
|
1202 |
+
pasteHint.className = 'paste-hint';
|
1203 |
+
pasteHint.textContent = '检测到剪贴板中的图片,按 Ctrl+V 粘贴';
|
1204 |
+
document.body.appendChild(pasteHint);
|
1205 |
+
|
1206 |
+
// 获取聊天容器作为拖拽区域
|
1207 |
+
const chatContainer = document.querySelector('#chatbot .chatbot-chat') || document.body;
|
1208 |
+
|
1209 |
+
// 防止默认的拖拽行为
|
1210 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
1211 |
+
chatContainer.addEventListener(eventName, preventDefaults, false);
|
1212 |
+
document.body.addEventListener(eventName, preventDefaults, false);
|
1213 |
+
});
|
1214 |
+
|
1215 |
+
function preventDefaults(e) {
|
1216 |
+
e.preventDefault();
|
1217 |
+
e.stopPropagation();
|
1218 |
+
}
|
1219 |
+
|
1220 |
+
// 拖拽进入
|
1221 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
1222 |
+
chatContainer.addEventListener(eventName, highlight, false);
|
1223 |
+
});
|
1224 |
+
|
1225 |
+
// 拖拽离开
|
1226 |
+
['dragleave', 'drop'].forEach(eventName => {
|
1227 |
+
chatContainer.addEventListener(eventName, unhighlight, false);
|
1228 |
+
});
|
1229 |
+
|
1230 |
+
function highlight(e) {
|
1231 |
+
if (e.dataTransfer.types.includes('Files')) {
|
1232 |
+
chatContainer.classList.add('drop-zone', 'drag-over');
|
1233 |
+
}
|
1234 |
+
}
|
1235 |
+
|
1236 |
+
function unhighlight(e) {
|
1237 |
+
chatContainer.classList.remove('drop-zone', 'drag-over');
|
1238 |
+
}
|
1239 |
+
|
1240 |
+
// 处理文件放置
|
1241 |
+
chatContainer.addEventListener('drop', handleDrop, false);
|
1242 |
+
|
1243 |
+
function handleDrop(e) {
|
1244 |
+
const dt = e.dataTransfer;
|
1245 |
+
const files = dt.files;
|
1246 |
+
|
1247 |
+
if (files.length > 0) {
|
1248 |
+
handleFileUpload(Array.from(files));
|
1249 |
+
}
|
1250 |
+
}
|
1251 |
+
|
1252 |
+
// 处理粘贴事件
|
1253 |
+
document.addEventListener('paste', handlePaste, false);
|
1254 |
+
|
1255 |
+
function handlePaste(e) {
|
1256 |
+
const items = e.clipboardData.items;
|
1257 |
+
const imageFiles = [];
|
1258 |
+
|
1259 |
+
for (let i = 0; i < items.length; i++) {
|
1260 |
+
if (items[i].type.indexOf('image') === 0) {
|
1261 |
+
const file = items[i].getAsFile();
|
1262 |
+
if (file) {
|
1263 |
+
imageFiles.push(file);
|
1264 |
+
}
|
1265 |
+
}
|
1266 |
+
}
|
1267 |
+
|
1268 |
+
if (imageFiles.length > 0) {
|
1269 |
+
e.preventDefault();
|
1270 |
+
handleFileUpload(imageFiles);
|
1271 |
+
showPasteSuccess();
|
1272 |
+
}
|
1273 |
+
}
|
1274 |
+
|
1275 |
+
// 显示粘贴成功提示
|
1276 |
+
function showPasteSuccess() {
|
1277 |
+
pasteHint.textContent = '图片粘贴成功!';
|
1278 |
+
pasteHint.classList.add('show');
|
1279 |
+
setTimeout(() => {
|
1280 |
+
pasteHint.classList.remove('show');
|
1281 |
+
}, 2000);
|
1282 |
+
}
|
1283 |
+
|
1284 |
+
// 处理文件上传
|
1285 |
+
function handleFileUpload(files) {
|
1286 |
+
console.log('Processing files:', files);
|
1287 |
+
|
1288 |
+
// 过滤只保留图片文件
|
1289 |
+
const imageFiles = files.filter(file => file.type.startsWith('image/'));
|
1290 |
+
|
1291 |
+
if (imageFiles.length === 0) {
|
1292 |
+
console.log('No image files found');
|
1293 |
+
return;
|
1294 |
+
}
|
1295 |
+
|
1296 |
+
// 查找上传组件
|
1297 |
+
const uploadInput = document.querySelector('input[type="file"][accept*="image"]');
|
1298 |
+
if (!uploadInput) {
|
1299 |
+
console.error('Upload input not found');
|
1300 |
+
return;
|
1301 |
+
}
|
1302 |
+
|
1303 |
+
try {
|
1304 |
+
// 创建新的文件列表
|
1305 |
+
const dt = new DataTransfer();
|
1306 |
+
imageFiles.forEach(file => {
|
1307 |
+
dt.items.add(file);
|
1308 |
+
});
|
1309 |
+
|
1310 |
+
// 设置文件到上传组件
|
1311 |
+
uploadInput.files = dt.files;
|
1312 |
+
|
1313 |
+
// 触发 change 事件
|
1314 |
+
const changeEvent = new Event('change', { bubbles: true });
|
1315 |
+
uploadInput.dispatchEvent(changeEvent);
|
1316 |
+
|
1317 |
+
console.log(`Successfully uploaded ${imageFiles.length} image(s)`);
|
1318 |
+
|
1319 |
+
// 显示成功提示
|
1320 |
+
showUploadSuccess(imageFiles.length);
|
1321 |
+
|
1322 |
+
} catch (error) {
|
1323 |
+
console.error('Error uploading files:', error);
|
1324 |
+
}
|
1325 |
+
}
|
1326 |
+
|
1327 |
+
// 显示上传成功提示
|
1328 |
+
function showUploadSuccess(count) {
|
1329 |
+
pasteHint.textContent = `成功上传 ${count} 张图片!`;
|
1330 |
+
pasteHint.classList.add('show');
|
1331 |
+
setTimeout(() => {
|
1332 |
+
pasteHint.classList.remove('show');
|
1333 |
+
}, 2000);
|
1334 |
+
}
|
1335 |
+
|
1336 |
+
// 监听剪贴板变化(可选功能)
|
1337 |
+
document.addEventListener('keydown', function(e) {
|
1338 |
+
if (e.ctrlKey && e.key === 'v') {
|
1339 |
+
// 检查是否聚焦在输入框上
|
1340 |
+
const activeElement = document.activeElement;
|
1341 |
+
const isInInputArea = activeElement && (
|
1342 |
+
activeElement.tagName === 'TEXTAREA' ||
|
1343 |
+
activeElement.tagName === 'INPUT' ||
|
1344 |
+
activeElement.contentEditable === 'true'
|
1345 |
+
);
|
1346 |
+
|
1347 |
+
if (isInInputArea) {
|
1348 |
+
// 短暂显示提示
|
1349 |
+
setTimeout(() => {
|
1350 |
+
if (navigator.clipboard && navigator.clipboard.read) {
|
1351 |
+
navigator.clipboard.read().then(items => {
|
1352 |
+
const hasImage = items.some(item =>
|
1353 |
+
item.types.some(type => type.startsWith('image/'))
|
1354 |
+
);
|
1355 |
+
if (hasImage) {
|
1356 |
+
pasteHint.textContent = '检测到图片,正在处理...';
|
1357 |
+
pasteHint.classList.add('show');
|
1358 |
+
setTimeout(() => {
|
1359 |
+
pasteHint.classList.remove('show');
|
1360 |
+
}, 1500);
|
1361 |
+
}
|
1362 |
+
}).catch(() => {
|
1363 |
+
// 忽略权限错误
|
1364 |
+
});
|
1365 |
+
}
|
1366 |
+
}, 100);
|
1367 |
+
}
|
1368 |
+
}
|
1369 |
+
});
|
1370 |
+
|
1371 |
+
console.log('Drag and paste functionality initialized successfully');
|
1372 |
+
}
|
1373 |
+
|
1374 |
+
// 初始化函数
|
1375 |
+
function init() {
|
1376 |
+
if (document.readyState === 'loading') {
|
1377 |
+
document.addEventListener('DOMContentLoaded', initializeDragAndPaste);
|
1378 |
+
} else {
|
1379 |
+
initializeDragAndPaste();
|
1380 |
+
}
|
1381 |
+
}
|
1382 |
+
|
1383 |
+
// 如果Gradio还没有完全加载,等待一下
|
1384 |
+
if (window.gradio && window.gradio.mount) {
|
1385 |
+
init();
|
1386 |
+
} else {
|
1387 |
+
// 等待Gradio加载
|
1388 |
+
setTimeout(init, 1000);
|
1389 |
+
}
|
1390 |
+
|
1391 |
+
// 也监听window load事件作为备选
|
1392 |
+
window.addEventListener('load', initializeDragAndPaste);
|
1393 |
+
|
1394 |
+
})();
|
1395 |
+
</script>
|
1396 |
"""
|
1397 |
|
1398 |
|
|
|
1410 |
ms.Span("dots.vlm1.inst")
|
1411 |
|
1412 |
|
1413 |
+
with gr.Blocks(css=css, fill_width=True, head=drag_and_paste_js) as demo:
|
1414 |
state = gr.State({
|
1415 |
"conversations_history": {},
|
1416 |
"conversations": [],
|
|
|
1814 |
}""") as suggestion:
|
1815 |
with ms.Slot("children"):
|
1816 |
with antdx.Sender(placeholder=get_text(
|
1817 |
+
"Enter Prompt (Drag & Drop or Ctrl+V to paste images)",
|
1818 |
+
"输入内容(可拖拽图片或 Ctrl+V 粘贴图片)"), ) as sender:
|
1819 |
with ms.Slot("actions"):
|
1820 |
# 停止生成按钮
|
1821 |
with antd.Button(
|