eli02 commited on
Commit
546720a
·
1 Parent(s): 59829e0

Add FastAPI application with WebSocket support and authentication

Browse files
Files changed (14) hide show
  1. .gitignore +1 -0
  2. Dockerfile +27 -0
  3. auth.py +63 -0
  4. docs/diagrams/system.md +28 -0
  5. docs/diagrams/system.pdf +0 -0
  6. gen.py +13 -0
  7. main.py +44 -0
  8. middleware.py +18 -0
  9. requirements.txt +27 -0
  10. schemas.py +49 -0
  11. static/index.html +666 -0
  12. static/script.js +44 -0
  13. static/style.css +172 -0
  14. utils.py +0 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .venv
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python 3.9 image
2
+ FROM python:3.9
3
+
4
+ # Set the working directory to /code
5
+ WORKDIR /code
6
+
7
+ # Copy the current directory contents into the container at /code
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ # Install requirements.txt
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+
13
+ # Set up a new user named "user" with user ID 1000
14
+ RUN useradd -m -u 1000 user
15
+ # Switch to the "user" user
16
+ USER user
17
+ # Set home to the user's home directory
18
+ ENV HOME=/home/user \
19
+ PATH=/home/user/.local/bin:$PATH
20
+
21
+ # Set the working directory to the user's home directory
22
+ WORKDIR $HOME/app
23
+
24
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
25
+ COPY --chown=user . $HOME/app
26
+
27
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
auth.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Depends, HTTPException, status
2
+ from fastapi.security import OAuth2PasswordBearer
3
+ from jose import JWTError, jwt
4
+ from passlib.context import CryptContext
5
+ from datetime import datetime, timedelta
6
+ import os
7
+
8
+ # Load secrets from environment variables
9
+ SECRET_KEY = os.getenv("SECRET_KEY")
10
+ ALGORITHM = "HS512"
11
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
12
+
13
+ # Password hashing
14
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
15
+
16
+ # OAuth2 scheme
17
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
18
+
19
+ # Mock user database (replace with a real database in production)
20
+ fake_users_db = {
21
+ "admin": {
22
+ "username": "admin",
23
+ "hashed_password": pwd_context.hash(os.getenv("ADMIN_PASSWORD")),
24
+ }
25
+ }
26
+
27
+ def verify_password(plain_password, hashed_password):
28
+ return pwd_context.verify(plain_password, hashed_password)
29
+
30
+ def authenticate_user(username: str, password: str):
31
+ user = fake_users_db.get(username)
32
+ if not user or not verify_password(password, user["hashed_password"]):
33
+ return False
34
+ return user
35
+
36
+ def create_access_token(data: dict, expires_delta: timedelta = None):
37
+ to_encode = data.copy()
38
+ if expires_delta:
39
+ expire = datetime.utcnow() + expires_delta
40
+ else:
41
+ expire = datetime.utcnow() + timedelta(minutes=15)
42
+ to_encode.update({"exp": expire})
43
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
44
+ return encoded_jwt
45
+
46
+ async def get_current_user(token: str = Depends(oauth2_scheme)):
47
+ credentials_exception = HTTPException(
48
+ status_code=status.HTTP_401_UNAUTHORIZED,
49
+ detail="Could not validate credentials",
50
+ headers={"WWW-Authenticate": "Bearer"},
51
+ )
52
+ try:
53
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
54
+ username: str = payload.get("sub")
55
+ if username is None:
56
+ raise credentials_exception
57
+ except JWTError:
58
+ raise credentials_exception
59
+
60
+ user = fake_users_db.get(username)
61
+ if user is None:
62
+ raise credentials_exception
63
+ return user
docs/diagrams/system.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```mermaid
2
+ graph TD
3
+ A[Client] -->|HTTP/WebSocket| B[FastAPI Server]
4
+ B -->|Authentication| C[Auth Module]
5
+ B -->|Text Generation| D[Generation Module]
6
+ B -->|Rate Limiting| E[Rate Limiter]
7
+ B -->|Logging| F[Logging Middleware]
8
+ C -->|JWT Token| G[Secrets Manager]
9
+ D -->|Model Inference| H[Hugging Face Model]
10
+ E -->|Rate Limit Check| I[Redis/Database]
11
+ F -->|Logs| J[Log Storage]
12
+ H -->|Streaming| A
13
+
14
+ subgraph FastAPI Server
15
+ B
16
+ C
17
+ D
18
+ E
19
+ F
20
+ end
21
+
22
+ subgraph External Services
23
+ G[Secrets Manager]
24
+ H[Hugging Face Model]
25
+ I[Redis/Database]
26
+ J[Log Storage]
27
+ end
28
+ ```
docs/diagrams/system.pdf ADDED
Binary file (20.6 kB). View file
 
gen.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import WebSocket
2
+ from transformers import pipeline
3
+ import asyncio
4
+
5
+ # Load the model
6
+ model_name = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
7
+ generator = pipeline("text-generation", model=model_name)
8
+
9
+ async def generate_text_stream(prompt: str, websocket: WebSocket):
10
+ for i in range(10): # Simulate streaming (replace with actual model inference)
11
+ chunk = generator(prompt, max_length=i + 10, do_sample=True)[0]["generated_text"]
12
+ await websocket.send_text(chunk)
13
+ await asyncio.sleep(0.1) # Simulate delay
main.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, WebSocket, Depends, HTTPException
2
+ from auth import get_current_user, authenticate_user, create_access_token
3
+ from gen import generate_text_stream
4
+ from fastapi.security import OAuth2PasswordRequestForm
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from middleware import setup_rate_limiter
7
+ import os
8
+
9
+ app = FastAPI()
10
+
11
+
12
+ # Apply rate limiting middleware
13
+ setup_rate_limiter(app)
14
+
15
+ # CORS middleware
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_credentials=True,
20
+ allow_methods=["*"],
21
+ allow_headers=["*"],
22
+ )
23
+
24
+ # Login endpoint
25
+ @app.post("/token")
26
+ async def login(form_data: OAuth2PasswordRequestForm = Depends()):
27
+ user = authenticate_user(form_data.username, form_data.password)
28
+ if not user:
29
+ raise HTTPException(status_code=400, detail="Incorrect username or password")
30
+ access_token = create_access_token(data={"sub": user["username"]})
31
+ return {"access_token": access_token, "token_type": "bearer"}
32
+
33
+ # WebSocket endpoint for streaming
34
+ @app.websocket("/generate")
35
+ async def websocket_generate(websocket: WebSocket, token: str):
36
+ await websocket.accept()
37
+ try:
38
+ user = get_current_user(token)
39
+ prompt = await websocket.receive_text()
40
+ await generate_text_stream(prompt, websocket)
41
+ except Exception as e:
42
+ await websocket.send_text(f"Error: {str(e)}")
43
+ finally:
44
+ await websocket.close()
middleware.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Request
2
+ from slowapi import Limiter
3
+ from slowapi.util import get_remote_address
4
+ from slowapi.middleware import SlowAPIMiddleware
5
+
6
+
7
+ limiter = Limiter(key_func=get_remote_address)
8
+
9
+ async def log_requests(request: Request, call_next):
10
+ print(f"Request: {request.method} {request.url}")
11
+ response = await call_next(request)
12
+ print(f"Response: {response.status_code}")
13
+ return response
14
+
15
+ # Apply the rate limiter middleware
16
+ def setup_rate_limiter(app):
17
+ app.state.limiter = limiter
18
+ app.add_middleware(SlowAPIMiddleware)
requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastAPI and ASGI server
2
+ fastapi
3
+ uvicorn
4
+
5
+ # Hugging Face Transformers for LLM inference
6
+ transformers
7
+ torch # or tensorflow if preferred
8
+
9
+ # Authentication and security
10
+ python-jose[cryptography] # JWT support
11
+ passlib # Password hashing
12
+
13
+ # Rate limiting
14
+ slowapi
15
+
16
+ # WebSocket support
17
+ websockets
18
+
19
+ # Logging and middleware
20
+ loguru # Optional: for better logging
21
+
22
+ # Testing (optional for development)
23
+ pytest
24
+ httpx # For async HTTP requests in tests
25
+
26
+ # Docker and deployment
27
+ gunicorn # Optional: for production deployment
schemas.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr
2
+
3
+ # Authentication
4
+ class Token(BaseModel):
5
+ access_token: str
6
+ token_type: str
7
+
8
+ class TokenData(BaseModel):
9
+ username: str | None = None
10
+
11
+ class User(BaseModel):
12
+ username: str
13
+ email: EmailStr | None = None
14
+
15
+ class UserInDB(User):
16
+ hashed_password: str
17
+
18
+ class LoginRequest(BaseModel):
19
+ username: str
20
+ password: str
21
+
22
+ # Generation
23
+ class GenerationRequest(BaseModel):
24
+ prompt: str
25
+ max_length: int = 100
26
+ temperature: float = 0.7
27
+ top_k: int = 50
28
+ top_p: float = 0.95
29
+
30
+ class GenerationResponse(BaseModel):
31
+ generated_text: str
32
+
33
+ # WebSocket
34
+ class WebSocketMessage(BaseModel):
35
+ prompt: str
36
+
37
+ # Error Handling
38
+ class HTTPError(BaseModel):
39
+ detail: str
40
+
41
+ class Config:
42
+ schema_extra = {
43
+ "example": {"detail": "Error message"},
44
+ }
45
+
46
+ # Rate Limiting
47
+ class RateLimitResponse(BaseModel):
48
+ message: str
49
+ retry_after: int
static/index.html ADDED
@@ -0,0 +1,666 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>LLM Host API Documentation</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
8
+ <link href="/home/style.css" rel="stylesheet" />
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <nav class="sidebar">
13
+ <div class="logo">
14
+ 🔗 LLM Host API
15
+ </div>
16
+ <div class="nav-items">
17
+ <!-- Navigation items will be populated by JavaScript -->
18
+ </div>
19
+ </nav>
20
+ <main class="content">
21
+ <h1>LLM Host API</h1>
22
+
23
+ <section id="endpoints">
24
+ <h2>Endpoints</h2>
25
+
26
+ <div class="endpoint" id="login">
27
+ <h3>1. Login Endpoint</h3>
28
+ <p><span class="method">POST</span><span class="endpoint-url">/login</span></p>
29
+ <p>This endpoint is used to authenticate the user and issue an access token and a refresh token.</p>
30
+
31
+ <h4>cURL Request:</h4>
32
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/login \
33
+ -H "Content-Type: application/x-www-form-urlencoded" \
34
+ -d 'username=user123&password=password123'</code></pre>
35
+
36
+ <h4>Flutter Implementation:</h4>
37
+ <pre><code class="language-dart">Future<LoginResponse> login(String username, String password) async {
38
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/login');
39
+
40
+ try {
41
+ final response = await http.post(
42
+ url,
43
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
44
+ body: {
45
+ 'username': username,
46
+ 'password': password,
47
+ },
48
+ );
49
+
50
+ if (response.statusCode == 200) {
51
+ return LoginResponse.fromJson(jsonDecode(response.body));
52
+ } else {
53
+ throw Exception('Failed to login: ${response.body}');
54
+ }
55
+ } catch (e) {
56
+ throw Exception('Network error: $e');
57
+ }
58
+ }
59
+
60
+ // Model class
61
+ class LoginResponse {
62
+ final String accessToken;
63
+ final String refreshToken;
64
+ final String tokenType;
65
+ final int expiresIn;
66
+
67
+ LoginResponse({
68
+ required this.accessToken,
69
+ required this.refreshToken,
70
+ required this.tokenType,
71
+ required this.expiresIn,
72
+ });
73
+
74
+ factory LoginResponse.fromJson(Map<String, dynamic> json) {
75
+ return LoginResponse(
76
+ accessToken: json['access_token'],
77
+ refreshToken: json['refresh_token'],
78
+ tokenType: json['token_type'],
79
+ expiresIn: json['expires_in'],
80
+ );
81
+ }
82
+ }</code></pre>
83
+
84
+ <h4>Response:</h4>
85
+ <pre><code class="language-json">{
86
+ "access_token": "your-access-token",
87
+ "refresh_token": "your-refresh-token",
88
+ "token_type": "bearer",
89
+ "expires_in": 1800
90
+ }</code></pre>
91
+ </div>
92
+
93
+ <div class="endpoint" id="refresh">
94
+ <h3>2. Refresh Token Endpoint</h3>
95
+ <p><span class="method">POST</span><span class="endpoint-url">/refresh</span></p>
96
+ <p>This endpoint is used to refresh the access token using a valid refresh token.</p>
97
+
98
+ <h4>cURL Request:</h4>
99
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/refresh \
100
+ -H "Content-Type: application/json" \
101
+ -d '{"refresh_token": "your-refresh-token"}'</code></pre>
102
+
103
+ <h4>Flutter Implementation:</h4>
104
+ <pre><code class="language-dart">Future<LoginResponse> refreshToken(String refreshToken) async {
105
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/refresh');
106
+
107
+ try {
108
+ final response = await http.post(
109
+ url,
110
+ headers: {'Content-Type': 'application/json'},
111
+ body: jsonEncode({
112
+ 'refresh_token': refreshToken,
113
+ }),
114
+ );
115
+
116
+ if (response.statusCode == 200) {
117
+ return LoginResponse.fromJson(jsonDecode(response.body));
118
+ } else {
119
+ throw Exception('Failed to refresh token: ${response.body}');
120
+ }
121
+ } catch (e) {
122
+ throw Exception('Network error: $e');
123
+ }
124
+ }</code></pre>
125
+
126
+ <h4>Response:</h4>
127
+ <pre><code class="language-json">{
128
+ "access_token": "new-access-token",
129
+ "refresh_token": "your-refresh-token",
130
+ "token_type": "bearer",
131
+ "expires_in": 1800
132
+ }</code></pre>
133
+ </div>
134
+
135
+ <div class="endpoint" id="search">
136
+ <h3>3. Search Endpoint</h3>
137
+ <p><span class="method">POST</span><span class="endpoint-url">/search</span></p>
138
+ <p>This endpoint is used to send a search query and retrieve results. It requires a valid access token.</p>
139
+
140
+ <h4>cURL Request:</h4>
141
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/search \
142
+ -H "Content-Type: application/json" \
143
+ -H "Authorization: Bearer your-access-token" \
144
+ -d '{"query": "test query"}'</code></pre>
145
+
146
+ <h4>Flutter Implementation:</h4>
147
+ <pre><code class="language-dart">class SearchResult {
148
+ final String text;
149
+ final double similarity;
150
+ final String modelType;
151
+
152
+ SearchResult({
153
+ required this.text,
154
+ required this.similarity,
155
+ required this.modelType,
156
+ });
157
+
158
+ factory SearchResult.fromJson(Map<String, dynamic> json) {
159
+ return SearchResult(
160
+ text: json['text'],
161
+ similarity: json['similarity'].toDouble(),
162
+ modelType: json['model_type'],
163
+ );
164
+ }
165
+ }
166
+
167
+ Future<List<SearchResult>> search(String query, String accessToken) async {
168
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/search');
169
+
170
+ try {
171
+ final response = await http.post(
172
+ url,
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ 'Authorization': 'Bearer $accessToken',
176
+ },
177
+ body: jsonEncode({
178
+ 'query': query,
179
+ }),
180
+ );
181
+
182
+ if (response.statusCode == 200) {
183
+ List<dynamic> jsonList = jsonDecode(response.body);
184
+ return jsonList.map((json) => SearchResult.fromJson(json)).toList();
185
+ } else {
186
+ throw Exception('Search failed: ${response.body}');
187
+ }
188
+ } catch (e) {
189
+ throw Exception('Network error: $e');
190
+ }
191
+ }</code></pre>
192
+
193
+ <h4>Response:</h4>
194
+ <pre><code class="language-json">[
195
+ {
196
+ "text": "Result 1 text",
197
+ "similarity": 0.95,
198
+ "model_type": "all-mpnet-base-v2"
199
+ },
200
+ {
201
+ "text": "Result 2 text",
202
+ "similarity": 0.92,
203
+ "model_type": "openai"
204
+ }
205
+ ]</code></pre>
206
+ </div>
207
+
208
+ <div class="endpoint" id="save">
209
+ <h3>4. Save Data Endpoint</h3>
210
+ <p><span class="method">POST</span><span class="endpoint-url">/save</span></p>
211
+ <p>This endpoint is used to save user feedback and search results to the Hugging Face dataset. It requires a valid access token.</p>
212
+
213
+ <h4>cURL Request:</h4>
214
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/save \
215
+ -H "Content-Type: application/json" \
216
+ -H "Authorization: Bearer your-access-token" \
217
+ -d '{
218
+ "items": [
219
+ {
220
+ "user_type": "user",
221
+ "username": "user123",
222
+ "query": "test query",
223
+ "retrieved_text": "Result 1 text",
224
+ "model_type": "all-mpnet-base-v2",
225
+ "reaction": "positive"
226
+ }
227
+ ]
228
+ }'</code></pre>
229
+
230
+ <h4>Flutter Implementation:</h4>
231
+ <pre><code class="language-dart">class SaveInput {
232
+ final String userType;
233
+ final String username;
234
+ final String query;
235
+ final String retrievedText;
236
+ final String modelType;
237
+ final String reaction;
238
+
239
+ SaveInput({
240
+ required this.userType,
241
+ required this.username,
242
+ required this.query,
243
+ required this.retrievedText,
244
+ required this.modelType,
245
+ required this.reaction,
246
+ });
247
+
248
+ Map<String, dynamic> toJson() {
249
+ return {
250
+ 'user_type': userType,
251
+ 'username': username,
252
+ 'query': query,
253
+ 'retrieved_text': retrievedText,
254
+ 'model_type': modelType,
255
+ 'reaction': reaction,
256
+ };
257
+ }
258
+ }
259
+
260
+ Future<void> saveData(List<SaveInput> items, String accessToken) async {
261
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/save');
262
+
263
+ try {
264
+ final response = await http.post(
265
+ url,
266
+ headers: {
267
+ 'Content-Type': 'application/json',
268
+ 'Authorization': 'Bearer $accessToken',
269
+ },
270
+ body: jsonEncode({
271
+ 'items': items.map((item) => item.toJson()).toList(),
272
+ }),
273
+ );
274
+
275
+ if (response.statusCode != 200) {
276
+ throw Exception('Failed to save data: ${response.body}');
277
+ }
278
+ } catch (e) {
279
+ throw Exception('Network error: $e');
280
+ }
281
+ }</code></pre>
282
+
283
+ <h4>Response:</h4>
284
+ <pre><code class="language-json">{
285
+ "message": "Data saved successfully"
286
+ }</code></pre>
287
+ </div>
288
+ </section>
289
+
290
+ <section id="workflow">
291
+ <h2>Workflow Example</h2>
292
+ <p>Here's a complete workflow demonstrating how to use the API:</p>
293
+
294
+ <h3>cURL Implementation</h3>
295
+ <div class="endpoint">
296
+ <h3>Step 1: Login</h3>
297
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/login \
298
+ -H "Content-Type: application/x-www-form-urlencoded" \
299
+ -d 'username=user123&password=password123'</code></pre>
300
+ </div>
301
+
302
+ <div class="endpoint">
303
+ <h3>Step 2: Search</h3>
304
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/search \
305
+ -H "Content-Type: application/json" \
306
+ -H "Authorization: Bearer your-access-token" \
307
+ -d '{"query": "test query"}'</code></pre>
308
+ </div>
309
+
310
+ <div class="endpoint">
311
+ <h3>Step 3: Save Data</h3>
312
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/save \
313
+ -H "Content-Type: application/json" \
314
+ -H "Authorization: Bearer your-access-token" \
315
+ -d '{
316
+ "items": [
317
+ {
318
+ "user_type": "user",
319
+ "username": "user123",
320
+ "query": "test query",
321
+ "retrieved_text": "Result 1 text",
322
+ "model_type": "all-mpnet-base-v2",
323
+ "reaction": "positive"
324
+ }
325
+ ]
326
+ }'</code></pre>
327
+ </div>
328
+
329
+ <div class="endpoint">
330
+ <h3>Step 4: Refresh Token</h3>
331
+ <pre><code class="language-bash">curl -X POST https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/refresh \
332
+ -H "Content-Type: application/json" \
333
+ -d '{"refresh_token": "your-refresh-token"}'</code></pre>
334
+ </div>
335
+
336
+ <h3>Flutter Implementation</h3>
337
+ <div class="endpoint">
338
+ <h4>Complete Flutter Example</h4>
339
+ <pre><code class="language-dart">import 'dart:convert';
340
+ import 'package:flutter/material.dart';
341
+ import 'package:http/http.dart' as http;
342
+
343
+ void main() {
344
+ runApp(MyApp());
345
+ }
346
+
347
+ class MyApp extends StatelessWidget {
348
+ @override
349
+ Widget build(BuildContext context) {
350
+ return MaterialApp(
351
+ title: 'Al Ghazali RAG API Example',
352
+ home: ApiWorkflowExample(),
353
+ );
354
+ }
355
+ }
356
+
357
+ class ApiWorkflowExample extends StatefulWidget {
358
+ @override
359
+ _ApiWorkflowExampleState createState() => _ApiWorkflowExampleState();
360
+ }
361
+
362
+ class _ApiWorkflowExampleState extends State<ApiWorkflowExample> {
363
+ String _accessToken = '';
364
+ String _refreshToken = '';
365
+ List<SearchResult> _searchResults = [];
366
+
367
+ Future<void> _login() async {
368
+ final username = 'user123';
369
+ final password = 'password123';
370
+
371
+ try {
372
+ final loginResponse = await login(username, password);
373
+ setState(() {
374
+ _accessToken = loginResponse.accessToken;
375
+ _refreshToken = loginResponse.refreshToken;
376
+ });
377
+ ScaffoldMessenger.of(context).showSnackBar(
378
+ SnackBar(content: Text('Login successful!')),
379
+ );
380
+ } catch (e) {
381
+ ScaffoldMessenger.of(context).showSnackBar(
382
+ SnackBar(content: Text('Login failed: $e')),
383
+ );
384
+ }
385
+ }
386
+
387
+ Future<void> _search() async {
388
+ final query = 'test query';
389
+
390
+ try {
391
+ final results = await search(query, _accessToken);
392
+ setState(() {
393
+ _searchResults = results;
394
+ });
395
+ ScaffoldMessenger.of(context).showSnackBar(
396
+ SnackBar(content: Text('Search successful!')),
397
+ );
398
+ } catch (e) {
399
+ ScaffoldMessenger.of(context).showSnackBar(
400
+ SnackBar(content: Text('Search failed: $e')),
401
+ );
402
+ }
403
+ }
404
+
405
+ Future<void> _saveData() async {
406
+ final items = [
407
+ SaveInput(
408
+ userType: 'user',
409
+ username: 'user123',
410
+ query: 'test query',
411
+ retrievedText: _searchResults[0].text,
412
+ modelType: _searchResults[0].modelType,
413
+ reaction: 'positive',
414
+ ),
415
+ ];
416
+
417
+ try {
418
+ await saveData(items, _accessToken);
419
+ ScaffoldMessenger.of(context).showSnackBar(
420
+ SnackBar(content: Text('Data saved successfully!')),
421
+ );
422
+ } catch (e) {
423
+ ScaffoldMessenger.of(context).showSnackBar(
424
+ SnackBar(content: Text('Failed to save data: $e')),
425
+ );
426
+ }
427
+ }
428
+
429
+ Future<void> _refreshToken() async {
430
+ try {
431
+ final loginResponse = await refreshToken(_refreshToken);
432
+ setState(() {
433
+ _accessToken = loginResponse.accessToken;
434
+ });
435
+ ScaffoldMessenger.of(context).showSnackBar(
436
+ SnackBar(content: Text('Token refreshed successfully!')),
437
+ );
438
+ } catch (e) {
439
+ ScaffoldMessenger.of(context).showSnackBar(
440
+ SnackBar(content: Text('Token refresh failed: $e')),
441
+ );
442
+ }
443
+ }
444
+
445
+ @override
446
+ Widget build(BuildContext context) {
447
+ return Scaffold(
448
+ appBar: AppBar(
449
+ title: Text('Al Ghazali RAG API Workflow'),
450
+ ),
451
+ body: Padding(
452
+ padding: const EdgeInsets.all(16.0),
453
+ child: Column(
454
+ crossAxisAlignment: CrossAxisAlignment.stretch,
455
+ children: [
456
+ ElevatedButton(
457
+ onPressed: _login,
458
+ child: Text('Login'),
459
+ ),
460
+ ElevatedButton(
461
+ onPressed: _search,
462
+ child: Text('Search'),
463
+ ),
464
+ ElevatedButton(
465
+ onPressed: _saveData,
466
+ child: Text('Save Data'),
467
+ ),
468
+ ElevatedButton(
469
+ onPressed: _refreshToken,
470
+ child: Text('Refresh Token'),
471
+ ),
472
+ Expanded(
473
+ child: ListView.builder(
474
+ itemCount: _searchResults.length,
475
+ itemBuilder: (context, index) {
476
+ final result = _searchResults[index];
477
+ return ListTile(
478
+ title: Text(result.text),
479
+ subtitle: Text('Similarity: ${result.similarity}, Model: ${result.modelType}'),
480
+ );
481
+ },
482
+ ),
483
+ ),
484
+ ],
485
+ ),
486
+ ),
487
+ );
488
+ }
489
+ }
490
+
491
+ Future<LoginResponse> login(String username, String password) async {
492
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/login');
493
+
494
+ try {
495
+ final response = await http.post(
496
+ url,
497
+ headers: {'Content-Type': 'application/x-www-form-urlencoded'},
498
+ body: {
499
+ 'username': username,
500
+ 'password': password,
501
+ },
502
+ );
503
+
504
+ if (response.statusCode == 200) {
505
+ return LoginResponse.fromJson(jsonDecode(response.body));
506
+ } else {
507
+ throw Exception('Failed to login: ${response.body}');
508
+ }
509
+ } catch (e) {
510
+ throw Exception('Network error: $e');
511
+ }
512
+ }
513
+
514
+ Future<LoginResponse> refreshToken(String refreshToken) async {
515
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/refresh');
516
+
517
+ try {
518
+ final response = await http.post(
519
+ url,
520
+ headers: {'Content-Type': 'application/json'},
521
+ body: jsonEncode({
522
+ 'refresh_token': refreshToken,
523
+ }),
524
+ );
525
+
526
+ if (response.statusCode == 200) {
527
+ return LoginResponse.fromJson(jsonDecode(response.body));
528
+ } else {
529
+ throw Exception('Failed to refresh token: ${response.body}');
530
+ }
531
+ } catch (e) {
532
+ throw Exception('Network error: $e');
533
+ }
534
+ }
535
+
536
+ Future<List<SearchResult>> search(String query, String accessToken) async {
537
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/search');
538
+
539
+ try {
540
+ final response = await http.post(
541
+ url,
542
+ headers: {
543
+ 'Content-Type': 'application/json',
544
+ 'Authorization': 'Bearer $accessToken',
545
+ },
546
+ body: jsonEncode({
547
+ 'query': query,
548
+ }),
549
+ );
550
+
551
+ if (response.statusCode == 200) {
552
+ List<dynamic> jsonList = jsonDecode(response.body);
553
+ return jsonList.map((json) => SearchResult.fromJson(json)).toList();
554
+ } else {
555
+ throw Exception('Search failed: ${response.body}');
556
+ }
557
+ } catch (e) {
558
+ throw Exception('Network error: $e');
559
+ }
560
+ }
561
+
562
+ Future<void> saveData(List<SaveInput> items, String accessToken) async {
563
+ final url = Uri.parse('https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/save');
564
+
565
+ try {
566
+ final response = await http.post(
567
+ url,
568
+ headers: {
569
+ 'Content-Type': 'application/json',
570
+ 'Authorization': 'Bearer $accessToken',
571
+ },
572
+ body: jsonEncode({
573
+ 'items': items.map((item) => item.toJson()).toList(),
574
+ }),
575
+ );
576
+
577
+ if (response.statusCode != 200) {
578
+ throw Exception('Failed to save data: ${response.body}');
579
+ }
580
+ } catch (e) {
581
+ throw Exception('Network error: $e');
582
+ }
583
+ }
584
+
585
+ class LoginResponse {
586
+ final String accessToken;
587
+ final String refreshToken;
588
+ final String tokenType;
589
+ final int expiresIn;
590
+
591
+ LoginResponse({
592
+ required this.accessToken,
593
+ required this.refreshToken,
594
+ required this.tokenType,
595
+ required this.expiresIn,
596
+ });
597
+
598
+ factory LoginResponse.fromJson(Map<String, dynamic> json) {
599
+ return LoginResponse(
600
+ accessToken: json['access_token'],
601
+ refreshToken: json['refresh_token'],
602
+ tokenType: json['token_type'],
603
+ expiresIn: json['expires_in'],
604
+ );
605
+ }
606
+ }
607
+
608
+ class SearchResult {
609
+ final String text;
610
+ final double similarity;
611
+ final String modelType;
612
+
613
+ SearchResult({
614
+ required this.text,
615
+ required this.similarity,
616
+ required this.modelType,
617
+ });
618
+
619
+ factory SearchResult.fromJson(Map<String, dynamic> json) {
620
+ return SearchResult(
621
+ text: json['text'],
622
+ similarity: json['similarity'].toDouble(),
623
+ modelType: json['model_type'],
624
+ );
625
+ }
626
+ }
627
+
628
+ class SaveInput {
629
+ final String userType;
630
+ final String username;
631
+ final String query;
632
+ final String retrievedText;
633
+ final String modelType;
634
+ final String reaction;
635
+
636
+ SaveInput({
637
+ required this.userType,
638
+ required this.username,
639
+ required this.query,
640
+ required this.retrievedText,
641
+ required this.modelType,
642
+ required this.reaction,
643
+ });
644
+
645
+ Map<String, dynamic> toJson() {
646
+ return {
647
+ 'user_type': userType,
648
+ 'username': username,
649
+ 'query': query,
650
+ 'retrieved_text': retrievedText,
651
+ 'model_type': modelType,
652
+ 'reaction': reaction,
653
+ };
654
+ }
655
+ }</code></pre>
656
+ </div>
657
+ </section>
658
+ </main>
659
+ </div>
660
+
661
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/prism.min.js"></script>
662
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/components/prism-bash.min.js"></script>
663
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prismjs/1.29.0/components/prism-json.min.js"></script>
664
+ <script src="/home/script.js"></script>
665
+ </body>
666
+ </html>
static/script.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Generate navigation items
2
+ const sections = [
3
+ { id: 'endpoints', title: 'Endpoints' },
4
+ { id: 'login', title: 'Login' },
5
+ { id: 'refresh', title: 'Refresh Token' },
6
+ { id: 'search', title: 'Search' },
7
+ { id: 'save', title: 'Save'},
8
+ { id: 'workflow', title: 'Workflow Example' }
9
+ ];
10
+
11
+ const navItems = document.querySelector('.nav-items');
12
+ sections.forEach(section => {
13
+ const item = document.createElement('div');
14
+ item.className = 'nav-item';
15
+ item.textContent = section.title;
16
+ item.setAttribute('data-section', section.id);
17
+ item.addEventListener('click', () => {
18
+ document.getElementById(section.id).scrollIntoView({ behavior: 'smooth' });
19
+ });
20
+ navItems.appendChild(item);
21
+ });
22
+
23
+ // Highlight current section in navigation
24
+ const observerCallback = (entries) => {
25
+ entries.forEach(entry => {
26
+ if (entry.isIntersecting) {
27
+ const currentNav = document.querySelector(`.nav-item[data-section="${entry.target.id}"]`);
28
+ if (currentNav) {
29
+ document.querySelectorAll('.nav-item').forEach(item => {
30
+ item.style.backgroundColor = '';
31
+ });
32
+ currentNav.style.backgroundColor = '#f3f4f6';
33
+ }
34
+ }
35
+ });
36
+ };
37
+
38
+ const observer = new IntersectionObserver(observerCallback, {
39
+ threshold: 0.2
40
+ });
41
+
42
+ document.querySelectorAll('section').forEach(section => {
43
+ observer.observe(section);
44
+ });
static/style.css ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-color: #4f46e5;
3
+ --secondary-color: #1e40af;
4
+ --text-color: #1f2937;
5
+ --code-bg: #1e1e1e;
6
+ --border-color: #e5e7eb;
7
+ }
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
17
+ line-height: 1.6;
18
+ color: var(--text-color);
19
+ background-color: #f9fafb;
20
+ }
21
+
22
+ .container {
23
+ display: flex;
24
+ min-height: 100vh;
25
+ }
26
+
27
+ .sidebar {
28
+ width: 280px;
29
+ background-color: white;
30
+ border-right: 1px solid var(--border-color);
31
+ padding: 2rem;
32
+ position: fixed;
33
+ height: 100vh;
34
+ overflow-y: auto;
35
+ }
36
+
37
+ .content {
38
+ flex: 1;
39
+ padding: 2rem;
40
+ margin-left: 280px;
41
+ max-width: 1200px;
42
+ }
43
+
44
+ .logo {
45
+ font-size: 1.5rem;
46
+ font-weight: bold;
47
+ color: var(--primary-color);
48
+ margin-bottom: 2rem;
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 0.5rem;
52
+ }
53
+
54
+ .nav-item {
55
+ margin: 0.5rem 0;
56
+ cursor: pointer;
57
+ padding: 0.5rem;
58
+ border-radius: 0.375rem;
59
+ transition: background-color 0.2s;
60
+ }
61
+
62
+ .nav-item:hover {
63
+ background-color: #f3f4f6;
64
+ }
65
+
66
+ h1 {
67
+ font-size: 2.5rem;
68
+ margin-bottom: 1.5rem;
69
+ color: var(--primary-color);
70
+ }
71
+
72
+ h2 {
73
+ font-size: 1.8rem;
74
+ margin: 2rem 0 1rem;
75
+ padding-top: 2rem;
76
+ border-top: 1px solid var(--border-color);
77
+ }
78
+
79
+ h3 {
80
+ font-size: 1.4rem;
81
+ margin: 1.5rem 0 1rem;
82
+ color: var(--secondary-color);
83
+ }
84
+
85
+ p {
86
+ margin-bottom: 1rem;
87
+ }
88
+
89
+ pre {
90
+ margin: 1rem 0;
91
+ padding: 1rem;
92
+ border-radius: 0.5rem;
93
+ background-color: #2d2d2d !important;
94
+ overflow-x: auto;
95
+ border: 1px solid #3f3f3f;
96
+ }
97
+
98
+ code {
99
+ font-family: 'Fira Code', monospace;
100
+ font-size: 0.9rem;
101
+ color: #e6e6e6;
102
+ }
103
+
104
+ /* Specific syntax highlighting overrides */
105
+ .language-bash {
106
+ color: #e6e6e6 !important;
107
+ }
108
+
109
+ .language-bash .token.function {
110
+ color: #87ceeb !important;
111
+ }
112
+
113
+ .language-bash .token.operator {
114
+ color: #87ceeb !important;
115
+ }
116
+
117
+ .language-bash .token.string {
118
+ color: #98c379 !important;
119
+ }
120
+
121
+ .language-json {
122
+ color: #e6e6e6 !important;
123
+ }
124
+
125
+ .language-json .token.property {
126
+ color: #87ceeb !important;
127
+ }
128
+
129
+ .language-json .token.string {
130
+ color: #98c379 !important;
131
+ }
132
+
133
+ .language-json .token.number {
134
+ color: #d19a66 !important;
135
+ }
136
+
137
+ /* Add a subtle glow effect to code blocks */
138
+ pre code {
139
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
140
+ }
141
+
142
+ .endpoint {
143
+ background-color: white;
144
+ border: 1px solid var(--border-color);
145
+ border-radius: 0.5rem;
146
+ padding: 1.5rem;
147
+ margin: 1.5rem 0;
148
+ }
149
+
150
+ .method {
151
+ display: inline-block;
152
+ padding: 0.25rem 0.5rem;
153
+ border-radius: 0.25rem;
154
+ background-color: var(--primary-color);
155
+ color: white;
156
+ font-weight: 500;
157
+ margin-right: 0.5rem;
158
+ }
159
+
160
+ .endpoint-url {
161
+ font-family: 'Fira Code', monospace;
162
+ color: var(--secondary-color);
163
+ }
164
+
165
+ @media (max-width: 768px) {
166
+ .sidebar {
167
+ display: none;
168
+ }
169
+ .content {
170
+ margin-left: 0;
171
+ }
172
+ }
utils.py ADDED
File without changes