File size: 12,115 Bytes
2f63d63
55f1bad
df227c8
 
 
 
 
 
e0c64bd
df227c8
2f63d63
 
0213467
df227c8
2f63d63
df227c8
 
2f63d63
df227c8
e0c64bd
2f63d63
 
e0c64bd
 
 
 
 
 
 
2f63d63
e0c64bd
 
acd95c4
df227c8
 
 
 
e0c64bd
 
df227c8
2f63d63
df227c8
 
 
 
 
 
 
 
 
 
2f63d63
df227c8
 
e0c64bd
 
2f63d63
e0c64bd
 
 
9e9daf3
 
 
 
 
 
 
 
 
2f63d63
e0c64bd
df227c8
 
9e9daf3
 
 
 
 
 
 
 
 
df227c8
 
2f63d63
e0c64bd
13bad0f
 
2f63d63
e0c64bd
df227c8
 
2f63d63
df227c8
d36d29e
 
df227c8
2f63d63
 
 
 
13bad0f
df227c8
2f63d63
 
 
e0c64bd
2f63d63
 
 
df227c8
9e9daf3
 
 
 
 
 
 
 
 
 
 
2f63d63
df227c8
 
2f63d63
 
 
e0c64bd
2f63d63
 
 
 
 
e0c64bd
2f63d63
df227c8
 
acd95c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f63d63
 
e42f902
2f63d63
 
 
 
acd95c4
2f63d63
 
 
 
 
 
 
 
 
 
 
df227c8
 
 
2f63d63
df227c8
2f63d63
e0c64bd
df227c8
 
 
e0c64bd
df227c8
 
9e9daf3
df227c8
 
e0c64bd
df227c8
 
 
9e9daf3
df227c8
 
 
2f63d63
e0c64bd
df227c8
 
 
 
 
 
 
2f63d63
e0c64bd
df227c8
 
 
e0c64bd
df227c8
 
 
 
 
2f63d63
df227c8
e0c64bd
df227c8
9e9daf3
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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
import httpx
import secrets
import os
from typing import List, Dict, Optional

# Initialize FastAPI app
app = FastAPI(title="AI Chat Application")
app.mount("/static", StaticFiles(directory="static"), name="static")

# Basic auth setup for admin
security = HTTPBasic()
ADMIN_USERNAME = os.environ.get("ADMIN_USERNAME", "admin")
ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "password")  # change in prod

def get_admin_user(credentials: HTTPBasicCredentials = Depends(security)):
    if not (secrets.compare_digest(credentials.username, ADMIN_USERNAME) and
            secrets.compare_digest(credentials.password, ADMIN_PASSWORD)):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username

# In-memory data stores
chat_history: Dict[str, List[Dict[str, str]]] = {}
rules: List[str] = [
    "You are Vetra, an office bot that answers based on given rules",
    "Be respectful and helpful",
    "Do not generate harmful content",
    "Provide accurate information"
]
available_models: List[str] = ["mistral-large-latest", "gemini", "openai-xlarge"]
current_model: str = available_models[0]

# Pydantic models
class ChatMessage(BaseModel):
    message: str
    chat_id: Optional[str] = None

class Rule(BaseModel):
    rule: str

class ModelRequest(BaseModel):
    model: str

# Serve index.html at root
@app.get("/", response_class=HTMLResponse)
async def read_root():
    try:
        with open("index.html", "r") as f:
            return HTMLResponse(content=f.read())
    except FileNotFoundError:
        raise HTTPException(status_code=404, detail="index.html not found")

# Helper function to create system message from rules
def create_system_message():
    """Create a system message by combining all active rules."""
    rules_text = "\n".join([f"- {rule}" for rule in rules])
    return {
        "role": "system", 
        "content": f"You are an AI assistant following these rules:\n{rules_text}"
    }

# Chat endpoint
@app.post("/api/chat", response_class=JSONResponse)
async def process_chat(chat_request: ChatMessage):
    chat_id = chat_request.chat_id or secrets.token_hex(8)
    
    # Initialize chat history with system message if it's a new chat
    if chat_id not in chat_history:
        chat_history[chat_id] = [create_system_message()]
    
    # Add user message to history
    chat_history[chat_id].append({"role": "user", "content": chat_request.message})

    # Check block rules
    for rule in rules:
        if rule.startswith("Block:") and rule[6:].lower() in chat_request.message.lower():
            return JSONResponse({"response": "I cannot respond due to content rules.", "chat_id": chat_id})

    # Always use the admin-selected model - no user choice
    api_request = {"model": current_model, "messages": chat_history[chat_id],
                   "temperature": 0.7, "max_tokens": 1000}

    try:
        async with httpx.AsyncClient(timeout=60.0) as client:
            resp = await client.post(
                "https://parthsadaria-lokiai.hf.space/chat/completions",
                json=api_request,
                headers={"Authorization": "sigma"}
            )
            data = resp.json()
            if choices := data.get("choices"):
                msg = choices[0]["message"]["content"]
                chat_history[chat_id].append({"role": "assistant", "content": msg})
                return {"response": msg, "chat_id": chat_id, "model": current_model}
            else:
                err = "No response from AI model"
                chat_history[chat_id].append({"role": "assistant", "content": err})
                return {"response": err, "chat_id": chat_id}
    except httpx.RequestError as e:
        err = f"Error communicating with AI service: {e}"
        chat_history[chat_id].append({"role": "assistant", "content": err})
        return {"response": err, "chat_id": chat_id}

# Update system message when rules change
def update_system_message_in_chats():
    """Update the system message in all active chats when rules change."""
    new_system_msg = create_system_message()
    for chat_id in chat_history:
        # Replace the first message if it's a system message, or insert at beginning
        if chat_history[chat_id] and chat_history[chat_id][0].get("role") == "system":
            chat_history[chat_id][0] = new_system_msg
        else:
            chat_history[chat_id].insert(0, new_system_msg)

# Admin panel HTML
@app.get("/admin", response_class=HTMLResponse)
async def admin_panel(username: str = Depends(get_admin_user)):
    opts = "".join(
        f'<option value="{m}"{" selected" if m==current_model else ""}>{m}</option>'
        for m in available_models
    )
    rl = "".join(f'<li>{r} <button onclick="deleteRule({i})">Delete</button></li>'
                   for i, r in enumerate(rules))
    ch = "".join(
        f'''<li><a href="#" onclick="viewChat('{cid}')">{cid}</a></li>'''
        for cid in chat_history
    )
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Admin Panel</title>
        <link rel="stylesheet" href="/static/style.css">
        <script>
            // Inline JavaScript for admin panel
            function updateModel() {{
                const model = document.getElementById('model-selector').value;
                fetch('/api/model', {{
                    method: 'POST',
                    headers: {{'Content-Type': 'application/json'}},
                    body: JSON.stringify({{ model: model }})
                }})
                .then(response => response.json())
                .then(data => {{
                    document.getElementById('current-model-display').textContent = data.model;
                    alert('Model updated successfully!');
                }})
                .catch(error => {{
                    console.error('Error updating model:', error);
                    alert('Error updating model: ' + error);
                }});
            }}

            function addRule() {{
                const ruleInput = document.getElementById('new-rule');
                const rule = ruleInput.value.trim();
                if (!rule) return;
                
                fetch('/api/rules', {{
                    method: 'POST',
                    headers: {{'Content-Type': 'application/json'}},
                    body: JSON.stringify({{ rule: rule }})
                }})
                .then(response => response.json())
                .then(data => {{
                    ruleInput.value = '';
                    refreshRules(data.rules);
                }})
                .catch(error => {{
                    console.error('Error adding rule:', error);
                    alert('Error adding rule: ' + error);
                }});
            }}

            function deleteRule(index) {{
                fetch(`/api/rules/${{index}}`, {{
                    method: 'DELETE'
                }})
                .then(response => response.json())
                .then(data => {{
                    refreshRules(data.rules);
                }})
                .catch(error => {{
                    console.error('Error deleting rule:', error);
                    alert('Error deleting rule: ' + error);
                }});
            }}

            function refreshRules(rules) {{
                const rulesList = document.getElementById('rules-list');
                rulesList.innerHTML = '';
                rules.forEach((rule, index) => {{
                    const li = document.createElement('li');
                    li.textContent = rule + ' ';
                    
                    const deleteBtn = document.createElement('button');
                    deleteBtn.textContent = 'Delete';
                    deleteBtn.onclick = function() {{ deleteRule(index); }};
                    
                    li.appendChild(deleteBtn);
                    rulesList.appendChild(li);
                }});
            }}

            function viewChat(chatId) {{
                fetch(`/api/chats/${{chatId}}`)
                .then(response => response.json())
                .then(data => {{
                    const chatViewer = document.getElementById('chat-viewer');
                    chatViewer.innerHTML = `<h3>Chat: ${{chatId}}</h3>`;
                    
                    const chatList = document.createElement('ul');
                    chatList.className = 'chat-messages';
                    
                    data.history.forEach(msg => {{
                        const li = document.createElement('li');
                        li.className = `message ${{msg.role}}`;
                        li.innerHTML = `<strong>${{msg.role}}:</strong> ${{msg.content}}`;
                        chatList.appendChild(li);
                    }});
                    
                    chatViewer.appendChild(chatList);
                }})
                .catch(error => {{
                    console.error('Error fetching chat:', error);
                    alert('Error fetching chat: ' + error);
                }});
            }}
        </script>
    </head>
    <body>
      <h1>Admin</h1>
      
      <div>
        <label>Model:</label>
        <select id="model-selector">{opts}</select>
        <button onclick="updateModel()">Update</button>
        <p>Current model: <strong id="current-model-display">{current_model}</strong> (Applies to all chats)</p>
      </div>
      <div>
        <h2>Rules</h2>
        <ul id="rules-list">{rl}</ul>
        <input id="new-rule" placeholder="New rule"><button onclick="addRule()">Add</button>
      </div>
      <div>
        <h2>Chats</h2>
        <ul id="active-chats">{ch}</ul>
        <div id="chat-viewer"></div>
      </div>
    </body>
    </html>
    """
    return HTMLResponse(content=html)

# Rule management
@app.get("/api/rules", response_class=JSONResponse)
async def get_rules(username: str = Depends(get_admin_user)):
    return {"rules": rules}

@app.post("/api/rules", response_class=JSONResponse)
async def add_rule(rule_request: Rule, username: str = Depends(get_admin_user)):
    rules.append(rule_request.rule)
    update_system_message_in_chats()  # Update system messages in all chats
    return {"status": "success", "rules": rules}

@app.delete("/api/rules/{rule_id}", response_class=JSONResponse)
async def delete_rule(rule_id: int, username: str = Depends(get_admin_user)):
    if 0 <= rule_id < len(rules):
        rules.pop(rule_id)
        update_system_message_in_chats()  # Update system messages in all chats
        return {"status": "success", "rules": rules}
    raise HTTPException(status_code=404, detail="Rule not found")

# Model endpoint
@app.post("/api/model", response_class=JSONResponse)
async def set_model(model_request: ModelRequest, username: str = Depends(get_admin_user)):
    global current_model
    if model_request.model in available_models:
        current_model = model_request.model
        return {"status": "success", "model": current_model}
    raise HTTPException(status_code=400, detail="Invalid model")

# Chat retrieval
@app.get("/api/chats", response_class=JSONResponse)
async def get_chats(username: str = Depends(get_admin_user)):
    return {"chats": list(chat_history.keys())}

@app.get("/api/chats/{chat_id}", response_class=JSONResponse)
async def get_chat_history(chat_id: str, username: str = Depends(get_admin_user)):
    if chat_id in chat_history:
        return {"history": chat_history[chat_id]}
    raise HTTPException(status_code=404, detail="Chat not found")

# Debug run
if __name__ == "__main__":
    port = int(os.environ.get("PORT", 7860))
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=port)