Skip to content

Commit 2b90302

Browse files
feat: drag file to chatbot to upload 拖动以上传文件 (binary-husky#1396)
* feat: 拖动以上传文件 * 上传文件过程中转圈圈 * fix: 解决仅在第一次上传时才有上传动画的问题 --------- Co-authored-by: 505030475 <qingxu.fu@outlook.com>
1 parent f7588d4 commit 2b90302

File tree

2 files changed

+145
-48
lines changed

2 files changed

+145
-48
lines changed

main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,9 @@ def fn_area_visibility_2(a):
292292
cancel_handles.append(click_handle)
293293
# 文件上传区,接收文件后与chatbot的互动
294294
file_upload.upload(on_file_uploaded, [file_upload, chatbot, txt, txt2, checkboxes, cookies], [chatbot, txt, txt2, cookies])
295+
file_upload.upload(None, None, None, _js=r"()=>{toast_push('上传完毕, 请等待文件清单展现后继续操作 ...'); cancel_loading_status();}")
295296
file_upload_2.upload(on_file_uploaded, [file_upload_2, chatbot, txt, txt2, checkboxes, cookies], [chatbot, txt, txt2, cookies])
297+
file_upload_2.upload(None, None, None, _js=r"()=>{toast_push('上传完毕, 请等待文件清单展现后继续操作 ...'); cancel_loading_status();}")
296298
# 函数插件-固定按钮区
297299
for k in plugins:
298300
if not plugins[k].get("AsButton", True): continue

themes/common.js

Lines changed: 143 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function gradioApp() {
33
const elems = document.getElementsByTagName('gradio-app');
44
const elem = elems.length == 0 ? document : elems[0];
55
if (elem !== document) {
6-
elem.getElementById = function(id) {
6+
elem.getElementById = function (id) {
77
return document.getElementById(id);
88
};
99
}
@@ -12,31 +12,31 @@ function gradioApp() {
1212

1313
function setCookie(name, value, days) {
1414
var expires = "";
15-
15+
1616
if (days) {
17-
var date = new Date();
18-
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
19-
expires = "; expires=" + date.toUTCString();
17+
var date = new Date();
18+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
19+
expires = "; expires=" + date.toUTCString();
2020
}
21-
21+
2222
document.cookie = name + "=" + value + expires + "; path=/";
2323
}
2424

2525
function getCookie(name) {
2626
var decodedCookie = decodeURIComponent(document.cookie);
2727
var cookies = decodedCookie.split(';');
28-
28+
2929
for (var i = 0; i < cookies.length; i++) {
30-
var cookie = cookies[i].trim();
31-
32-
if (cookie.indexOf(name + "=") === 0) {
33-
return cookie.substring(name.length + 1, cookie.length);
34-
}
30+
var cookie = cookies[i].trim();
31+
32+
if (cookie.indexOf(name + "=") === 0) {
33+
return cookie.substring(name.length + 1, cookie.length);
34+
}
3535
}
36-
36+
3737
return null;
38-
}
39-
38+
}
39+
4040
function addCopyButton(botElement) {
4141
// https://github.com/GaiZhenbiao/ChuanhuChatGPT/tree/main/web_assets/javascript
4242
// Copy bot button
@@ -49,7 +49,7 @@ function addCopyButton(botElement) {
4949
// messageBtnColumnElement.remove();
5050
return;
5151
}
52-
52+
5353
var copyButton = document.createElement('button');
5454
copyButton.classList.add('copy-bot-btn');
5555
copyButton.setAttribute('aria-label', 'Copy');
@@ -98,40 +98,38 @@ function chatbotContentChanged(attempt = 1, force = false) {
9898
}
9999
}
100100

101-
function chatbotAutoHeight(){
101+
function chatbotAutoHeight() {
102102
// 自动调整高度
103-
function update_height(){
103+
function update_height() {
104104
var { panel_height_target, chatbot_height, chatbot } = get_elements(true);
105-
if (panel_height_target!=chatbot_height)
106-
{
105+
if (panel_height_target != chatbot_height) {
107106
var pixelString = panel_height_target.toString() + 'px';
108-
chatbot.style.maxHeight = pixelString; chatbot.style.height = pixelString;
107+
chatbot.style.maxHeight = pixelString; chatbot.style.height = pixelString;
109108
}
110109
}
111110

112-
function update_height_slow(){
111+
function update_height_slow() {
113112
var { panel_height_target, chatbot_height, chatbot } = get_elements();
114-
if (panel_height_target!=chatbot_height)
115-
{
116-
new_panel_height = (panel_height_target - chatbot_height)*0.5 + chatbot_height;
117-
if (Math.abs(new_panel_height - panel_height_target) < 10){
113+
if (panel_height_target != chatbot_height) {
114+
new_panel_height = (panel_height_target - chatbot_height) * 0.5 + chatbot_height;
115+
if (Math.abs(new_panel_height - panel_height_target) < 10) {
118116
new_panel_height = panel_height_target;
119117
}
120118
// console.log(chatbot_height, panel_height_target, new_panel_height);
121119
var pixelString = new_panel_height.toString() + 'px';
122-
chatbot.style.maxHeight = pixelString; chatbot.style.height = pixelString;
120+
chatbot.style.maxHeight = pixelString; chatbot.style.height = pixelString;
123121
}
124122
}
125123
monitoring_input_box()
126124
update_height();
127-
setInterval(function() {
125+
setInterval(function () {
128126
update_height_slow()
129127
}, 50); // 每100毫秒执行一次
130128
}
131129

132130

133131

134-
function get_elements(consider_state_panel=false) {
132+
function get_elements(consider_state_panel = false) {
135133
var chatbot = document.querySelector('#gpt-chatbot > div.wrap.svelte-18telvq');
136134
if (!chatbot) {
137135
chatbot = document.querySelector('#gpt-chatbot');
@@ -142,13 +140,13 @@ function get_elements(consider_state_panel=false) {
142140
// const panel4 = document.querySelector('#interact-panel').getBoundingClientRect();
143141
const panel5 = document.querySelector('#input-panel2').getBoundingClientRect();
144142
const panel_active = document.querySelector('#state-panel').getBoundingClientRect();
145-
if (consider_state_panel || panel_active.height < 25){
143+
if (consider_state_panel || panel_active.height < 25) {
146144
document.state_panel_height = panel_active.height;
147145
}
148146
// 25 是chatbot的label高度, 16 是右侧的gap
149-
var panel_height_target = panel1.height + panel2.height + panel3.height + 0 + 0 - 25 + 16*2;
147+
var panel_height_target = panel1.height + panel2.height + panel3.height + 0 + 0 - 25 + 16 * 2;
150148
// 禁止动态的state-panel高度影响
151-
panel_height_target = panel_height_target + (document.state_panel_height-panel_active.height)
149+
panel_height_target = panel_height_target + (document.state_panel_height - panel_active.height)
152150
var panel_height_target = parseInt(panel_height_target);
153151
var chatbot_height = chatbot.style.height;
154152
var chatbot_height = parseInt(chatbot_height);
@@ -173,7 +171,7 @@ function add_func_paste(input) {
173171
}
174172
if (paste_files.length > 0) {
175173
// 按照文件列表执行批量上传逻辑
176-
await paste_upload_files(paste_files);
174+
await upload_files(paste_files);
177175
paste_files = []
178176

179177
}
@@ -182,8 +180,42 @@ function add_func_paste(input) {
182180
}
183181
}
184182

183+
function add_func_drag(elem) {
184+
if (elem) {
185+
const dragEvents = ["dragover", "dragenter"];
186+
const leaveEvents = ["dragleave", "dragend", "drop"];
187+
188+
const onDrag = function (e) {
189+
e.preventDefault();
190+
e.stopPropagation();
191+
if (elem_upload_float.querySelector("input[type=file]")) {
192+
toast_push('释放以上传文件', 50)
193+
} else {
194+
toast_push('⚠️请先删除上传区中的历史文件,再尝试上传。', 50)
195+
}
196+
};
197+
198+
const onLeave = function (e) {
199+
e.preventDefault();
200+
e.stopPropagation();
201+
};
202+
203+
dragEvents.forEach(event => {
204+
elem.addEventListener(event, onDrag);
205+
});
206+
207+
leaveEvents.forEach(event => {
208+
elem.addEventListener(event, onLeave);
209+
});
185210

186-
async function paste_upload_files(files) {
211+
elem.addEventListener("drop", async function (e) {
212+
const files = e.dataTransfer.files;
213+
await upload_files(files);
214+
});
215+
}
216+
}
217+
218+
async function upload_files(files) {
187219
const uploadInputElement = elem_upload_float.querySelector("input[type=file]");
188220
let totalSizeMb = 0
189221
if (files && files.length > 0) {
@@ -195,19 +227,20 @@ async function paste_upload_files(files) {
195227
}
196228
// 检查文件总大小是否超过20MB
197229
if (totalSizeMb > 20) {
198-
toast_push('⚠️文件夹大于20MB 🚀上传文件中', 2000)
230+
toast_push('⚠️文件夹大于 20MB 🚀上传文件中', 3000)
199231
// return; // 如果超过了指定大小, 可以不进行后续上传操作
200232
}
201-
// 监听change事件, 原生Gradio可以实现
233+
// 监听change事件, 原生Gradio可以实现
202234
// uploadInputElement.addEventListener('change', function(){replace_input_string()});
203235
let event = new Event("change");
204-
Object.defineProperty(event, "target", {value: uploadInputElement, enumerable: true});
205-
Object.defineProperty(event, "currentTarget", {value: uploadInputElement, enumerable: true});
206-
Object.defineProperty(uploadInputElement, "files", {value: files, enumerable: true});
236+
Object.defineProperty(event, "target", { value: uploadInputElement, enumerable: true });
237+
Object.defineProperty(event, "currentTarget", { value: uploadInputElement, enumerable: true });
238+
Object.defineProperty(uploadInputElement, "files", { value: files, enumerable: true });
207239
uploadInputElement.dispatchEvent(event);
240+
208241
// toast_push('🎉上传文件成功', 2000)
209242
} else {
210-
toast_push('⚠️请先删除上传区中的历史文件,再尝试粘贴。', 2000)
243+
toast_push('⚠️请先删除上传区中的历史文件,再尝试上传。', 3000)
211244
}
212245
}
213246
}
@@ -231,23 +264,85 @@ var elem_upload = null;
231264
var elem_upload_float = null;
232265
var elem_input_main = null;
233266
var elem_input_float = null;
267+
var gptChatbot = null;
234268

235269

270+
function begin_loading_status() {
271+
// Create the loader div and add styling
272+
var loader = document.createElement('div');
273+
loader.id = 'Js_File_Loading';
274+
loader.style.position = "absolute";
275+
loader.style.top = "50%";
276+
loader.style.left = "50%";
277+
loader.style.width = "60px";
278+
loader.style.height = "60px";
279+
loader.style.border = "16px solid #f3f3f3";
280+
loader.style.borderTop = "16px solid #3498db";
281+
loader.style.borderRadius = "50%";
282+
loader.style.animation = "spin 2s linear infinite";
283+
loader.style.transform = "translate(-50%, -50%)";
284+
document.body.appendChild(loader); // Add the loader to the body
285+
// Set the CSS animation keyframes
286+
var styleSheet = document.createElement('style');
287+
// styleSheet.type = 'text/css';
288+
styleSheet.id = 'Js_File_Loading_Style'
289+
styleSheet.innerText = `
290+
@keyframes spin {
291+
0% { transform: rotate(0deg); }
292+
100% { transform: rotate(360deg); }
293+
}`;
294+
document.head.appendChild(styleSheet);
295+
}
296+
function cancel_loading_status() {
297+
var loadingElement = document.getElementById('Js_File_Loading');
298+
if (loadingElement) {
299+
document.body.removeChild(loadingElement); // remove the loader from the body
300+
}
301+
var loadingStyle = document.getElementById('Js_File_Loading_Style');
302+
if (loadingStyle) {
303+
document.head.removeChild(loadingStyle);
304+
}
305+
let clearButton = document.querySelectorAll('div[id*="elem_upload"] button[aria-label="Clear"]');
306+
for (let button of clearButton) {
307+
button.addEventListener('click', function () {
308+
setTimeout(function () {
309+
register_upload_event();
310+
}, 50);
311+
});
312+
}
313+
}
314+
function register_upload_event() {
315+
elem_upload_float = document.getElementById('elem_upload_float')
316+
const upload_component = elem_upload_float.querySelector("input[type=file]");
317+
if (upload_component) {
318+
upload_component.addEventListener('change', function (event) {
319+
toast_push('正在上传中,请稍等。', 2000);
320+
begin_loading_status();
321+
});
322+
}
323+
}
236324
function monitoring_input_box() {
325+
register_upload_event();
326+
237327
elem_upload = document.getElementById('elem_upload')
238328
elem_upload_float = document.getElementById('elem_upload_float')
239329
elem_input_main = document.getElementById('user_input_main')
240330
elem_input_float = document.getElementById('user_input_float')
331+
241332
if (elem_input_main) {
242333
if (elem_input_main.querySelector("textarea")) {
243334
add_func_paste(elem_input_main.querySelector("textarea"))
244335
}
245336
}
246337
if (elem_input_float) {
247-
if (elem_input_float.querySelector("textarea")){
338+
if (elem_input_float.querySelector("textarea")) {
248339
add_func_paste(elem_input_float.querySelector("textarea"))
249340
}
250341
}
342+
gptChatbot = document.getElementById('gpt-chatbot')
343+
if (gptChatbot) {
344+
add_func_drag(gptChatbot)
345+
}
251346
}
252347

253348

@@ -259,28 +354,28 @@ window.addEventListener("DOMContentLoaded", function () {
259354

260355
function audio_fn_init() {
261356
let audio_component = document.getElementById('elem_audio');
262-
if (audio_component){
357+
if (audio_component) {
263358
let buttonElement = audio_component.querySelector('button');
264359
let specificElement = audio_component.querySelector('.hide.sr-only');
265360
specificElement.remove();
266361

267362
buttonElement.childNodes[1].nodeValue = '启动麦克风';
268-
buttonElement.addEventListener('click', function(event) {
363+
buttonElement.addEventListener('click', function (event) {
269364
event.stopPropagation();
270365
toast_push('您启动了麦克风!下一步请点击“实时语音对话”启动语音对话。');
271366
});
272367

273368
// 查找语音插件按钮
274369
let buttons = document.querySelectorAll('button');
275370
let audio_button = null;
276-
for(let button of buttons){
277-
if (button.textContent.includes('语音')){
371+
for (let button of buttons) {
372+
if (button.textContent.includes('语音')) {
278373
audio_button = button;
279374
break;
280375
}
281376
}
282-
if (audio_button){
283-
audio_button.addEventListener('click', function() {
377+
if (audio_button) {
378+
audio_button.addEventListener('click', function () {
284379
toast_push('您点击了“实时语音对话”启动语音对话。');
285380
});
286381
let parent_element = audio_component.parentElement; // 将buttonElement移动到audio_button的内部
@@ -300,5 +395,5 @@ function GptAcademicJavaScriptInit(LAYOUT = "LEFT-RIGHT") {
300395
chatbotContentChanged(1);
301396
});
302397
chatbotObserver.observe(chatbotIndicator, { attributes: true, childList: true, subtree: true });
303-
if (LAYOUT === "LEFT-RIGHT") {chatbotAutoHeight();}
398+
if (LAYOUT === "LEFT-RIGHT") { chatbotAutoHeight(); }
304399
}

0 commit comments

Comments
 (0)