feat: update AI assistant views
Browse files- ai_assistant/views.py +168 -2
ai_assistant/views.py
CHANGED
|
@@ -1,3 +1,169 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
from rest_framework.views import APIView
|
| 4 |
+
from rest_framework.response import Response
|
| 5 |
+
from rest_framework import permissions, status
|
| 6 |
+
from django.db.models import Sum
|
| 7 |
+
from finance.models import Transaction, Budget
|
| 8 |
|
| 9 |
+
class ChatbotView(APIView):
|
| 10 |
+
permission_classes = [permissions.IsAuthenticated]
|
| 11 |
+
|
| 12 |
+
def post(self, request):
|
| 13 |
+
user_message = request.data.get('message')
|
| 14 |
+
if not user_message:
|
| 15 |
+
return Response({"error": "Message is required"}, status=status.HTTP_400_BAD_REQUEST)
|
| 16 |
+
|
| 17 |
+
# 1. Gather User Context
|
| 18 |
+
user = request.user
|
| 19 |
+
|
| 20 |
+
# Calculate cash available from profile
|
| 21 |
+
cash_available = getattr(user, 'profile', None) and user.profile.cash_available or 0
|
| 22 |
+
net_worth = getattr(user, 'profile', None) and user.profile.net_worth or 0
|
| 23 |
+
|
| 24 |
+
# Get budgets
|
| 25 |
+
budgets = Budget.objects.filter(user=user)
|
| 26 |
+
budget_context = "\n".join([f"- {b.name}: ${b.spent_amount} spent out of ${b.budget_amount} ({b.status})" for b in budgets])
|
| 27 |
+
|
| 28 |
+
# Get recent transactions
|
| 29 |
+
recent_tx = Transaction.objects.filter(user=user).order_by('-date')[:5]
|
| 30 |
+
tx_context = "\n".join([f"- {t.date}: ${t.amount} at {t.merchant} ({t.category})" for t in recent_tx])
|
| 31 |
+
|
| 32 |
+
context_prompt = f"""You are Aureon's premium AI Financial Assistant. You give concise, professional, and helpful financial advice.
|
| 33 |
+
User Profile:
|
| 34 |
+
- Available Cash: ${cash_available}
|
| 35 |
+
- Net Worth: ${net_worth}
|
| 36 |
+
Active Budgets:
|
| 37 |
+
{budget_context if budget_context else "No active budgets."}
|
| 38 |
+
Recent Transactions:
|
| 39 |
+
{tx_context if tx_context else "No recent transactions."}
|
| 40 |
+
|
| 41 |
+
Based strictly on this data, answer the user's question. Do not make up data not present here.
|
| 42 |
+
User Question: "{user_message}"
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
# 2. Call Hugging Face API
|
| 46 |
+
hf_api_key = os.getenv('HUGGINGFACE_API_KEY')
|
| 47 |
+
if not hf_api_key:
|
| 48 |
+
return Response({"error": "Hugging Face API Key is not configured on the server. Please add HUGGINGFACE_API_KEY to your .env file."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
| 49 |
+
|
| 50 |
+
# Using Llama 3.1 8B Instruct via modern Hugging Face OpenAI-compatible Router Endpoint
|
| 51 |
+
API_URL = "https://router.huggingface.co/v1/chat/completions"
|
| 52 |
+
headers = {
|
| 53 |
+
"Authorization": f"Bearer {hf_api_key}",
|
| 54 |
+
"Content-Type": "application/json"
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
# OpenAI-compatible context-aware payload
|
| 58 |
+
payload = {
|
| 59 |
+
"model": "meta-llama/Llama-3.1-8B-Instruct",
|
| 60 |
+
"messages": [
|
| 61 |
+
{"role": "system", "content": context_prompt},
|
| 62 |
+
{"role": "user", "content": user_message}
|
| 63 |
+
],
|
| 64 |
+
"max_tokens": 300,
|
| 65 |
+
"temperature": 0.5
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
| 70 |
+
if response.status_code == 200:
|
| 71 |
+
result = response.json()
|
| 72 |
+
if 'choices' in result and len(result['choices']) > 0:
|
| 73 |
+
bot_reply = result['choices'][0]['message']['content'].strip()
|
| 74 |
+
else:
|
| 75 |
+
bot_reply = "I'm sorry, I couldn't generate a response at this time."
|
| 76 |
+
|
| 77 |
+
return Response({"reply": bot_reply})
|
| 78 |
+
else:
|
| 79 |
+
return Response({"error": f"AI model error: {response.text}"}, status=status.HTTP_502_BAD_GATEWAY)
|
| 80 |
+
except Exception as e:
|
| 81 |
+
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
import json
|
| 85 |
+
|
| 86 |
+
class AIInsightsView(APIView):
|
| 87 |
+
permission_classes = [permissions.IsAuthenticated]
|
| 88 |
+
|
| 89 |
+
def get(self, request):
|
| 90 |
+
user = request.user
|
| 91 |
+
cash_available = getattr(user, 'profile', None) and user.profile.cash_available or 0
|
| 92 |
+
net_worth = getattr(user, 'profile', None) and user.profile.net_worth or 0
|
| 93 |
+
|
| 94 |
+
# Get budgets
|
| 95 |
+
budgets = Budget.objects.filter(user=user)
|
| 96 |
+
budget_context = "\n".join([f"- {b.name}: ${b.spent_amount} spent out of ${b.budget_amount} ({b.status})" for b in budgets])
|
| 97 |
+
|
| 98 |
+
# Get recent transactions
|
| 99 |
+
recent_tx = Transaction.objects.filter(user=user).order_by('-date')[:5]
|
| 100 |
+
tx_context = "\n".join([f"- {t.date}: ${t.amount} at {t.merchant} ({t.category})" for t in recent_tx])
|
| 101 |
+
|
| 102 |
+
context_prompt = f"""You are Aureon's premium AI Financial Assistant. Based on the user's financial profile, generate exactly three helpful financial insights.
|
| 103 |
+
User Profile:
|
| 104 |
+
- Available Cash: ${cash_available}
|
| 105 |
+
- Net Worth: ${net_worth}
|
| 106 |
+
Active Budgets:
|
| 107 |
+
{budget_context if budget_context else "No active budgets."}
|
| 108 |
+
Recent Transactions:
|
| 109 |
+
{tx_context if tx_context else "No recent transactions."}
|
| 110 |
+
|
| 111 |
+
Provide the output strictly as a JSON array of three objects. Do not include markdown code block formatting (like ```json), other tags, or explanations. Each object must have these exactly:
|
| 112 |
+
- "id": unique number 1, 2, 3
|
| 113 |
+
- "type": one of "success", "warning", "suggestion", "pattern"
|
| 114 |
+
- "title": a very short title
|
| 115 |
+
- "message": a helpful one-sentence message
|
| 116 |
+
- "savings": optional, an integer number of potential savings in dollars (e.g. 42)
|
| 117 |
+
- "detail": a short secondary detail string (e.g. "Keep it up!")
|
| 118 |
+
- "options": optional array of 2 strings if type is "suggestion"
|
| 119 |
+
|
| 120 |
+
Example:
|
| 121 |
+
[
|
| 122 |
+
{{"id": 1, "type": "success", "title": "Great news!", "message": "You spent 15% less this week on food.", "savings": 25, "detail": "Keep it up!"}},
|
| 123 |
+
{{"id": 2, "type": "warning", "title": "Watch out!", "message": "Your subscriptions total $45 this month.", "detail": "Consider cancelling unused ones."}},
|
| 124 |
+
{{"id": 3, "type": "suggestion", "title": "Smart Suggestion", "message": "You have extra cash.", "options": ["Invest it", "Add to goal"]}}
|
| 125 |
+
]
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
hf_api_key = os.getenv('HUGGINGFACE_API_KEY')
|
| 129 |
+
if not hf_api_key:
|
| 130 |
+
fallback_insights = [
|
| 131 |
+
{"id": 1, "type": "success", "title": "Welcome Aboard!", "message": "Your Aureon setup is complete. Let's start tracking your finances.", "detail": "Nice job!"},
|
| 132 |
+
{"id": 2, "type": "warning", "title": "No Transactions Yet", "message": "Upload a CSV bank statement to get deep Llama-powered insights.", "detail": "Visit Data Import page."},
|
| 133 |
+
{"id": 3, "type": "suggestion", "title": "Set a Saving Goal", "message": "Create your first goal to help Llama calculate targets.", "options": ["Go to Goals", "View suggestions"]}
|
| 134 |
+
]
|
| 135 |
+
return Response(fallback_insights)
|
| 136 |
+
|
| 137 |
+
# Using Llama 3.1 8B Instruct via modern Hugging Face OpenAI-compatible Router Endpoint
|
| 138 |
+
API_URL = "https://router.huggingface.co/v1/chat/completions"
|
| 139 |
+
headers = {
|
| 140 |
+
"Authorization": f"Bearer {hf_api_key}",
|
| 141 |
+
"Content-Type": "application/json"
|
| 142 |
+
}
|
| 143 |
+
payload = {
|
| 144 |
+
"model": "meta-llama/Llama-3.1-8B-Instruct",
|
| 145 |
+
"messages": [
|
| 146 |
+
{"role": "user", "content": context_prompt}
|
| 147 |
+
],
|
| 148 |
+
"max_tokens": 500,
|
| 149 |
+
"temperature": 0.3
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
try:
|
| 153 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
| 154 |
+
if response.status_code == 200:
|
| 155 |
+
result = response.json()
|
| 156 |
+
bot_reply = result['choices'][0]['message']['content'].strip() if ('choices' in result and len(result['choices']) > 0) else ""
|
| 157 |
+
if bot_reply.startswith("```"):
|
| 158 |
+
bot_reply = bot_reply.strip("`").strip("json").strip()
|
| 159 |
+
parsed_insights = json.loads(bot_reply)
|
| 160 |
+
return Response(parsed_insights)
|
| 161 |
+
else:
|
| 162 |
+
raise Exception(response.text)
|
| 163 |
+
except Exception as e:
|
| 164 |
+
fallback_insights = [
|
| 165 |
+
{"id": 1, "type": "success", "title": "Safe Balance", "message": f"Your current cash balance is ${cash_available}.", "detail": "Good liquidity status."},
|
| 166 |
+
{"id": 2, "type": "suggestion", "title": "Create a Budget", "message": "Setting up budgets helps Llama suggest dynamic saving paths.", "detail": "Create one in the Budget tab."},
|
| 167 |
+
{"id": 3, "type": "pattern", "title": "Clean Sheet", "message": "No abnormal spending patterns detected this week.", "detail": "Nice work!"}
|
| 168 |
+
]
|
| 169 |
+
return Response(fallback_insights)
|