File size: 9,634 Bytes
bb12219
 
b96b289
bb12219
 
f0502f7
bb12219
cab03d6
bb12219
351bb59
 
bb12219
f51917e
bb12219
 
 
ee2e099
f0502f7
 
bb12219
 
 
 
f0502f7
bb12219
 
 
351bb59
f0502f7
5e51aba
26fd760
 
 
 
351bb59
f0502f7
bb12219
 
 
 
f0502f7
bb12219
 
 
 
 
 
 
 
f51917e
2df5630
e92af5d
 
 
53ff958
 
 
e92af5d
 
 
2df5630
e92af5d
 
 
 
bb12219
 
 
 
f51917e
bb12219
f51917e
 
bb12219
 
 
f51917e
bb12219
f51917e
 
bb12219
7b32b5c
caa2aff
 
 
 
 
 
 
 
 
 
 
 
 
5e51aba
caa2aff
 
170f71d
 
f51917e
 
 
 
5e51aba
7b32b5c
f51917e
bb12219
 
 
 
 
 
f51917e
bb12219
 
f0502f7
bb12219
 
 
 
 
 
f51917e
bb12219
 
 
 
 
 
 
cab03d6
 
 
 
 
caa2aff
cab03d6
bb12219
 
 
 
 
 
 
351bb59
bb12219
 
 
 
 
 
 
f0502f7
bb12219
 
f51917e
bb12219
 
 
 
 
 
 
 
 
f51917e
bb12219
 
 
f0502f7
bb12219
 
 
 
f0502f7
bb12219
 
 
 
 
f0502f7
 
 
f96cd6f
 
cab03d6
5e51aba
 
 
 
cab03d6
 
 
f51917e
 
 
 
 
 
 
 
 
351bb59
f51917e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e51aba
 
 
f51917e
 
 
 
 
 
 
 
 
 
cab03d6
f96cd6f
351bb59
f0502f7
 
 
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
import os
import tempfile
from fastapi import FastAPI, UploadFile, File, Header, HTTPException, Body
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from spitch import Spitch
from langchain.prompts import PromptTemplate
from langchain_huggingface import HuggingFaceEndpoint
from langdetect import detect, DetectorFactory
from huggingface_hub.utils import HfHubHTTPError
from smebuilder_vector import retriever  # your retriever

# ----------------- CONFIG -----------------
DetectorFactory.seed = 0

SPITCH_API_KEY = os.getenv("SPITCH_API_KEY")
HF_MODEL = os.getenv("HF_MODEL", "deepseek-ai/deepseek-coder-1.3b-instruct")
FRONTEND_ORIGIN = os.getenv("ALLOWED_ORIGIN", "*")
PROJECT_API_KEY = os.getenv("PROJECT_API_KEY", "")

if not SPITCH_API_KEY:
    raise RuntimeError("Set SPITCH_API_KEY in environment before starting.")

# Init Spitch
os.environ["SPITCH_API_KEY"] = SPITCH_API_KEY
spitch_client = Spitch()

# HuggingFace LLM
llm = HuggingFaceEndpoint(
    repo_id=HF_MODEL,
    temperature=0.7,
    top_p=0.9,
    do_sample=True,
    repetition_penalty=1.1,
    max_new_tokens=2048
)

# FastAPI app
app = FastAPI(title="DevAssist AI Backend (FastAPI + LangChain)")

# CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=[FRONTEND_ORIGIN] if FRONTEND_ORIGIN != "*" else ["*"],
    allow_credentials=True,
    allow_methods=["GET", "POST", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type"],
)

# ----------------- PROMPT TEMPLATES -----------------
chat_template = """You are DevAssist, an AI coding assistant.

Guidelines:
- Always format responses in Markdown.
- Do NOT use inline formatting like bold (**), italics (*), or underlines.
- Use plain text section headers (e.g., Explanation:, Steps to Fix:, Fixed Code:).
- Use bullet points (-, 1.) for steps.
- Use fenced code blocks (```python ... ```) for code.
- Be friendly yet professional; explain step by step.

Question: {question}

Answer:
"""

stt_chat_template = """You are DevAssist, an AI coding assistant.
- The input is transcribed speech. Interpret it as a dev question.
- Provide clear answers with code examples (use markdown triple backticks).
- If input is unclear, ask a clarifying question.

Spoken Question: {speech}
Answer:
"""

autodoc_template = """You are DevAssist DocBot.
- Read the code and produce professional documentation in markdown.

Code: {code}
Documentation:
"""

sme_template = """
You are a senior full-stack engineer specializing in modern front-end development.
Your job is to generate **production-ready code** for websites and apps.

Guidelines:
- Always return three separate files: index.html, styles.css, and script.js
- HTML must be semantic, responsive, and mobile-first (use <meta viewport>)
- CSS should use Flexbox/Grid and include hover/transition effects
- JavaScript must add interactivity (e.g. button actions, animations, toggles)
- Include a hero section, feature grid, testimonials, and footer
- Fill with realistic content (no lorem ipsum, no placeholders)
- Return **only valid JSON** with keys: "files" → { "index.html": "...", "styles.css": "...", "script.js": "..." }

Prompt: {user_prompt}
Context: {context}

Output:
"""

# ----------------- CHAINS -----------------
chat_chain = PromptTemplate(input_variables=["question"], template=chat_template) | llm
stt_chain = PromptTemplate(input_variables=["speech"], template=stt_chat_template) | llm
autodoc_chain = PromptTemplate(input_variables=["code"], template=autodoc_template) | llm
sme_chain = PromptTemplate(input_variables=["user_prompt", "context"], template=sme_template) | llm

# ----------------- REQUEST MODELS -----------------
class ChatRequest(BaseModel):
    question: str

class AutoDocRequest(BaseModel):
    code: str

# ----------------- AUTH -----------------
def check_auth(authorization: str | None):
    if not PROJECT_API_KEY:
        return
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing bearer token")
    token = authorization.split(" ", 1)[1]
    if token != PROJECT_API_KEY:
        raise HTTPException(status_code=403, detail="Invalid token")

# ----------------- ENDPOINTS -----------------
@app.get("/")
def root():
    return {"status": "DevAssist AI Backend running"}

@app.post("/chat")
def chat(req: ChatRequest, authorization: str | None = Header(None)):
    check_auth(authorization)
    try:
        answer = chat_chain.invoke({"question": req.question})
        return {"reply": answer.strip() if isinstance(answer, str) else str(answer)}
    except HfHubHTTPError as e:
        if "exceeded" in str(e).lower() or "quota" in str(e).lower():
            return {"reply": "⚠️ Daily token limit reached. Try again in 24 hours."}
        raise e

@app.post("/stt")
async def stt_audio(file: UploadFile = File(...), lang_hint: str | None = None, authorization: str | None = Header(None)):
    check_auth(authorization)

    suffix = os.path.splitext(file.filename)[1] or ".wav"
    with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tf:
        tf.write(await file.read())
        tmp_path = tf.name

    try:
        if lang_hint:
            resp = spitch_client.speech.transcribe(language=lang_hint, content=open(tmp_path, "rb").read())
        else:
            resp = spitch_client.speech.transcribe(content=open(tmp_path, "rb").read())
    except Exception:
        resp = spitch_client.speech.transcribe(language="en", content=open(tmp_path, "rb").read())

    transcription = getattr(resp, "text", "") or (resp.get("text", "") if isinstance(resp, dict) else "")
    try:
        detected_lang = detect(transcription) if transcription.strip() else "en"
    except Exception:
        detected_lang = "en"

    translation = transcription
    if detected_lang != "en":
        try:
            translation_resp = spitch_client.text.translate(text=transcription, source=detected_lang, target="en")
            translation = getattr(translation_resp, "text", "") or translation_resp.get("text", "")
        except Exception:
            translation = transcription

    reply = stt_chain.invoke({"speech": translation})
    return {
        "transcription": transcription,
        "detected_language": detected_lang,
        "translation": translation,
        "reply": reply.strip() if isinstance(reply, str) else str(reply)
    }

@app.post("/autodoc")
def autodoc(req: AutoDocRequest, authorization: str | None = Header(None)):
    check_auth(authorization)
    docs = autodoc_chain.invoke({"code": req.code})
    return {"documentation": docs.strip() if isinstance(docs, str) else str(docs)}

@app.post("/sme/generate")
async def sme_generate(payload: dict = Body(...)):
    try:
        user_prompt = payload.get("user_prompt", "")
        context_docs = retriever.get_relevant_documents(user_prompt)
        context = "\n".join([doc.page_content for doc in context_docs]) if context_docs else "No extra context"
        response = sme_chain.invoke({"user_prompt": user_prompt, "context": context})
        return {"success": True, "data": response}
    except HfHubHTTPError as e:
        if "exceeded" in str(e).lower() or "quota" in str(e).lower():
            return {"success": False, "error": "⚠️ Token quota for today has been used. Please come back in 24 hours."}
        raise e

@app.post("/sme/speech-generate")
async def sme_speech_generate(file: UploadFile = File(...), lang_hint: str | None = None, authorization: str | None = Header(None)):
    check_auth(authorization)

    suffix = os.path.splitext(file.filename)[1] or ".wav"
    with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tf:
        tf.write(await file.read())
        tmp_path = tf.name

    try:
        if lang_hint:
            resp = spitch_client.speech.transcribe(language=lang_hint, content=open(tmp_path, "rb").read())
        else:
            resp = spitch_client.speech.transcribe(content=open(tmp_path, "rb").read())
    except Exception:
        resp = spitch_client.speech.transcribe(language="en", content=open(tmp_path, "rb").read())

    transcription = getattr(resp, "text", "") or (resp.get("text", "") if isinstance(resp, dict) else "")
    try:
        detected_lang = detect(transcription) if transcription.strip() else "en"
    except Exception:
        detected_lang = "en"

    translation = transcription
    if detected_lang != "en":
        try:
            translation_resp = spitch_client.text.translate(text=transcription, source=detected_lang, target="en")
            translation = getattr(translation_resp, "text", "") or translation_resp.get("text", "")
        except Exception:
            translation = transcription

    try:
        context_docs = retriever.get_relevant_documents(translation)
        context = "\n".join([doc.page_content for doc in context_docs]) if context_docs else "No extra context"
        sme_response = sme_chain.invoke({"user_prompt": translation, "context": context})
        return {
            "success": True,
            "transcription": transcription,
            "detected_language": detected_lang,
            "translation": translation,
            "sme_site": sme_response
        }
    except HfHubHTTPError as e:
        if "exceeded" in str(e).lower() or "quota" in str(e).lower():
            return {"success": False, "error": "⚠️ Token quota for today has been used. Please come back in 24 hours."}
        raise e

# ----------------- MAIN -----------------
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=False)