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)