Spaces:
Sleeping
Sleeping
File size: 7,533 Bytes
24d9aaf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | import os
import random
import time
import json
from datetime import datetime, timedelta
from flask import Flask, render_template, jsonify, request
from faker import Faker
app = Flask(__name__)
fake = Faker('zh_CN')
# --- Configuration ---
PORT = 7860
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# --- In-Memory Data Store ---
tickets = []
agents = [
{"id": 1, "name": "王强", "role": "L1 Support", "status": "online", "load": 0, "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=王强"},
{"id": 2, "name": "李娜", "role": "L2 Support", "status": "busy", "load": 4, "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=李娜"},
{"id": 3, "name": "张伟", "role": "Tech Lead", "status": "online", "load": 1, "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=张伟"},
{"id": 4, "name": "刘洋", "role": "Billing Specialist", "status": "offline", "load": 0, "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=刘洋"},
{"id": 5, "name": "陈敏", "role": "L1 Support", "status": "online", "load": 2, "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=陈敏"},
]
# --- Business Logic & Simulation ---
KEYWORDS_URGENT = ["投诉", "退款", "崩溃", "无法登录", "数据丢失", "紧急", "报错", "严重"]
KEYWORDS_BILLING = ["发票", "扣款", "续费", "价格", "支付", "账单"]
KEYWORDS_TECH = ["API", "Bug", "连接", "延迟", "配置", "代码", "服务器", "数据库"]
def calculate_sentiment(text):
"""
Simulate sentiment analysis.
Returns score -1.0 (Negative) to 1.0 (Positive).
"""
negative_words = ["失望", "垃圾", "慢", "差", "无法", "错误", "失败", "投诉", "骗子", "恶心", "烦", "糟糕"]
positive_words = ["谢谢", "棒", "好", "解决", "顺利", "喜欢", "感谢", "赞", "优秀", "完美"]
score = 0.0
for w in negative_words:
if w in text:
score -= 0.3
for w in positive_words:
if w in text:
score += 0.3
# Random noise
score += random.uniform(-0.1, 0.1)
return max(min(score, 1.0), -1.0)
def auto_triage(ticket):
"""
AI Triage Logic: Assign Priority and Category based on content.
"""
text = ticket['content'] + " " + ticket['subject']
# Category Detection
if any(k in text for k in KEYWORDS_BILLING):
ticket['category'] = 'Billing'
elif any(k in text for k in KEYWORDS_TECH):
ticket['category'] = 'Technical'
else:
ticket['category'] = 'General'
# Priority Detection
if any(k in text for k in KEYWORDS_URGENT):
ticket['priority'] = 'High'
ticket['tags'].append('Risk')
elif ticket['category'] == 'Billing':
ticket['priority'] = 'Medium'
else:
ticket['priority'] = 'Low'
# Sentiment
ticket['sentiment_score'] = calculate_sentiment(text)
return ticket
def generate_ticket():
"""Generate a realistic support ticket."""
created_at = fake.date_time_between(start_date='-2d', end_date='now')
# Generate somewhat context-aware content
prob = random.random()
if prob < 0.2:
subject = f"紧急:{fake.sentence(nb_words=3)}"
content = f"我的系统彻底崩溃了,无法登录!报错代码500。{fake.text()}"
elif prob < 0.4:
subject = f"关于发票的问题"
content = f"请问上个月的扣款为什么多出了50元?需要解释。{fake.text()}"
elif prob < 0.7:
subject = f"功能咨询:{fake.word()}"
content = f"我想知道如何配置API密钥,文档看不懂。{fake.text()}"
else:
subject = fake.sentence(nb_words=4)
content = fake.text()
ticket = {
"id": fake.uuid4()[:8].upper(),
"subject": subject,
"content": content,
"customer_email": fake.email(),
"customer_name": fake.name(),
"status": "New",
"created_at": created_at.strftime("%Y-%m-%d %H:%M:%S"),
"tags": [],
"history": [],
"attachments": []
}
return auto_triage(ticket)
# Initial Population
for _ in range(20):
tickets.append(generate_ticket())
# Sort by date
tickets.sort(key=lambda x: x['created_at'], reverse=True)
# --- Routes ---
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/stats')
def get_stats():
total = len(tickets)
open_tickets = len([t for t in tickets if t['status'] != 'Resolved'])
high_priority = len([t for t in tickets if t['priority'] == 'High' and t['status'] != 'Resolved'])
# Calculate avg sentiment of last 50 tickets
recent = tickets[:50]
avg_sentiment = sum(t['sentiment_score'] for t in recent) / len(recent) if recent else 0
# Agent load
agent_stats = agents
return jsonify({
"total_tickets": total,
"open_tickets": open_tickets,
"high_priority_risk": high_priority,
"customer_happiness": round((avg_sentiment + 1) * 50, 1), # Scale -1..1 to 0..100
"agents": agent_stats
})
@app.route('/api/tickets')
def get_tickets():
# Return recent 50 tickets
return jsonify(tickets[:50])
@app.route('/api/simulate', methods=['POST'])
def simulate_incoming():
"""Simulate receiving 1-3 new tickets"""
count = random.randint(1, 3)
new_tickets = []
for _ in range(count):
t = generate_ticket()
# Set to very recent
t['created_at'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
tickets.insert(0, t)
new_tickets.append(t)
# Auto-assign to least loaded online agent
online_agents = [a for a in agents if a['status'] == 'online']
if online_agents:
for t in new_tickets:
online_agents.sort(key=lambda x: x['load'])
target = online_agents[0]
target['load'] += 1
t['assigned_to'] = target['name']
t['status'] = 'Open'
return jsonify({"added": count, "tickets": new_tickets})
@app.route('/api/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
if file:
filename = file.filename
# In a real app, use secure_filename and unique IDs
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
return jsonify({'success': True, 'filename': filename, 'url': f'/uploads/{filename}'})
@app.route('/api/resolve', methods=['POST'])
def resolve_ticket():
"""Simulate resolving tickets to keep list clean"""
data = request.json
ticket_id = data.get('id')
for t in tickets:
if t['id'] == ticket_id:
t['status'] = 'Resolved'
# Reduce load of assigned agent
if 'assigned_to' in t:
for a in agents:
if a['name'] == t['assigned_to'] and a['load'] > 0:
a['load'] -= 1
return jsonify({"success": True})
return jsonify({"success": False}), 404
@app.errorhandler(404)
def page_not_found(e):
return render_template('index.html'), 404 # SPA fallback mostly
@app.errorhandler(500)
def internal_server_error(e):
return jsonify(error=str(e)), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT, debug=True)
|