1- from flask import Flask , render_template , jsonify , request , send_from_directory
1+ from flask import (
2+ Flask ,
3+ render_template ,
4+ jsonify ,
5+ request ,
6+ send_from_directory ,
7+ redirect ,
8+ make_response ,
9+ )
210import requests
311import threading
412import queue
513from ollamaClient import OllamaClient
614from dotenv import load_dotenv
7-
15+ import os
816import hashlib
17+ from uuid import uuid4
18+ from base64 import b64encode
19+ from itsdangerous import URLSafeSerializer
20+ import markdown
921
10- # Load environment variables from .env file
22+ # ===================== INITIALIZATION =====================
23+ # Initialize Flask app and Ollama client
24+ app = Flask (__name__ )
25+ ai = OllamaClient ()
1126
12- load_dotenv ("./config.env" )
27+ # Queue for handling asynchronous chat requests
28+ request_queue : queue .Queue = queue .Queue ()
29+ auth_portal_api_url = ""
30+ no_of_auth_reqs = 0
31+ no_of_reqs = 0
1332
33+ # Load environment variables from .env file
34+ load_dotenv ("./config.env" )
1435
15- # Helper function to get configuration values
1636
37+ # ===================== HELPER FUNCTIONS =====================
1738def get_config (key , default = None ):
18-
39+ """
40+ Retrieve a configuration value from environment variables.
41+ Exits the program if the key is not found and no default is provided.
42+ """
1943 value = os .getenv (key , default )
20-
2144 if value is None :
22-
2345 print (f"Error: The { key } is not set in the environment." )
24-
2546 exit (1 ) # Exit with non-zero status to indicate an error occurred
26-
2747 return value
2848
2949
30- # Configuration
31-
50+ # ===================== CONFIGURATION =====================
51+ # Load configuration from environment variables
3252config = {
33-
34- "host" : get_config ("SIMPLE_AI_UI_SERVER_HOST" , "0.0.0.0" ),
35-
36- "port" : int (get_config ("SIMPLE_AI_UI_SERVER_PORT" , "5000" )),
37-
53+ "host" : get_config ("SIMPLE_AI_UI_SERVER_HOST" ),
54+ "port" : int (get_config ("SIMPLE_AI_UI_SERVER_PORT" )),
3855 "users" : {
39-
4056 pair .split (":" )[0 ]: pair .split (":" )[1 ]
41-
42- for pair in get_config ("SIMPLE_AI_UI_AUTH_USERS" , "joe:1234,apple:1234" ).split ("," )
43-
57+ for pair in get_config ("SIMPLE_AI_UI_AUTH_USERS" ).split ("," )
4458 if ":" in pair
45-
4659 },
60+ "limit_of_auth_requests" : int (get_config ("SIMPLE_AI_UI_AUTH_NO_OF_USERS_PER_DAY" )),
61+ "limit_of_requests" : int (get_config ("SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY" )),
62+ }
4763
48- "open_stats" : get_config ("SIMPLE_AI_UI_AUTH_SHOW_USER_STATS" , "true" ).lower () == "true" ,
4964
50- "limit_of_requests" : int (get_config ("SIMPLE_AI_UI_API_LIMIT_OF_REQS_PER_DAY" , "1000" )),
65+ # ===================== AUTHENTICATION AND SECURITY =====================
66+ def auth (username , password ):
67+ """
68+ Authenticate a user by checking their username and password.
69+ Returns "OK-12" if authentication is successful, otherwise an error code.
70+ """
71+ if username in config ["users" ]:
72+ if hashing (config ["users" ][username ]) == hashing (password ):
73+ return "OK-12"
74+ else :
75+ return "BAD-2"
76+ return "BAD-12"
5177
52- "limit_of_devices" : int (get_config ("SIMPLE_AI_UI_API_NO_OF_DEVICES" , "1000" )),
5378
54- }
55- # Initialize Flask app and Ollama client
56- app = Flask (__name__ )
57- ai = OllamaClient ()
79+ def hashing (text ):
80+ """
81+ Hash a given text using SHA-256.
82+ """
83+ hash_object = hashlib .sha256 (text .encode ("utf-8" ))
84+ return hash_object .hexdigest ()
85+
86+
87+ def middleware_check (type ):
88+ """
89+ Middleware to check if the user is authenticated.
90+ Redirects to the login page or returns an error if not authenticated.
91+ """
92+ global no_of_auth_reqs
93+ try :
94+ auth_cookies = request .cookies .get ("auth" , "" )
95+ except ValueError :
96+ auth_cookies = ""
97+
98+ if auth_cookies == "" :
99+ if type == "api" :
100+ return jsonify (
101+ {
102+ "error" : "Auth error, You are not logged in!\n Login via <a href='/login'>LOGIN PAGE</a>"
103+ }
104+ )
105+ else :
106+ return redirect ("/login" )
107+ else :
108+ no_of_auth_reqs += 1
109+ return None
110+
111+
112+ def middleware_init (username ):
113+ """
114+ Initialize middleware by generating a token for the authenticated user.
115+ """
116+ global no_of_auth_reqs
117+ data = {
118+ "device" : request .headers .get ("User-Agent" ),
119+ "user" : username ,
120+ "ID" : str (uuid4 ()),
121+ "number" : no_of_auth_reqs ,
122+ }
123+ token , _ = server_encode_tokenizer (data )
124+ return token
125+
126+
127+ def server_encode_tokenizer (data ):
128+ """
129+ Encode data into a token using a URL-safe serializer.
130+ """
131+ key = str (uuid4 ())
132+ with open (".keys" , "a" ) as f :
133+ f .write (key + "\n " )
134+ auth_s = URLSafeSerializer (
135+ key + "-abujuni.dev" , "Hi I am Junaid, Support my project"
136+ )
137+ token = auth_s .dumps (data )
138+ return token , auth_s
58139
59- # Queue for handling asynchronous chat requests
60- request_queue : queue .Queue = queue .Queue ()
61140
141+ def server_decode_tokenizer (token , auth_s ):
142+ """
143+ Decode a token back into data using a URL-safe serializer.
144+ """
145+ data = auth_s .loads (token )
146+ return data
62147
148+
149+ # ===================== BACKGROUND WORKER THREAD =====================
63150def process_requests () -> None :
64- """Worker thread function to process queued chat requests."""
151+ """
152+ Worker thread function to process queued chat requests.
153+ """
65154 while True :
66155 task = request_queue .get ()
67156 if task is None :
@@ -81,60 +170,67 @@ def process_requests() -> None:
81170# Start background worker thread
82171threading .Thread (target = process_requests , daemon = True ).start ()
83172
84- def auth (username , password ):
85- for user in config .get ("users" ).keys ():
86- if user == username :
87- if config .get ("users" ).get (user ) == password :
88- return "OK-12"
89- else :
90- return "BAD-2"
91- else :
92- return "BAD-12"
93173
94- def hashing (text ):
95- content = str (uuid4 )+ text + str (uuid4 )
96- hash_object = hashlib .sha256 (content .encode ("utf-8" ))
97- hex_dig = hash_object .hexdigest ()
98- return hex_dig
99-
100- def middlewares (username ,password ):
101- auth_status = auth (username , password )
102- if auth_status == "OK-12" :
103- hash_obj = hashing (username + password )
104- request .cookies .add ("auth" :hash_obj )
105- ALLOW_HASHS .append (hash_obj )
106- return True
107- else :
108- return render_template ("error.html" , error = "YOU ARE NOT ALLOWED - AUTH ERROR" )
109-
110- def api_chat_func ():
111- data = request .json
112- response_queue : queue .Queue = queue .Queue ()
113- request_queue .put ((data , response_queue ))
114- response = response_queue .get () # Blocking wait for response
115- status = 200 if "response" in response else 400
116- return jsonify (response ), status
117-
118- # Routes
174+ # ===================== ROUTES =====================
175+ # ============= FRONTEND ROUTES =============
119176@app .route ("/" )
120177def index ():
178+ """
179+ Render the index page after checking authentication.
180+ """
181+ middleware_response = middleware_check (type = "html" )
182+ if middleware_response :
183+ return middleware_response
121184 return render_template ("index.html" ), 200
122185
123186
124187@app .route ("/static/<path:filename>" )
125188def serve_static (filename : str ):
189+ """
190+ Serve static files from the 'static' directory.
191+ """
126192 return send_from_directory ("static" , filename ), 200
127193
128194
129195@app .errorhandler (Exception )
130196def handle_error (error : Exception ):
197+ """
198+ Handle errors and render appropriate error pages.
199+ """
131200 if hasattr (error , "code" ) and error .code == 404 :
132201 return render_template ("404.html" ), 404
133202 return render_template ("error.html" , error = str (error )), 500
134203
135204
205+ # ============= SECURITY ROUTES =============
206+ @app .route ("/login" , methods = ["GET" , "POST" ])
207+ def api_auth ():
208+ """
209+ Handle user login. Authenticates the user and sets an auth cookie if successful.
210+ """
211+ if request .method == "POST" :
212+ username = request .form .get ("username" )
213+ password = request .form .get ("password" )
214+ auth_result = auth (username , password )
215+ if auth_result == "OK-12" :
216+ token = middleware_init (username )
217+ response = make_response (redirect ("/" ))
218+ response .set_cookie ("auth" , token )
219+ return response
220+ else :
221+ return render_template ("login.html" , error = "Invalid username or password" )
222+ return render_template ("login.html" )
223+
224+
225+ # ============= API ROUTES =============
136226@app .route ("/api/connection" )
137227def api_connection ():
228+ """
229+ Check the connection to the Ollama server.
230+ """
231+ middleware_response = middleware_check (type = "api" )
232+ if middleware_response :
233+ return middleware_response
138234 try :
139235 res = requests .get ("http://localhost:11434" , timeout = 5 )
140236 status_code = res .status_code
@@ -156,6 +252,12 @@ def api_connection():
156252
157253@app .route ("/api/connection/stats" )
158254def api_connection_stats ():
255+ """
256+ Retrieve statistics about available and active models from the Ollama client.
257+ """
258+ middleware_response = middleware_check (type = "api" )
259+ if middleware_response :
260+ return middleware_response
159261 models = ai .list_models ()
160262 active_models = ai .list_active_models ()
161263
@@ -167,8 +269,30 @@ def api_connection_stats():
167269
168270@app .route ("/api/chat" , methods = ["POST" ])
169271def api_chat ():
170- middlewares_firewall = middlewares ()
272+ """
273+ Handle chat requests by processing the prompt and model, and returning the AI's response.
274+ """
275+ middleware_response = middleware_check (type = "api" )
276+ if middleware_response :
277+ return middleware_response
278+
279+ data = request .json
280+ if not data or "prompt" not in data or "model" not in data :
281+ return jsonify ({"error" : "Invalid request data" }), 400
282+
283+ response_queue = queue .Queue ()
284+ request_queue .put ((data , response_queue ))
285+
286+ response = response_queue .get ()
287+
288+ if "response" not in response :
289+ return jsonify ({"error" : "Invalid response from the AI" }), 500
290+
291+ html_response = markdown .markdown (response ["response" ])
292+
293+ return jsonify ({"response" : html_response }), 200
171294
172295
296+ # ===================== MAIN EXECUTION =====================
173297if __name__ == "__main__" :
174- app .run ()
298+ app .run (host = config . get ( "host" ), port = config . get ( "port" ) )
0 commit comments