chenge commited on
Commit
8c18698
·
1 Parent(s): 83bbd08

增加图片功能

Browse files
Files changed (1) hide show
  1. app.py +317 -27
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
- else:
869
  # 如果直接是文件路径
870
  file_path = file_info
871
  logger.info(f"Direct file path: {file_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872
 
873
  if file_path:
874
- # 保存文件路径
875
- image_file_paths.append(file_path)
876
- logger.info(f"Added to image_file_paths: {file_path}")
877
-
878
- # 使用PIL加载图片
879
- image = Image.open(file_path)
880
- logger.info(f"Loaded image with size: {image.size}")
881
-
882
- # 可选:调整图片大小以节省带宽
883
- if max(image.size) > 1024:
884
- ratio = 1024 / max(image.size)
885
- new_size = tuple(int(dim * ratio) for dim in image.size)
886
- image = image.resize(new_size, Image.Resampling.LANCZOS)
887
- logger.info(f"Resized image to: {new_size}")
888
-
889
- uploaded_images.append(image)
 
 
 
 
 
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-messages .chatbot-chat-message:last-child .chatbot-chat-message-footer {
966
  visibility: visible;
967
  opacity: 1;
968
  }
969
- #chatbot .chatbot-chat .chatbot-chat-messages .chatbot-chat-message:hover .chatbot-chat-message-footer {
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
- "输入"), ) as sender:
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(