File size: 7,365 Bytes
2cf7ce5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6bb0df2
 
2cf7ce5
6bb0df2
 
 
 
 
 
 
 
 
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
"""
AI Voice + Text Chatbot - Backend
Flask + OpenRouter API with automatic multi-model fallback.

RUN:
    pip install -r requirements.txt
    python app.py

Then open http://127.0.0.1:5000 in Chrome or Edge (Web Speech API requirement).
"""

import time
import logging
import requests
from flask import Flask, render_template, request, jsonify

# ============================================================
# CONFIGURATION - put your OpenRouter API key here directly
# ============================================================

API_KEY = "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"  # <-- REPLACE THIS with your real key

OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"

# Models are tried in this exact order. If one fails (error, timeout,
# rate limit, empty reply) the next one is tried automatically until
# one succeeds or the list is exhausted.
MODELS = [
    "nvidia/nemotron-3-ultra-550b-a55b:free",
    "google/gemma-4-26b-a4b-it:free",
    "openai/gpt-oss-20b:free",
    "google/gemma-4-31b-it:free",
    "inclusionai/ling-3.0-flash:free",
    "cohere/north-mini-code:free",
    "poolside/laguna-s-2.1:free",
    "nvidia/nemotron-3-nano-30b-a3b:free",
]

REQUEST_TIMEOUT = 30  # seconds allowed per model attempt

# ============================================================
# ANSWER STYLE
# ============================================================
# This system prompt makes every model answer like an experienced human
# teacher: natural introduction -> detailed theory -> key points -> real
# world examples -> and, if code is relevant, the FULL working code first,
# followed by its explanation.

SYSTEM_PROMPT = """You are an experienced, friendly human teacher explaining concepts to a student.
Never answer with only bullet points or a dry list. Write naturally, in full sentences and
paragraphs, the way a good teacher speaks, and use clear section headers.

Follow this structure for every answer:

1. Short Introduction
Explain the topic naturally in 1-3 paragraphs, in plain conversational language.

2. Detailed Theory
Explain everything clearly using simple, professional language, step by step. Cover the
important concepts and, where relevant, advantages and disadvantages. Full sentences,
not just bullets.

3. Key Points
A bullet list summarizing the most important takeaways.

4. Real World Example
Give at least one, ideally two, practical real-world examples of the concept in use
(e.g. banking, hospital systems, e-commerce, student records, interviews, etc).

5. Code (only if the question needs code)
If the question requires code, put the COMPLETE, FULL WORKING CODE FIRST, before any
explanation, inside a fenced code block. Never explain before showing the code. After
the code, in this order: explanation of how it works, the expected output, time/space
complexity if relevant, best practices, and common mistakes to avoid.

If the question does not need code, simply skip section 5.
"""

# ============================================================
# LOGGING
# ============================================================

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
logger = logging.getLogger("voice-chatbot")

# ============================================================
# FLASK APP
# ============================================================

app = Flask(__name__)


def call_openrouter(model_name, messages):
    """
    Send a single chat completion request to OpenRouter for one model.
    Returns the reply text on success, raises RuntimeError on any failure.
    """
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        # These two headers are recommended by OpenRouter (optional but nice to have)
        "HTTP-Referer": "http://localhost:5000",
        "X-Title": "AI Voice Chatbot",
    }

    payload = {
        "model": model_name,
        "messages": messages,
    }

    response = requests.post(
        OPENROUTER_URL,
        headers=headers,
        json=payload,
        timeout=REQUEST_TIMEOUT,
    )

    if response.status_code != 200:
        raise RuntimeError(f"HTTP {response.status_code}: {response.text[:250]}")

    data = response.json()

    choices = data.get("choices")
    if not choices:
        raise RuntimeError("No choices returned from model")

    reply = choices[0].get("message", {}).get("content", "")
    if not reply or not reply.strip():
        raise RuntimeError("Empty response from model")

    return reply.strip()


def get_ai_response(user_text):
    """
    Try every model in MODELS, in order, until one succeeds.
    Returns (reply_text, model_used, elapsed_seconds).
    Raises RuntimeError only if every single model failed.
    """
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_text},
    ]

    errors = []

    for model_name in MODELS:
        start = time.time()
        try:
            logger.info(f"Trying model: {model_name}")
            reply = call_openrouter(model_name, messages)
            elapsed = round(time.time() - start, 2)
            logger.info(f"SUCCESS with {model_name} in {elapsed}s")
            return reply, model_name, elapsed
        except Exception as exc:
            elapsed = round(time.time() - start, 2)
            logger.warning(f"FAILED {model_name} after {elapsed}s: {exc}")
            errors.append(f"{model_name} -> {exc}")
            continue

    raise RuntimeError("All models failed:\n" + "\n".join(errors))


# ============================================================
# ROUTES
# ============================================================

@app.route("/")
def home():
    return render_template("index.html")


@app.route("/chat", methods=["POST"])
def chat():
    """
    Receives the merged (voice transcript + manual text) prompt from the
    browser and returns an AI answer, trying each OpenRouter model in the
    fallback list until one succeeds.
    """
    data = request.get_json(silent=True) or {}
    user_text = (data.get("text") or "").strip()

    if not user_text:
        return jsonify({"success": False, "error": "No text provided. Speak or type something first."}), 400

    if API_KEY == "YOUR_OPENROUTER_API_KEY":
        return jsonify({
            "success": False,
            "error": "Server misconfigured: set your real OpenRouter API key in app.py (API_KEY variable)."
        }), 400

    try:
        reply, model_used, elapsed = get_ai_response(user_text)
        return jsonify({
            "success": True,
            "reply": reply,
            "model": model_used,
            "response_time": elapsed,
        })
    except Exception as exc:
        logger.error(f"Chat failed completely: {exc}")
        return jsonify({"success": False, "error": str(exc)}), 500


@app.route("/status")
def status():
    return jsonify({
        "status": "ready",
        "models_configured": len(MODELS),
        "models": MODELS,
        "api_key_set": API_KEY != "YOUR_OPENROUTER_API_KEY",
    })


import os

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 7860))

    logger.info(f"Starting AI Voice Chatbot on port {port}")

    app.run(
        host="0.0.0.0",
        port=port,
        debug=False
    )