Jul 9 - Chat - save chat sessions
This commit is contained in:
+122
-33
@@ -6,7 +6,8 @@ from flask import (Blueprint, render_template, redirect, url_for,
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from app import db
|
||||
from app.models.support import SupportTicket, SupportTicketReply
|
||||
from app.models.support import (SupportTicket, SupportTicketReply,
|
||||
SupportChatSession, SupportChatMessage)
|
||||
from app.models.user import User
|
||||
from app.models.facility import Facility
|
||||
from app.utils.decorators import supervisor_required
|
||||
@@ -65,11 +66,29 @@ def chat():
|
||||
.filter(Facility.id.in_(cids), Facility.active == True)
|
||||
.order_by(Facility.name).all()) if cids else []
|
||||
|
||||
# Load the customer's most recent conversation so it continues on return.
|
||||
# A ?new=1 param (New conversation button) starts a fresh, empty window.
|
||||
start_new = request.args.get('new')
|
||||
session = None
|
||||
if not start_new:
|
||||
session = (SupportChatSession.query
|
||||
.filter_by(customer_id=current_user.id)
|
||||
.order_by(SupportChatSession.updated_at.desc())
|
||||
.first())
|
||||
chat_history = []
|
||||
if session:
|
||||
chat_history = [
|
||||
{'role': m.role, 'content': m.content}
|
||||
for m in session.messages.order_by(SupportChatMessage.created_at.asc()).all()
|
||||
]
|
||||
|
||||
groq_ready = bool(os.environ.get('GROQ_API_KEY'))
|
||||
return render_template('support/chat.html',
|
||||
faqs=FAQS,
|
||||
facilities=facilities,
|
||||
groq_ready=groq_ready)
|
||||
groq_ready=groq_ready,
|
||||
chat_session_id=(session.id if session else None),
|
||||
chat_history=chat_history)
|
||||
|
||||
|
||||
# ── Groq chat AJAX endpoint ───────────────────────────────────────────────────
|
||||
@@ -80,47 +99,92 @@ def chat_message():
|
||||
if current_user.role != 'customer':
|
||||
return jsonify({'error': 'Forbidden'}), 403
|
||||
|
||||
api_key = os.environ.get('GROQ_API_KEY')
|
||||
if not api_key:
|
||||
return jsonify({'reply': (
|
||||
"I'm sorry, the AI assistant isn't configured right now. "
|
||||
"Please use the **Submit to Support** form to reach our team directly."
|
||||
)})
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
history = data.get('messages', []) # list of {role, content} dicts
|
||||
user_message = data.get('message', '').strip()
|
||||
session_id = data.get('session_id')
|
||||
|
||||
if not user_message:
|
||||
return jsonify({'error': 'Empty message'}), 400
|
||||
|
||||
try:
|
||||
from groq import Groq
|
||||
client = Groq(api_key=api_key)
|
||||
# ── Generate the reply ────────────────────────────────────────────────
|
||||
api_key = os.environ.get('GROQ_API_KEY')
|
||||
if not api_key:
|
||||
reply = ("I'm sorry, the AI assistant isn't configured right now. "
|
||||
"Please use the **Submit to Support** form to reach our team directly.")
|
||||
else:
|
||||
try:
|
||||
from groq import Groq
|
||||
client = Groq(api_key=api_key)
|
||||
|
||||
messages = [{'role': 'system', 'content': _SYSTEM_PROMPT}]
|
||||
# Append prior conversation (cap at last 20 turns to control token usage)
|
||||
for m in history[-20:]:
|
||||
if m.get('role') in ('user', 'assistant') and m.get('content'):
|
||||
messages.append({'role': m['role'], 'content': m['content']})
|
||||
messages.append({'role': 'user', 'content': user_message})
|
||||
messages = [{'role': 'system', 'content': _SYSTEM_PROMPT}]
|
||||
# Append prior conversation (cap at last 20 turns to control token usage)
|
||||
for m in history[-20:]:
|
||||
if m.get('role') in ('user', 'assistant') and m.get('content'):
|
||||
messages.append({'role': m['role'], 'content': m['content']})
|
||||
messages.append({'role': 'user', 'content': user_message})
|
||||
|
||||
model = os.environ.get('GROQ_MODEL', 'llama-3.3-70b-versatile')
|
||||
completion = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
max_tokens=512,
|
||||
temperature=0.5,
|
||||
)
|
||||
reply = completion.choices[0].message.content.strip()
|
||||
return jsonify({'reply': reply})
|
||||
model = os.environ.get('GROQ_MODEL', 'llama-3.3-70b-versatile')
|
||||
completion = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
max_tokens=512,
|
||||
temperature=0.5,
|
||||
)
|
||||
reply = completion.choices[0].message.content.strip()
|
||||
except Exception as exc:
|
||||
logger.error('SUPPORT | Groq error: %s', exc)
|
||||
reply = ("I ran into a problem reaching the AI assistant. "
|
||||
"Please try again, or use **Submit to Support** to contact our team.")
|
||||
|
||||
except Exception as exc:
|
||||
logger.error('SUPPORT | Groq error: %s', exc)
|
||||
return jsonify({'reply': (
|
||||
"I ran into a problem reaching the AI assistant. "
|
||||
"Please try again, or use **Submit to Support** to contact our team."
|
||||
)})
|
||||
# ── Persist the turn (user message + assistant reply) ─────────────────
|
||||
now = now_eastern()
|
||||
session = None
|
||||
if session_id:
|
||||
session = db.session.get(SupportChatSession, session_id)
|
||||
if session is not None and session.customer_id != current_user.id:
|
||||
session = None # never write into someone else's session
|
||||
if session is None:
|
||||
session = SupportChatSession(customer_id=current_user.id,
|
||||
created_at=now, updated_at=now)
|
||||
db.session.add(session)
|
||||
db.session.flush() # assign session.id
|
||||
|
||||
db.session.add(SupportChatMessage(session_id=session.id, role='user',
|
||||
content=user_message, created_at=now))
|
||||
db.session.add(SupportChatMessage(session_id=session.id, role='assistant',
|
||||
content=reply, created_at=now))
|
||||
session.updated_at = now
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'reply': reply, 'session_id': session.id})
|
||||
|
||||
|
||||
# ── Customer: saved chat conversations ────────────────────────────────────────
|
||||
|
||||
@bp.route('/my-conversations')
|
||||
@login_required
|
||||
def my_conversations():
|
||||
if current_user.role != 'customer':
|
||||
abort(403)
|
||||
sessions = (SupportChatSession.query
|
||||
.filter_by(customer_id=current_user.id)
|
||||
.order_by(SupportChatSession.updated_at.desc())
|
||||
.all())
|
||||
return render_template('support/my_conversations.html', sessions=sessions)
|
||||
|
||||
|
||||
@bp.route('/my-conversations/<int:session_id>')
|
||||
@login_required
|
||||
def conversation_detail(session_id):
|
||||
if current_user.role != 'customer':
|
||||
abort(403)
|
||||
session = db.session.get(SupportChatSession, session_id)
|
||||
if session is None or session.customer_id != current_user.id:
|
||||
abort(404)
|
||||
messages = session.messages.order_by(SupportChatMessage.created_at.asc()).all()
|
||||
return render_template('support/conversation_detail.html',
|
||||
session=session, messages=messages)
|
||||
|
||||
|
||||
# ── Submit support ticket ─────────────────────────────────────────────────────
|
||||
@@ -327,6 +391,31 @@ def admin_ticket_detail(ticket_id):
|
||||
replies=replies)
|
||||
|
||||
|
||||
# ── Admin: view saved chat conversations (read-only) ──────────────────────────
|
||||
|
||||
@bp.route('/admin/conversations')
|
||||
@login_required
|
||||
@supervisor_required
|
||||
def admin_conversations():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
sessions = (SupportChatSession.query
|
||||
.order_by(SupportChatSession.updated_at.desc())
|
||||
.paginate(page=page, per_page=25, error_out=False))
|
||||
return render_template('support/admin_conversations.html', sessions=sessions)
|
||||
|
||||
|
||||
@bp.route('/admin/conversations/<int:session_id>')
|
||||
@login_required
|
||||
@supervisor_required
|
||||
def admin_conversation_detail(session_id):
|
||||
session = db.session.get(SupportChatSession, session_id)
|
||||
if session is None:
|
||||
abort(404)
|
||||
messages = session.messages.order_by(SupportChatMessage.created_at.asc()).all()
|
||||
return render_template('support/admin_conversation_detail.html',
|
||||
session=session, messages=messages)
|
||||
|
||||
|
||||
def _notify_customer_reply(ticket, reply):
|
||||
"""Create an in-app notification and send an email to the customer."""
|
||||
if not ticket.customer:
|
||||
|
||||
Reference in New Issue
Block a user