Trae Assistant
Initial commit with enhanced UI and features
24d9aaf
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)