加后台登录、图片预览发送、自动清理上传文件

This commit is contained in:
songxinnuo
2026-10-28 14:05:46 +08:00
parent d8d4ee0a31
commit c4ac4f513c
3 changed files with 61 additions and 11 deletions
+30
View File
@@ -269,6 +269,21 @@ def backup_db():
print(f"[数据库备份失败] {e}")
def cleanup_uploads():
"""删除 uploads/ 里 7 天前的图片"""
try:
udir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "uploads")
if not os.path.isdir(udir):
return
cutoff = time.time() - 7 * 86400
for f in os.listdir(udir):
fp = os.path.join(udir, f)
if os.path.isfile(fp) and os.path.getmtime(fp) < cutoff:
os.remove(fp)
except Exception as e:
print(f"[清理上传文件失败] {e}")
def cleanup_old_conversations():
"""自动清理 30 天前的旧对话,防止数据库无限膨胀"""
try:
@@ -290,6 +305,7 @@ def cleanup_old_conversations():
backup_db()
cleanup_uploads()
# 自动清理已关闭,不删旧对话
@@ -919,6 +935,9 @@ def stream_chat():
conv_id = request.args.get("conv_id", type=int)
if not msg:
return Response(sse({"e": "问题不能为空"}), mimetype="text/event-stream")
# 纯图片消息:不调AI
if "[图片]" in msg and len(msg) < 300:
return Response(sse({"t": "收到您的图片,请描述一下您的问题。"}), mimetype="text/event-stream")
# 运单号:直接给轨迹卡片,不让模型编
track_no = extract_tracking_no(msg)
@@ -1437,8 +1456,19 @@ def uploaded_file(name):
def index():
return render_template("index.html")
@app.route("/admin/login", methods=["POST"])
def admin_login():
pwd = request.json.get("password", "")
if pwd == "123":
return jsonify({"ok": True})
return jsonify({"ok": False}), 401
@app.route("/admin")
def admin():
return render_template("admin_login.html")
@app.route("/admin/dashboard")
def admin_dash():
return render_template("admin.html")
+6 -1
View File
@@ -149,7 +149,12 @@ function switchSession(sid){
function appendMsg(m){
var div=document.createElement("div");
div.className="msg "+(m.side==="customer"?"cust":"agent");
div.innerHTML='<div><div class="bubble">'+m.content.replace(/</g,"&lt;")+'</div><div class="meta">'+(m.side==="customer"?"客户":"客服")+'</div></div>';
var c = m.content.replace(/</g,"&lt;");
var imgMatch = c.match(/\[图片\](\/uploads\/\S+)/);
if(imgMatch){
c = c.replace(imgMatch[0], '<img src="'+imgMatch[1]+'" style="max-width:200px;border-radius:6px;display:block;">');
}
div.innerHTML='<div><div class="bubble">'+c+'</div><div class="meta">'+(m.side==="customer"?"客户":"客服")+'</div></div>';
document.getElementById("chatBox").appendChild(div);
document.getElementById("chatBox").scrollTop=999999;
}
+25 -10
View File
@@ -1321,9 +1321,15 @@ uploadInput.onchange = function(){
.then(data=>{
uploadBtn.textContent = "📎";
if(data.ok){
let text = "[附件] " + data.url;
if(data.is_image) text = "[图片] " + data.url;
inputDom.value = text;
window.__pendingImg = data.url;
var old = document.getElementById("imgPreview");
if(old) old.remove();
var prev = document.createElement("div");
prev.id = "imgPreview";
prev.style.cssText="margin:0 0 8px 50px;";
prev.innerHTML = '<img src="'+data.url+'" style="max-height:60px;border-radius:6px;border:1px solid #ddd;">';
var area = document.querySelector(".input-area");
area.insertBefore(prev, area.firstChild);
inputDom.focus();
}else{
alert(data.error || "上传失败");
@@ -1961,7 +1967,8 @@ function addMsg(content, isUser){
const userMsg = document.createElement('div');
userMsg.className = "user-message";
if(imgUrl){
userMsg.innerHTML = '<img src="' + imgUrl + '" style="max-width:220px;border-radius:10px;display:block;padding:0;background:transparent;cursor:zoom-in;" onclick="window.open(this.src)">';
var txt = content.replace(/\[图片\]\/uploads\/\S+\.(?:jpg|jpeg|png|gif|webp)/i, "").trim();
userMsg.innerHTML = '<img src="' + imgUrl + '" style="max-width:220px;border-radius:10px;display:block;background:transparent;cursor:zoom-in;" onclick="window.open(this.src)">' + (txt ? '<div style="margin-top:6px;">'+txt+'</div>' : '');
}else{
userMsg.innerText = content;
}
@@ -2121,17 +2128,21 @@ function renderFollowups(bubble){
async function doSend(retryText){
const isRetry = typeof retryText === "string";
const sendWord = (isRetry ? retryText : inputDom.value).trim();
if(!sendWord){
var imgUrl = window.__pendingImg || "";
if(!sendWord && !imgUrl){
inputDom.focus();
return;
}
// 人工客服模式:直接发给客服,不走AI
if(window.humanSid){
if(!isRetry) addMsg(sendWord, true);
var content = sendWord;
if(imgUrl) content += (content ? " " : "") + "[图片]" + imgUrl;
if(!isRetry) addMsg(content, true);
inputDom.value = "";
window.__pendingImg = "";
var p = document.getElementById("imgPreview"); if(p) p.remove();
inputDom.focus();
fetch("/api/human/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:window.humanSid,side:"customer",content:sendWord})});
fetch("/api/human/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sid:window.humanSid,side:"customer",content:content})});
return;
}
@@ -2139,8 +2150,12 @@ async function doSend(retryText){
if(cancelActive){ cancelActive(); cancelActive = null; }
if(!isRetry){
addMsg(sendWord, true);
var fullContent = sendWord;
if(imgUrl) fullContent += (fullContent ? " " : "") + "[图片]" + imgUrl;
addMsg(fullContent, true);
inputDom.value = "";
window.__pendingImg = "";
var p = document.getElementById("imgPreview"); if(p) p.remove();
inputDom.focus();
}
@@ -2294,7 +2309,7 @@ async function doSend(retryText){
fetch("/stream_chat", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({msg: sendWord, conv_id: currentConvId}),
body: JSON.stringify({msg: fullContent, conv_id: currentConvId}),
signal: controller.signal
}).then(function(res){
if(!res.ok){ throw new Error("HTTP " + res.status); }