Spaces:
Sleeping
Sleeping
Gemini commited on
Commit ·
39dcb26
1
Parent(s): d078b16
feat: Add chat interface and versioned API
Browse files- Dockerfile +3 -1
- chat.html +21 -0
- main.py +47 -4
- static/script.js +47 -0
- static/style.css +68 -0
Dockerfile
CHANGED
|
@@ -16,10 +16,12 @@ RUN apt-get update && apt-get install -y python3 python3-pip curl
|
|
| 16 |
WORKDIR /app
|
| 17 |
COPY ./main.py /app/main.py
|
| 18 |
COPY ./start.sh /app/start.sh
|
|
|
|
|
|
|
| 19 |
RUN chmod +x /app/start.sh
|
| 20 |
|
| 21 |
# 3. Install Python dependencies for the FastAPI gateway
|
| 22 |
-
RUN pip3 install fastapi uvicorn requests
|
| 23 |
|
| 24 |
# Expose the port the FastAPI gateway will listen on
|
| 25 |
EXPOSE 7860
|
|
|
|
| 16 |
WORKDIR /app
|
| 17 |
COPY ./main.py /app/main.py
|
| 18 |
COPY ./start.sh /app/start.sh
|
| 19 |
+
COPY ./chat.html /app/chat.html
|
| 20 |
+
COPY ./static /app/static
|
| 21 |
RUN chmod +x /app/start.sh
|
| 22 |
|
| 23 |
# 3. Install Python dependencies for the FastAPI gateway
|
| 24 |
+
RUN pip3 install fastapi uvicorn requests --break-system-packages
|
| 25 |
|
| 26 |
# Expose the port the FastAPI gateway will listen on
|
| 27 |
EXPOSE 7860
|
chat.html
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Ollama Chat</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/style.css">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="chat-container">
|
| 11 |
+
<div id="chat-window">
|
| 12 |
+
<ul id="message-list"></ul>
|
| 13 |
+
</div>
|
| 14 |
+
<div id="input-container">
|
| 15 |
+
<input type="text" id="message-input" placeholder="Type your message...">
|
| 16 |
+
<button id="send-button">Send</button>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<script src="/static/script.js"></script>
|
| 20 |
+
</body>
|
| 21 |
+
</html>
|
main.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
import os
|
| 2 |
import requests
|
| 3 |
-
from fastapi import FastAPI, Request, HTTPException
|
| 4 |
-
from fastapi.responses import JSONResponse
|
|
|
|
| 5 |
|
| 6 |
# Get the secret API key from the environment variables (set in Space secrets)
|
| 7 |
# This is the key the user must provide to access the service.
|
|
@@ -12,8 +13,14 @@ OLLAMA_API_URL = "http://localhost:11434"
|
|
| 12 |
|
| 13 |
app = FastAPI()
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# 1. Check for the API Key
|
| 18 |
provided_key = request.headers.get("X-API-Key")
|
| 19 |
if not AUTH_KEY or provided_key != AUTH_KEY:
|
|
@@ -42,3 +49,39 @@ async def proxy(request: Request, path: str):
|
|
| 42 |
)
|
| 43 |
except Exception as e:
|
| 44 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import requests
|
| 3 |
+
from fastapi import FastAPI, Request, HTTPException, APIRouter
|
| 4 |
+
from fastapi.responses import JSONResponse, RedirectResponse, FileResponse
|
| 5 |
+
from fastapi.staticfiles import StaticFiles
|
| 6 |
|
| 7 |
# Get the secret API key from the environment variables (set in Space secrets)
|
| 8 |
# This is the key the user must provide to access the service.
|
|
|
|
| 13 |
|
| 14 |
app = FastAPI()
|
| 15 |
|
| 16 |
+
# Mount the static directory to serve CSS and JS files
|
| 17 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 18 |
+
|
| 19 |
+
# API v1 Router (secured)
|
| 20 |
+
api_v1 = APIRouter()
|
| 21 |
+
|
| 22 |
+
@api_v1.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
|
| 23 |
+
async def proxy_v1(request: Request, path: str):
|
| 24 |
# 1. Check for the API Key
|
| 25 |
provided_key = request.headers.get("X-API-Key")
|
| 26 |
if not AUTH_KEY or provided_key != AUTH_KEY:
|
|
|
|
| 49 |
)
|
| 50 |
except Exception as e:
|
| 51 |
raise HTTPException(status_code=500, detail=str(e))
|
| 52 |
+
|
| 53 |
+
app.include_router(api_v1, prefix="/api/v1")
|
| 54 |
+
|
| 55 |
+
# Chat API endpoint (not secured)
|
| 56 |
+
@app.post("/api/chat")
|
| 57 |
+
async def chat_endpoint(request: Request):
|
| 58 |
+
body = await request.json()
|
| 59 |
+
model = body.get("model", "llama3")
|
| 60 |
+
prompt = body.get("prompt")
|
| 61 |
+
|
| 62 |
+
if not prompt:
|
| 63 |
+
raise HTTPException(status_code=400, detail="Prompt is required")
|
| 64 |
+
|
| 65 |
+
url = f"{OLLAMA_API_URL}/api/generate"
|
| 66 |
+
|
| 67 |
+
try:
|
| 68 |
+
response = requests.post(
|
| 69 |
+
url=url,
|
| 70 |
+
json={"model": model, "prompt": prompt, "stream": False}
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
return JSONResponse(
|
| 74 |
+
status_code=response.status_code,
|
| 75 |
+
content=response.json()
|
| 76 |
+
)
|
| 77 |
+
except Exception as e:
|
| 78 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@app.get("/")
|
| 82 |
+
async def root():
|
| 83 |
+
return RedirectResponse(url="/chat")
|
| 84 |
+
|
| 85 |
+
@app.get("/chat")
|
| 86 |
+
async def chat_page():
|
| 87 |
+
return FileResponse('chat.html')
|
static/script.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const messageList = document.getElementById('message-list');
|
| 2 |
+
const messageInput = document.getElementById('message-input');
|
| 3 |
+
const sendButton = document.getElementById('send-button');
|
| 4 |
+
|
| 5 |
+
sendButton.addEventListener('click', sendMessage);
|
| 6 |
+
messageInput.addEventListener('keydown', (event) => {
|
| 7 |
+
if (event.key === 'Enter') {
|
| 8 |
+
sendMessage();
|
| 9 |
+
}
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
function sendMessage() {
|
| 13 |
+
const message = messageInput.value.trim();
|
| 14 |
+
if (message === '') {
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
appendMessage(message, 'user-message');
|
| 19 |
+
messageInput.value = '';
|
| 20 |
+
|
| 21 |
+
fetch('/api/chat', {
|
| 22 |
+
method: 'POST',
|
| 23 |
+
headers: {
|
| 24 |
+
'Content-Type': 'application/json'
|
| 25 |
+
},
|
| 26 |
+
body: JSON.stringify({
|
| 27 |
+
model: 'llama3', // You can change the model here
|
| 28 |
+
prompt: message
|
| 29 |
+
})
|
| 30 |
+
})
|
| 31 |
+
.then(response => response.json())
|
| 32 |
+
.then(data => {
|
| 33 |
+
appendMessage(data.response, 'bot-message');
|
| 34 |
+
})
|
| 35 |
+
.catch(error => {
|
| 36 |
+
console.error('Error:', error);
|
| 37 |
+
appendMessage('Sorry, something went wrong.', 'bot-message');
|
| 38 |
+
});
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function appendMessage(message, className) {
|
| 42 |
+
const li = document.createElement('li');
|
| 43 |
+
li.textContent = message;
|
| 44 |
+
li.classList.add(className);
|
| 45 |
+
messageList.appendChild(li);
|
| 46 |
+
messageList.scrollTop = messageList.scrollHeight;
|
| 47 |
+
}
|
static/style.css
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: sans-serif;
|
| 3 |
+
margin: 0;
|
| 4 |
+
background-color: #f0f0f0;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
#chat-container {
|
| 8 |
+
display: flex;
|
| 9 |
+
flex-direction: column;
|
| 10 |
+
height: 100vh;
|
| 11 |
+
max-width: 800px;
|
| 12 |
+
margin: 0 auto;
|
| 13 |
+
background-color: white;
|
| 14 |
+
border-left: 1px solid #ccc;
|
| 15 |
+
border-right: 1px solid #ccc;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
#chat-window {
|
| 19 |
+
flex-grow: 1;
|
| 20 |
+
overflow-y: auto;
|
| 21 |
+
padding: 20px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
#message-list {
|
| 25 |
+
list-style: none;
|
| 26 |
+
margin: 0;
|
| 27 |
+
padding: 0;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
#message-list li {
|
| 31 |
+
margin-bottom: 10px;
|
| 32 |
+
padding: 10px;
|
| 33 |
+
border-radius: 5px;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
#message-list .user-message {
|
| 37 |
+
background-color: #dcf8c6;
|
| 38 |
+
align-self: flex-end;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
#message-list .bot-message {
|
| 42 |
+
background-color: #f1f0f0;
|
| 43 |
+
align-self: flex-start;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
#input-container {
|
| 47 |
+
display: flex;
|
| 48 |
+
padding: 20px;
|
| 49 |
+
border-top: 1px solid #ccc;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
#message-input {
|
| 53 |
+
flex-grow: 1;
|
| 54 |
+
border: 1px solid #ccc;
|
| 55 |
+
border-radius: 5px;
|
| 56 |
+
padding: 10px;
|
| 57 |
+
font-size: 16px;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
#send-button {
|
| 61 |
+
background-color: #4CAF50;
|
| 62 |
+
color: white;
|
| 63 |
+
border: none;
|
| 64 |
+
padding: 10px 20px;
|
| 65 |
+
border-radius: 5px;
|
| 66 |
+
margin-left: 10px;
|
| 67 |
+
cursor: pointer;
|
| 68 |
+
}
|