Compare commits
2
Commits
d8d4ee0a31
...
fc9897ccb2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc9897ccb2 | ||
|
|
c4ac4f513c |
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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,"<")+'</div><div class="meta">'+(m.side==="customer"?"客户":"客服")+'</div></div>';
|
||||
var c = m.content.replace(/</g,"<");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>登录 - 客服后台</title>
|
||||
<style>
|
||||
body{font-family:sans-serif;background:#f0f2f5;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;}
|
||||
.box{background:#fff;padding:40px;border-radius:12px;box-shadow:0 2px 12px rgba(0,0,0,0.1);width:320px;}
|
||||
h2{text-align:center;color:#18479c;margin-bottom:24px;}
|
||||
input{width:100%;padding:10px;border:1px solid #ddd;border-radius:6px;margin-bottom:12px;font-size:14px;box-sizing:border-box;}
|
||||
button{width:100%;padding:10px;background:#2978e8;color:#fff;border:none;border-radius:6px;font-size:14px;cursor:pointer;}
|
||||
.err{color:#dc2626;font-size:13px;text-align:center;display:none;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h2>🔒 后台登录</h2>
|
||||
<input type="password" id="pwd" placeholder="请输入密码">
|
||||
<p class="err" id="err">密码错误</p>
|
||||
<button onclick="login()">登录</button>
|
||||
</div>
|
||||
<script>
|
||||
function login(){
|
||||
var p=document.getElementById("pwd").value;
|
||||
fetch("/admin/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password:p})})
|
||||
.then(function(r){if(r.ok){location.href="/admin/dashboard"}else{document.getElementById("err").style.display="block"}});
|
||||
}
|
||||
document.getElementById("pwd").addEventListener("keydown",function(e){if(e.key==="Enter")login()});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+25
-10
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user