Py.Cafe

hejhdiss/

custom-ai-assistant-for-devs

Personalized AI Assistant for Developers

DocsPricing
  • app.py
  • requirements.txt
app.py
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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
from flask import Flask, request, jsonify
from flask_cors import CORS
import os, json, uuid, requests
from supabase import create_client, Client 
import pycafe
app = Flask(__name__)
CORS(app)


SUPABASE_URL = pycafe.get_secret("SUPABASE_URL")
SUPABASE_KEY = pycafe.get_secret("SUPABASE_KEY")

if not SUPABASE_URL or not SUPABASE_KEY:
    raise ValueError(
        "SUPABASE_URL and SUPABASE_KEY environment variables must be set. "
        "Please check your environment setup. "
    )

supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

ROLE_MAPPING = {
    "java": {
        "full_name": "java developer",
        "json_file": "java polished.json" 
    },
    "android": {
        "full_name": "android developer",
        "json_file": "android.json" 
    }
}


OPENROUTER_KEY =  pycafe.get_secret("OPENROUTER_KEY")
if not OPENROUTER_KEY:
    raise ValueError(
        "OPENROUTER_KEY environment variable must be set. "
        "Please get one from https://openrouter.ai/ and set it."
    )


def load_json(file_name):
    """Loads a JSON file from the local filesystem."""
    try:
        with open(file_name, "r", encoding="utf-8") as f:
            return json.load(f)
    except FileNotFoundError:
        raise FileNotFoundError(f"JSON file not found: {file_name}. "
                                "Please ensure it's in the correct directory.")
    except json.JSONDecodeError:
        raise ValueError(f"Error decoding JSON from file: {file_name}. "
                         "Please check if the file content is valid JSON.")


def extract_role(query):
    """Extracts a role keyword from the user query."""
    role_map_keywords = {
        "java": ["java", "jdk", "java developer"],
        "android": ["android", "kotlin", "android studio", "android developer"]
    }
    query = query.lower()
    for role_key, keywords in role_map_keywords.items():
        if any(k in query for k in keywords):
            return role_key
    return None

async def save_session_to_supabase(session_id: str, session_data: dict):
    """Saves or updates session data in Supabase."""
    try:
        
        response = supabase.table("sessions").upsert(
            {"session_id": session_id, "session_data": session_data},
            on_conflict="session_id" 
        ).execute()
        response.data 
    except Exception as e:
        print(f"Error saving session '{session_id}' to Supabase: {e}")


async def load_session_from_supabase(session_id: str) -> dict:
    """Loads session data from Supabase."""
    try:
        response = supabase.table("sessions").select("session_data").eq("session_id", session_id).execute()
        if response.data and len(response.data) > 0:
            return response.data[0]["session_data"]

        return {"history": []} 
    except Exception as e:
        print(f"Error loading session '{session_id}' from Supabase: {e}")
        return {"history": []}


def generate_response(user_query: str, json_data: dict, role_title: str) -> str:
    """Generates an AI response using OpenRouter."""
    prompt = f"""
You are a helpful assistant for {role_title}(softwarestackhubassisstant).

Respond in **Markdown** with:
- Headings
- Bullet points
- Code blocks
- Better formatting
- Use emojis where appropriate

---

### User Query:
{user_query}

---

### Tool Dataset:
{json.dumps(json_data, indent=2)}

---

Now write the answer:
"""
    headers = {
        "Authorization": f"Bearer {OPENROUTER_KEY}",
        "HTTP-Referer": "https://dsfapp.com/", 
        "X-Title": "StackHub AI"
    }
    data = {
        "model": "deepseek/deepseek-r1-0528:free", 
        "messages": [{"role": "user", "content": prompt}]
    }
    try:
        r = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=data)
        r.raise_for_status() 
        return r.json()["choices"][0]["message"]["content"]
    except requests.exceptions.RequestException as e:
        print(f"OpenRouter API request failed: {e}")
        return f"AI response failed (API request error): {str(e)}"
    except KeyError:
        print(f"Unexpected API response format from OpenRouter: {r.json()}")
        return "AI response failed: Unexpected API response format."
    except Exception as e:
        print(f"An unexpected error occurred during AI response generation: {e}")
        return f"AI response failed: {str(e)}"

@app.route("/ask", methods=["POST"])
async def ask():
    """Handles user queries, manages sessions, and generates AI responses."""
    data = request.get_json()
    raw_query = data.get("query", "").strip()

    try:
     
        session_id, query = raw_query.split(" ", 1)
        session_id = session_id.strip()
        query = query.strip()
    except ValueError:
        return jsonify({"error": "Query format: '<session_id> job:...' or '<session_id> user query: ...'"}), 400

    session_data = await load_session_from_supabase(session_id)

    if "history" not in session_data:
        session_data["history"] = []


    if query.lower() == "system: delete":
        try:
            response = supabase.table("sessions").delete().eq("session_id", session_id).execute()
            if response.data: 
                return jsonify({"status": f"Session '{session_id}' deleted."})
            return jsonify({"status": f"No session found with ID '{session_id}'."})
        except Exception as e:
            return jsonify({"error": f"Failed to delete session from Supabase: {e}"}), 500


    if query.lower().startswith("job:"):
        extracted_role_key = extract_role(query)
        role_details = ROLE_MAPPING.get(extracted_role_key)

        if role_details:
            full_role_name = role_details["full_name"]
            json_file_name = role_details["json_file"]

            welcome_message = (
                f"👋 Welcome! You are now connected with a professional AI assistant for "
                f"**{full_role_name.title()}**."
            )

      
            session_data["history"].append({"query": query, "response": welcome_message, "role": full_role_name})
            session_data["json_used"] = json_file_name
            await save_session_to_supabase(session_id, session_data)
            return jsonify({"session_id": session_id, "answer": welcome_message})
        else:
            sorry_message = "Sorry, this profession is not yet supported."
            session_data["history"].append({"query": query, "response": sorry_message})
            await save_session_to_supabase(session_id, session_data) 
            return jsonify({"session_id": session_id, "answer": sorry_message})

    json_file = session_data.get("json_used")


    if not json_file:
        assigned_role_from_history = None
        for entry in reversed(session_data["history"]):
            if "role" in entry:
                assigned_role_from_history = entry["role"]
                break

        if assigned_role_from_history:
            for key, details in ROLE_MAPPING.items():
                if details["full_name"] == assigned_role_from_history:
                    json_file = details["json_file"]
                    break


        if not json_file:
            return jsonify({"error": "No valid profession assigned. Please start with 'job:...' to select a role."}), 400

    if not os.path.exists(json_file):
        return jsonify({"error": f"Assigned JSON file '{json_file}' not found on the server. "
                                 "Please check your ROLE_MAPPING configuration and file paths."}), 500

    current_role_for_prompt = "General User"
    for entry in reversed(session_data["history"]):
        if "role" in entry:
            current_role_for_prompt = entry["role"].title()
            break

    try:
        json_data_for_ai = load_json(json_file)
        response = generate_response(query, json_data_for_ai, current_role_for_prompt)
        session_data["history"].append({"query": query, "response": response})
        await save_session_to_supabase(session_id, session_data)
        return jsonify({"session_id": session_id, "answer": response})
    except Exception as e:
        print(f"Error during AI response generation or session saving: {e}")
        return jsonify({"error": str(e)}), 500

@app.route("/history/<session_id>", methods=["GET"])
async def history(session_id: str):
    """Retrieves and returns the full history for a given session ID."""
    session_data = await load_session_from_supabase(session_id)

    if not session_data.get("history"):
        return jsonify({"error": f"No session found with ID '{session_id}' or session is empty."}), 404


    role_in_session_title_cased = None
    for entry in reversed(session_data["history"]):
        if "role" in entry:
            role_in_session_title_cased = entry["role"].title()
            break

    return jsonify({
        "session_id": session_id,
        "role": role_in_session_title_cased,
        "json_used": session_data.get("json_used"),
        "history": session_data["history"]
    })

@app.route("/reset", methods=["POST"])
async def reset():
    """Clears all sessions from the Supabase database."""
    try:
       
        response = supabase.table("sessions").delete().neq("session_id", "NULL").execute()

        return jsonify({"status": f"All {len(response.data)} sessions cleared from Supabase."})
    except Exception as e:
        print(f"Error clearing all sessions from Supabase: {e}")
        return jsonify({"error": f"Failed to clear all sessions: {e}"}), 500

if __name__ == "__main__":
   app.run(debug=True, port=5000)