Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -393,38 +393,6 @@ def inject_custom_css():
|
|
| 393 |
|
| 394 |
inject_custom_css()
|
| 395 |
|
| 396 |
-
# 移动端可见性变化检测 - 从后台切回时如果连接断开则自动刷新
|
| 397 |
-
_visibility_js = """
|
| 398 |
-
<script>
|
| 399 |
-
(function() {
|
| 400 |
-
if (window._visibilityHandlerAdded) return;
|
| 401 |
-
window._visibilityHandlerAdded = true;
|
| 402 |
-
|
| 403 |
-
let hiddenTime = 0;
|
| 404 |
-
document.addEventListener('visibilitychange', function() {
|
| 405 |
-
if (document.hidden) {
|
| 406 |
-
hiddenTime = Date.now();
|
| 407 |
-
} else {
|
| 408 |
-
// 从后台切回,检查是否超过10秒
|
| 409 |
-
const elapsed = Date.now() - hiddenTime;
|
| 410 |
-
if (elapsed > 10000) {
|
| 411 |
-
// 检查是否显示 Connecting 状态
|
| 412 |
-
setTimeout(function() {
|
| 413 |
-
const statusEl = document.querySelector('[data-testid="stStatusWidget"]');
|
| 414 |
-
const connectingEl = document.querySelector('.stConnectionStatus');
|
| 415 |
-
if (statusEl || connectingEl) {
|
| 416 |
-
console.log('检测到连接断开,自动刷新...');
|
| 417 |
-
window.location.reload();
|
| 418 |
-
}
|
| 419 |
-
}, 2000);
|
| 420 |
-
}
|
| 421 |
-
}
|
| 422 |
-
});
|
| 423 |
-
})();
|
| 424 |
-
</script>
|
| 425 |
-
"""
|
| 426 |
-
components.html(_visibility_js, height=0)
|
| 427 |
-
|
| 428 |
|
| 429 |
def _render_action_buttons(content, msg_id):
|
| 430 |
"""生成消息底部的复制/分享/播报按钮(纯HTML部分)"""
|
|
@@ -1276,41 +1244,6 @@ def process_upload(uploaded_files, target_prefix, scope):
|
|
| 1276 |
# 6.5 聊天记录持久化(Supabase chat_history 表)
|
| 1277 |
# =========================
|
| 1278 |
import threading
|
| 1279 |
-
from queue import Queue, Empty
|
| 1280 |
-
|
| 1281 |
-
|
| 1282 |
-
def _stream_with_heartbeat(response_iter, heartbeat_interval=1.5):
|
| 1283 |
-
"""
|
| 1284 |
-
包装流式响应迭代器,添加心跳机制防止WebSocket断开。
|
| 1285 |
-
在后台线程读取API响应,主线程定时发送心跳(空字符串)。
|
| 1286 |
-
heartbeat_interval: 心跳间隔(秒),移动端需要更短间隔
|
| 1287 |
-
"""
|
| 1288 |
-
q = Queue()
|
| 1289 |
-
done = object() # 标记结束
|
| 1290 |
-
|
| 1291 |
-
def reader():
|
| 1292 |
-
try:
|
| 1293 |
-
for chunk in response_iter:
|
| 1294 |
-
q.put(chunk)
|
| 1295 |
-
except Exception as e:
|
| 1296 |
-
q.put(e)
|
| 1297 |
-
finally:
|
| 1298 |
-
q.put(done)
|
| 1299 |
-
|
| 1300 |
-
t = threading.Thread(target=reader, daemon=True)
|
| 1301 |
-
t.start()
|
| 1302 |
-
|
| 1303 |
-
while True:
|
| 1304 |
-
try:
|
| 1305 |
-
item = q.get(timeout=heartbeat_interval)
|
| 1306 |
-
if item is done:
|
| 1307 |
-
break
|
| 1308 |
-
if isinstance(item, Exception):
|
| 1309 |
-
raise item
|
| 1310 |
-
yield item
|
| 1311 |
-
except Empty:
|
| 1312 |
-
# 超时未收到数据,发送心跳保持连接
|
| 1313 |
-
yield None
|
| 1314 |
|
| 1315 |
|
| 1316 |
def _async_run(fn, *args):
|
|
@@ -1784,12 +1717,9 @@ def llm_answer(query, context_docs, selected_display_name, web_enabled, kb_mode=
|
|
| 1784 |
|
| 1785 |
full_text = ""
|
| 1786 |
has_content = False
|
| 1787 |
-
#
|
| 1788 |
-
for chunk in
|
| 1789 |
-
if chunk
|
| 1790 |
-
# 心跳信号,yield空字符串保持连接
|
| 1791 |
-
yield ""
|
| 1792 |
-
elif chunk.choices and chunk.choices[0].delta.content:
|
| 1793 |
content = chunk.choices[0].delta.content
|
| 1794 |
full_text += content
|
| 1795 |
has_content = True
|
|
@@ -1852,12 +1782,9 @@ def llm_answer(query, context_docs, selected_display_name, web_enabled, kb_mode=
|
|
| 1852 |
|
| 1853 |
full_text = ""
|
| 1854 |
has_content = False
|
| 1855 |
-
#
|
| 1856 |
-
for chunk in
|
| 1857 |
-
if chunk
|
| 1858 |
-
# 心跳信号,yield空字符串保持连接
|
| 1859 |
-
yield ""
|
| 1860 |
-
elif chunk.choices and chunk.choices[0].delta.content:
|
| 1861 |
content = chunk.choices[0].delta.content
|
| 1862 |
full_text += content
|
| 1863 |
has_content = True
|
|
|
|
| 393 |
|
| 394 |
inject_custom_css()
|
| 395 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
def _render_action_buttons(content, msg_id):
|
| 398 |
"""生成消息底部的复制/分享/播报按钮(纯HTML部分)"""
|
|
|
|
| 1244 |
# 6.5 聊天记录持久化(Supabase chat_history 表)
|
| 1245 |
# =========================
|
| 1246 |
import threading
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1247 |
|
| 1248 |
|
| 1249 |
def _async_run(fn, *args):
|
|
|
|
| 1717 |
|
| 1718 |
full_text = ""
|
| 1719 |
has_content = False
|
| 1720 |
+
# 直接迭代流式响应
|
| 1721 |
+
for chunk in response:
|
| 1722 |
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
|
|
|
|
|
|
|
|
| 1723 |
content = chunk.choices[0].delta.content
|
| 1724 |
full_text += content
|
| 1725 |
has_content = True
|
|
|
|
| 1782 |
|
| 1783 |
full_text = ""
|
| 1784 |
has_content = False
|
| 1785 |
+
# 直接迭代流式响应
|
| 1786 |
+
for chunk in response:
|
| 1787 |
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
|
|
|
|
|
|
|
|
| 1788 |
content = chunk.choices[0].delta.content
|
| 1789 |
full_text += content
|
| 1790 |
has_content = True
|