Trae Assistant
feat: complete project setup with upload feature and localization
5ccf934
import os
import json
import sqlite3
import datetime
import requests
import random
from flask import Flask, render_template, request, jsonify, g
from flask_cors import CORS
app = Flask(__name__, instance_relative_config=True)
CORS(app)
# Configuration
app.config['SECRET_KEY'] = 'support-pilot-secret-key'
app.config['DATABASE'] = os.path.join(app.instance_path, 'support_pilot.db')
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB Max Upload
SILICONFLOW_API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi"
SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions"
# Ensure instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# --- Database Helpers ---
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(app.config['DATABASE'])
g.db.row_factory = sqlite3.Row
return g.db
@app.teardown_appcontext
def close_db(error):
if hasattr(g, 'db'):
g.db.close()
def init_db():
db = get_db()
# Tickets/Logs Table
db.execute('''
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
source TEXT DEFAULT 'manual',
sentiment TEXT,
intent TEXT,
priority TEXT,
status TEXT DEFAULT 'new',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Knowledge Base / Solutions
db.execute('''
CREATE TABLE IF NOT EXISTS solutions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
tags TEXT,
usage_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Seed Data
cursor = db.execute("SELECT count(*) FROM solutions")
if cursor.fetchone()[0] == 0:
db.execute("INSERT INTO solutions (title, content, tags) VALUES (?, ?, ?)",
("退款流程", "用户申请退款需提供订单号,若在7天内且无使用痕迹,可直接办理。", "refund,policy"))
db.execute("INSERT INTO solutions (title, content, tags) VALUES (?, ?, ?)",
("账号无法登录", "请引导用户检查大小写,或使用'忘记密码'功能重置。", "login,account"))
# Seed Tickets
cursor = db.execute("SELECT count(*) FROM tickets")
if cursor.fetchone()[0] == 0:
sample_tickets = [
("我的包裹丢了!你们怎么搞的?", "Negative", "Logistics", "High"),
("这个产品怎么使用?说明书看不懂。", "Neutral", "Inquiry", "Medium"),
("感谢你们的帮助,问题解决了。", "Positive", "Feedback", "Low")
]
for t in sample_tickets:
db.execute("INSERT INTO tickets (content, sentiment, intent, priority, status) VALUES (?, ?, ?, ?, ?)",
(t[0], t[1], t[2], t[3], 'closed'))
db.commit()
# --- AI Integration ---
def call_ai_model(system_prompt, user_input, model="Qwen/Qwen2.5-7B-Instruct"):
headers = {
"Authorization": f"Bearer {SILICONFLOW_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_input}
],
"temperature": 0.7,
"response_format": {"type": "json_object"}
}
try:
response = requests.post(SILICONFLOW_API_URL, json=payload, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
content = data['choices'][0]['message']['content']
# Try to parse JSON
try:
return json.loads(content)
except json.JSONDecodeError:
# Fallback if model didn't return pure JSON
return {"raw_content": content, "error": "json_parse_error"}
except Exception as e:
print(f"AI API Error: {e}")
# Mock Fallback
return None
# --- Routes ---
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/init', methods=['POST'])
def init_data():
with app.app_context():
init_db()
return jsonify({"status": "initialized"})
@app.route('/api/analyze', methods=['POST'])
def analyze_ticket():
content = request.json.get('content', '')
if not content:
return jsonify({"error": "No content"}), 400
system_prompt = """
You are a Customer Support AI Analyst. Analyze the following customer message.
Return a JSON object with:
- "sentiment": "Positive", "Neutral", "Negative" or "Urgent"
- "intent": The user's main goal (e.g., Refund, Tech Support, Inquiry)
- "priority": "Low", "Medium", "High", "Critical"
- "suggested_reply": A polite, professional, and helpful draft response (in Chinese).
- "keywords": A list of 3-5 keywords.
"""
ai_result = call_ai_model(system_prompt, content)
# Fallback if AI fails
if not ai_result or 'error' in ai_result:
ai_result = {
"sentiment": "Neutral (Mock)",
"intent": "Unknown",
"priority": "Medium",
"suggested_reply": "感谢您的反馈,人工客服稍后会介入处理。",
"keywords": ["fallback"]
}
# Save to DB
db = get_db()
db.execute('INSERT INTO tickets (content, sentiment, intent, priority, status) VALUES (?, ?, ?, ?, ?)',
(content, ai_result.get('sentiment'), ai_result.get('intent'), ai_result.get('priority'), 'analyzed'))
db.commit()
return jsonify(ai_result)
@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
try:
# Read file content
content = file.read()
# Try to decode as UTF-8
try:
text_content = content.decode('utf-8')
return jsonify({"content": text_content, "filename": file.filename})
except UnicodeDecodeError:
return jsonify({"error": "Binary or non-UTF-8 file detected. Please upload a text file."}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/stats', methods=['GET'])
def get_stats():
db = get_db()
tickets = db.execute('SELECT * FROM tickets ORDER BY created_at DESC LIMIT 50').fetchall()
solutions = db.execute('SELECT * FROM solutions').fetchall()
# Calculate stats
sentiment_counts = {}
intent_counts = {}
for t in tickets:
s = t['sentiment'] or 'Unknown'
i = t['intent'] or 'Other'
sentiment_counts[s] = sentiment_counts.get(s, 0) + 1
intent_counts[i] = intent_counts.get(i, 0) + 1
return jsonify({
"total_tickets": len(tickets),
"total_solutions": len(solutions),
"sentiment_dist": [{"name": k, "value": v} for k, v in sentiment_counts.items()],
"intent_dist": [{"name": k, "value": v} for k, v in intent_counts.items()],
"recent_tickets": [dict(t) for t in tickets[:5]]
})
@app.route('/api/simulate', methods=['POST'])
def simulate_chat():
# Roleplay: User acts as Agent, AI acts as Customer
history = request.json.get('history', []) # List of {role, content}
scenario = request.json.get('scenario', 'refund_dispute')
system_prompt = f"""
You are roleplaying as a CUSTOMER in a support chat.
Scenario: {scenario}.
Your goal is to test the support agent. Be realistic.
If the agent helps you, thank them. If they are rude or unhelpful, get angry.
Return JSON: {{"reply": "Your response as customer", "satisfaction_score": 1-10 (integer)}}
"""
last_msg = history[-1]['content'] if history else "Start"
ai_result = call_ai_model(system_prompt, last_msg)
if not ai_result:
ai_result = {
"reply": "Wait, I need to check (Simulated Delay)...",
"satisfaction_score": 5
}
return jsonify(ai_result)
@app.route('/api/solutions', methods=['GET', 'POST'])
def manage_solutions():
db = get_db()
if request.method == 'POST':
data = request.json
db.execute('INSERT INTO solutions (title, content, tags) VALUES (?, ?, ?)',
(data['title'], data['content'], data['tags']))
db.commit()
return jsonify({"status": "success"})
else:
rows = db.execute('SELECT * FROM solutions ORDER BY usage_count DESC').fetchall()
return jsonify([dict(r) for r in rows])
if __name__ == '__main__':
with app.app_context():
init_db()
app.run(host='0.0.0.0', port=7860, debug=True)