File size: 6,744 Bytes
1bc6e15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
run.py - FastAPI backend + frontend server for GAKR AI

Features:
- Serves templates/chat.html at "/"
- /api/analyze:
    - prompt: required (must not be empty)
    - files: optional list[UploadFile] (0, 1, many; any type)
    - api_key: required
- If files are present β†’ file-analysis mode (different system prompt)
- If no files β†’ general assistant mode
- Detailed exceptions and logs for easier debugging
"""

from typing import List, Optional

import json
import traceback

from fastapi import (
    FastAPI,
    HTTPException,
    Form,
    UploadFile,
    File,
    Request,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates
import uvicorn

from load_model import init_model
from generate import generate_response
from file_pipeline import process_files


# ============================================================
# APP SETUP
# ============================================================

app = FastAPI(title="GAKR AI")

# Templates (chat.html lives in ./templates)
templates = Jinja2Templates(directory="templates")

# CORS (open for dev; restrict origins in production)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],         # change to specific origin in production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# API Key
API_KEY = "gakr-ai-2025-secret"

# Load model ONCE at startup
print("πŸš€ Starting GAKR AI Backend...")
try:
    model, tokenizer = init_model(".")
    print("βœ… Model initialized successfully")
except Exception as e:
    print("❌ Failed to load model at startup:")
    traceback.print_exc()
    raise e


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

@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    """
    Serve chat.html as homepage.
    """
    try:
        return templates.TemplateResponse("chat.html", {"request": request})
    except Exception as e:
        # If template not found or other template error
        traceback.print_exc()
        raise HTTPException(
            status_code=500,
            detail=f"Failed to render chat.html: {str(e)}",
        )


@app.post("/api/analyze")
async def analyze_endpoint(
    prompt: str = Form(...),                             # required
    api_key: str = Form(...),
    files: Optional[List[UploadFile]] = File(None),      # optional
):
    """
    Main analysis endpoint.

    Cases:
    - prompt only (no files)      β†’ general assistant mode
    - prompt + one/many files     β†’ file-analysis mode (uses file context)
    """
    try:
        # ---------- Basic validation ----------
        if api_key != API_KEY:
            raise HTTPException(status_code=401, detail="Invalid API key")

        if prompt is None:
            raise HTTPException(status_code=400, detail="Prompt is missing")
        if not prompt.strip():
            raise HTTPException(status_code=400, detail="Prompt cannot be empty")

        files = files or []

        # ---------- Branch by presence of files ----------
        if files:
            # ----- CASE 1: prompt + files -----
            try:
                context = await process_files(files)
            except Exception as extract_err:
                traceback.print_exc()
                raise HTTPException(
                    status_code=500,
                    detail=f"Error while processing uploaded files: {str(extract_err)}",
                )

            context_text = json.dumps(context, indent=2, ensure_ascii=False)
            combined_user_prompt = f"""
User question:
{prompt}

Below is structured information extracted from the user's uploaded files.
The extraction was done by automated tools.

Your task:
1. Answer the user's question as accurately as possible.
2. Use the context when it is relevant.
3. Highlight important patterns, risks, or opportunities.
4. If some information is missing or uncertain, say so honestly.

Context:
{context_text}
"""

            system_prompt = (
                "You are GAKR AI, a careful and honest analysis assistant that works with "
                "structured summaries of files (tables, PDFs, documents, images, audio, video, etc.). "
                "You never assume file contents beyond what the provided context states."
            )

        else:
            # ----- CASE 2: prompt only -----
            context = {"files": []}  # keep structure consistent
            combined_user_prompt = prompt

            system_prompt = (
                "You are GAKR AI, a helpful and honest general-purpose assistant. "
                "You answer questions, explain concepts, help with reasoning and coding, "
                "using your knowledge up to 2024. Be clear, concise, and direct."
            )

        # ---------- Generate with Phi‑3 ----------
        try:
            response_text = generate_response(
                user_prompt=combined_user_prompt,
                system_prompt=system_prompt,
                max_tokens=512,
                temperature=0.2,
            )
        except Exception as gen_err:
            traceback.print_exc()
            raise HTTPException(
                status_code=500,
                detail=f"Error during model generation: {str(gen_err)}",
            )

        return JSONResponse(
            {
                "response": response_text,
                "context": context,
                "status": "success",
            }
        )

    except HTTPException:
        # Let FastAPI handle HTTPException as-is
        raise
    except Exception as e:
        # Unexpected error: log full traceback and return 500
        traceback.print_exc()
        raise HTTPException(
            status_code=500,
            detail=f"Unexpected backend error: {str(e)}",
        )


@app.get("/health")
async def health_check():
    """
    Simple health check.
    """
    return {"status": "healthy", "model_loaded": True}


# ============================================================
# ENTRY POINT
# ============================================================

if __name__ == "__main__":
    print("\n" + "=" * 60)
    print("🌐 SERVER & CHAT LOCATION")
    print("=" * 60)
    print("πŸš€ CHAT INTERFACE:     http://localhost:8080")
    print("πŸ“± ALTERNATIVE URL:    http://127.0.0.1:8080")
    print("πŸ”§ API DOCUMENTATION:  http://localhost:8080/docs")
    print("βœ… CHAT.HTML SERVED:   templates/chat.html")
    print("πŸ“ TEMPLATES FOLDER:   ./templates/")
    print("=" * 60 + "\n")

    uvicorn.run(app, host="0.0.0.0", port=8080)