tommytracx commited on
Commit
28c2439
·
verified ·
1 Parent(s): a46c1ac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +476 -160
app.py CHANGED
@@ -1,149 +1,513 @@
1
- from flask import Flask, request, jsonify
 
2
  import os
3
- import subprocess
4
  import json
5
  import logging
6
  from typing import Dict, Any, List
7
- import requests
8
 
9
  app = Flask(__name__)
10
- logging.basicConfig(level=logging.INFO)
11
 
12
  # Configuration
13
  OLLAMA_BASE_URL = os.getenv('OLLAMA_BASE_URL', 'http://localhost:11434')
14
- MODELS_DIR = os.getenv('MODELS_DIR', '/models')
15
  ALLOWED_MODELS = os.getenv('ALLOWED_MODELS', 'llama2,llama2:13b,llama2:70b,codellama,neural-chat,gemma-3-270m').split(',')
 
 
16
 
17
  class OllamaManager:
18
  def __init__(self, base_url: str):
19
- self.base_url = base_url
20
- self.available_models = []
21
  self.refresh_models()
22
 
23
- def refresh_models(self):
24
- """Refresh the list of available models"""
25
  try:
26
  response = requests.get(f"{self.base_url}/api/tags", timeout=10)
27
- if response.status_code == 200:
28
- data = response.json()
29
- self.available_models = [model['name'] for model in data.get('models', [])]
30
- else:
31
- self.available_models = []
 
 
 
 
32
  except Exception as e:
33
  logging.error(f"Error refreshing models: {e}")
34
- self.available_models = []
35
 
36
  def list_models(self) -> List[str]:
37
- """List all available models"""
38
- self.refresh_models()
39
  return self.available_models
40
 
41
- def pull_model(self, model_name: str) -> Dict[str, Any]:
42
- """Pull a model from Ollama"""
43
- try:
44
- response = requests.post(f"{self.base_url}/api/pull",
45
- json={"name": model_name},
46
- timeout=300)
47
- if response.status_code == 200:
48
- return {"status": "success", "model": model_name}
49
- else:
50
- return {"status": "error", "message": f"Failed to pull model: {response.text}"}
51
- except Exception as e:
52
- return {"status": "error", "message": str(e)}
53
-
54
  def generate(self, model_name: str, prompt: str, **kwargs) -> Dict[str, Any]:
55
- """Generate text using a model"""
 
 
 
56
  try:
57
  payload = {
58
  "model": model_name,
59
  "prompt": prompt,
60
- "stream": False
 
 
 
 
 
 
 
 
 
 
61
  }
62
- payload.update(kwargs)
63
-
64
- response = requests.post(f"{self.base_url}/api/generate",
65
- json=payload,
66
- timeout=120)
67
-
68
- if response.status_code == 200:
69
- data = response.json()
70
- return {
71
- "status": "success",
72
- "response": data.get('response', ''),
73
- "model": model_name,
74
- "usage": data.get('usage', {})
75
- }
76
- else:
77
- return {"status": "error", "message": f"Generation failed: {response.text}"}
78
  except Exception as e:
 
79
  return {"status": "error", "message": str(e)}
 
 
 
 
 
 
 
 
 
 
80
 
81
  # Initialize Ollama manager
82
  ollama_manager = OllamaManager(OLLAMA_BASE_URL)
83
 
84
- @app.route('/')
85
- def home():
86
- """Home page with API documentation"""
87
- return '''
88
- <!DOCTYPE html>
89
- <html>
90
- <head>
91
- <title>Ollama API Space</title>
92
- <style>
93
- body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
94
- .endpoint { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
95
- .method { background: #007bff; color: white; padding: 2px 8px; border-radius: 3px; font-size: 12px; }
96
- .url { font-family: monospace; background: #e9ecef; padding: 2px 6px; border-radius: 3px; }
97
- </style>
98
- </head>
99
- <body>
100
- <h1>🚀 Ollama API Space</h1>
101
- <p>This Space provides API endpoints for Ollama model management and inference.</p>
102
-
103
- <h2>Available Endpoints</h2>
104
-
105
- <div class="endpoint">
106
- <span class="method">GET</span> <span class="url">/api/models</span>
107
- <p>List all available models</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </div>
109
 
110
- <div class="endpoint">
111
- <span class="method">POST</span> <span class="url">/api/models/pull</span>
112
- <p>Pull a model from Ollama</p>
113
- <p>Body: {"name": "model_name"}</p>
 
 
 
 
 
 
 
 
 
 
 
 
114
  </div>
115
 
116
- <div class="endpoint">
117
- <span class="method">POST</span> <span class="url">/api/generate</span>
118
- <p>Generate text using a model</p>
119
- <p>Body: {"model": "model_name", "prompt": "your prompt"}</p>
 
 
 
120
  </div>
121
 
122
- <div class="endpoint">
123
- <span class="method">GET</span> <span class="url">/health</span>
124
- <p>Health check endpoint</p>
125
  </div>
126
 
127
- <h2>Usage Examples</h2>
128
- <p>You can use this API with OpenWebUI or any other client that supports REST APIs.</p>
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- <h3>cURL Examples</h3>
131
- <pre>
132
- # List models
133
- curl https://your-space-url.hf.space/api/models
134
 
135
- # Generate text
136
- curl -X POST https://your-space-url.hf.space/api/generate \
137
- -H "Content-Type: application/json" \
138
- -d '{"model": "llama2", "prompt": "Hello, how are you?"}'
139
- </pre>
140
- </body>
141
- </html>
142
- '''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
  @app.route('/api/models', methods=['GET'])
145
- def list_models():
146
- """List all available models"""
147
  try:
148
  models = ollama_manager.list_models()
149
  return jsonify({
@@ -152,74 +516,26 @@ def list_models():
152
  "count": len(models)
153
  })
154
  except Exception as e:
155
- return jsonify({"status": "error", "message": str(e)}), 500
156
-
157
- @app.route('/api/models/pull', methods=['POST'])
158
- def pull_model():
159
- """Pull a model from Ollama"""
160
- try:
161
- data = request.get_json()
162
- if not data or 'name' not in data:
163
- return jsonify({"status": "error", "message": "Model name is required"}), 400
164
-
165
- model_name = data['name']
166
- if model_name not in ALLOWED_MODELS:
167
- return jsonify({"status": "error", "message": f"Model {model_name} not in allowed list"}), 400
168
-
169
- result = ollama_manager.pull_model(model_name)
170
- if result["status"] == "success":
171
- return jsonify(result), 200
172
- else:
173
- return jsonify(result), 500
174
- except Exception as e:
175
- return jsonify({"status": "error", "message": str(e)}), 500
176
-
177
- @app.route('/api/generate', methods=['POST'])
178
- def generate_text():
179
- """Generate text using a model"""
180
- try:
181
- data = request.get_json()
182
- if not data or 'model' not in data or 'prompt' not in data:
183
- return jsonify({"status": "error", "message": "Model name and prompt are required"}), 400
184
-
185
- model_name = data['model']
186
- prompt = data['prompt']
187
-
188
- # Remove additional parameters that might be passed
189
- kwargs = {k: v for k, v in data.items() if k not in ['model', 'prompt']}
190
-
191
- result = ollama_manager.generate(model_name, prompt, **kwargs)
192
- if result["status"] == "success":
193
- return jsonify(result), 200
194
- else:
195
- return jsonify(result), 500
196
- except Exception as e:
197
  return jsonify({"status": "error", "message": str(e)}), 500
198
 
199
  @app.route('/health', methods=['GET'])
200
  def health_check():
201
- """Health check endpoint"""
202
  try:
203
- # Try to connect to Ollama
204
- response = requests.get(f"{OLLAMA_BASE_URL}/api/tags", timeout=5)
205
- if response.status_code == 200:
206
- return jsonify({
207
- "status": "healthy",
208
- "ollama_connection": "connected",
209
- "available_models": len(ollama_manager.available_models)
210
- })
211
- else:
212
- return jsonify({
213
- "status": "unhealthy",
214
- "ollama_connection": "failed",
215
- "error": f"Ollama returned status {response.status_code}"
216
- }), 503
217
  except Exception as e:
 
218
  return jsonify({
219
  "status": "unhealthy",
220
- "ollama_connection": "failed",
221
- "error": str(e)
222
- }), 503
223
 
224
  if __name__ == '__main__':
225
- app.run(host='0.0.0.0', port=7860, debug=False)
 
1
+ # app.py
2
+ from flask import Flask, request, jsonify, render_template_string
3
  import os
4
+ import requests
5
  import json
6
  import logging
7
  from typing import Dict, Any, List
8
+ import time
9
 
10
  app = Flask(__name__)
11
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
12
 
13
  # Configuration
14
  OLLAMA_BASE_URL = os.getenv('OLLAMA_BASE_URL', 'http://localhost:11434')
 
15
  ALLOWED_MODELS = os.getenv('ALLOWED_MODELS', 'llama2,llama2:13b,llama2:70b,codellama,neural-chat,gemma-3-270m').split(',')
16
+ MAX_TOKENS = int(os.getenv('MAX_TOKENS', '2048'))
17
+ TEMPERATURE = float(os.getenv('TEMPERATURE', '0.7'))
18
 
19
  class OllamaManager:
20
  def __init__(self, base_url: str):
21
+ self.base_url = base_url.rstrip('/')
22
+ self.available_models = ALLOWED_MODELS # Initialize with allowed models
23
  self.refresh_models()
24
 
25
+ def refresh_models(self) -> None:
26
+ """Refresh the list of available models from Ollama API, falling back to allowed models."""
27
  try:
28
  response = requests.get(f"{self.base_url}/api/tags", timeout=10)
29
+ response.raise_for_status()
30
+ data = response.json()
31
+ models = [model['name'] for model in data.get('models', [])]
32
+ # Filter models to only include those in ALLOWED_MODELS
33
+ self.available_models = [model for model in models if model in ALLOWED_MODELS]
34
+ if not self.available_models:
35
+ self.available_models = ALLOWED_MODELS
36
+ logging.warning("No allowed models found in API response, using ALLOWED_MODELS")
37
+ logging.info(f"Available models: {self.available_models}")
38
  except Exception as e:
39
  logging.error(f"Error refreshing models: {e}")
40
+ self.available_models = ALLOWED_MODELS
41
 
42
  def list_models(self) -> List[str]:
43
+ """Return the list of available models."""
 
44
  return self.available_models
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def generate(self, model_name: str, prompt: str, **kwargs) -> Dict[str, Any]:
47
+ """Generate text using a model."""
48
+ if model_name not in self.available_models:
49
+ return {"status": "error", "message": f"Model {model_name} not available"}
50
+
51
  try:
52
  payload = {
53
  "model": model_name,
54
  "prompt": prompt,
55
+ "stream": False,
56
+ **kwargs
57
+ }
58
+ response = requests.post(f"{self.base_url}/api/generate", json=payload, timeout=120)
59
+ response.raise_for_status()
60
+ data = response.json()
61
+ return {
62
+ "status": "success",
63
+ "response": data.get('response', ''),
64
+ "model": model_name,
65
+ "usage": data.get('usage', {})
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  except Exception as e:
68
+ logging.error(f"Error generating response: {e}")
69
  return {"status": "error", "message": str(e)}
70
+
71
+ def health_check(self) -> Dict[str, Any]:
72
+ """Check the health of the Ollama API."""
73
+ try:
74
+ response = requests.get(f"{self.base_url}/api/tags", timeout=10)
75
+ response.raise_for_status()
76
+ return {"status": "healthy", "available_models": len(self.available_models)}
77
+ except Exception as e:
78
+ logging.error(f"Health check failed: {e}")
79
+ return {"status": "unhealthy", "error": str(e)}
80
 
81
  # Initialize Ollama manager
82
  ollama_manager = OllamaManager(OLLAMA_BASE_URL)
83
 
84
+ # HTML template for the chat interface (unchanged from original)
85
+ HTML_TEMPLATE = '''
86
+ <!DOCTYPE html>
87
+ <html lang="en">
88
+ <head>
89
+ <meta charset="UTF-8">
90
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
91
+ <title>OpenWebUI - Ollama Chat</title>
92
+ <style>
93
+ * {
94
+ margin: 0;
95
+ padding: 0;
96
+ box-sizing: border-box;
97
+ }
98
+ body {
99
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
100
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
101
+ min-height: 100vh;
102
+ padding: 20px;
103
+ }
104
+ .container {
105
+ max-width: 1200px;
106
+ margin: 0 auto;
107
+ background: white;
108
+ border-radius: 20px;
109
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
110
+ overflow: hidden;
111
+ }
112
+ .header {
113
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
114
+ color: white;
115
+ padding: 30px;
116
+ text-align: center;
117
+ }
118
+ .header h1 {
119
+ font-size: 2.5rem;
120
+ margin-bottom: 10px;
121
+ font-weight: 700;
122
+ }
123
+ .header p {
124
+ font-size: 1.1rem;
125
+ opacity: 0.9;
126
+ }
127
+ .controls {
128
+ padding: 20px 30px;
129
+ background: #f8f9fa;
130
+ border-bottom: 1px solid #e9ecef;
131
+ display: flex;
132
+ gap: 15px;
133
+ align-items: center;
134
+ flex-wrap: wrap;
135
+ }
136
+ .control-group {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: 8px;
140
+ }
141
+ .control-group label {
142
+ font-weight: 600;
143
+ color: #495057;
144
+ min-width: 80px;
145
+ }
146
+ .control-group select,
147
+ .control-group input {
148
+ padding: 8px 12px;
149
+ border: 2px solid #e9ecef;
150
+ border-radius: 8px;
151
+ font-size: 14px;
152
+ transition: border-color 0.3s;
153
+ }
154
+ .control-group select:focus,
155
+ .control-group input:focus {
156
+ outline: none;
157
+ border-color: #667eea;
158
+ }
159
+ .chat-container {
160
+ height: 500px;
161
+ overflow-y: auto;
162
+ padding: 20px;
163
+ background: #fafbfc;
164
+ }
165
+ .message {
166
+ margin-bottom: 20px;
167
+ display: flex;
168
+ gap: 15px;
169
+ }
170
+ .message.user {
171
+ flex-direction: row-reverse;
172
+ }
173
+ .message-avatar {
174
+ width: 40px;
175
+ height: 40px;
176
+ border-radius: 50%;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ font-weight: bold;
181
+ color: white;
182
+ flex-shrink: 0;
183
+ }
184
+ .message.user .message-avatar {
185
+ background: #667eea;
186
+ }
187
+ .message.assistant .message-avatar {
188
+ background: #28a745;
189
+ }
190
+ .message-content {
191
+ background: white;
192
+ padding: 15px 20px;
193
+ border-radius: 18px;
194
+ max-width: 70%;
195
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
196
+ line-height: 1.5;
197
+ }
198
+ .message.user .message-content {
199
+ background: #667eea;
200
+ color: white;
201
+ }
202
+ .message.assistant .message-content {
203
+ background: white;
204
+ color: #333;
205
+ }
206
+ .input-container {
207
+ padding: 20px 30px;
208
+ background: white;
209
+ border-top: 1px solid #e9ecef;
210
+ }
211
+ .input-form {
212
+ display: flex;
213
+ gap: 15px;
214
+ }
215
+ .input-field {
216
+ flex: 1;
217
+ padding: 15px 20px;
218
+ border: 2px solid #e9ecef;
219
+ border-radius: 25px;
220
+ font-size: 16px;
221
+ transition: border-color 0.3s;
222
+ resize: none;
223
+ min-height: 50px;
224
+ max-height: 120px;
225
+ }
226
+ .input-field:focus {
227
+ outline: none;
228
+ border-color: #667eea;
229
+ }
230
+ .send-button {
231
+ padding: 15px 30px;
232
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
233
+ color: white;
234
+ border: none;
235
+ border-radius: 25px;
236
+ font-size: 16px;
237
+ font-weight: 600;
238
+ cursor: pointer;
239
+ transition: transform 0.2s;
240
+ min-width: 100px;
241
+ }
242
+ .send-button:hover {
243
+ transform: translateY(-2px);
244
+ }
245
+ .send-button:disabled {
246
+ opacity: 0.6;
247
+ cursor: not-allowed;
248
+ transform: none;
249
+ }
250
+ .status {
251
+ text-align: center;
252
+ padding: 10px;
253
+ font-size: 14px;
254
+ color: #6c757d;
255
+ }
256
+ .status.error {
257
+ color: #dc3545;
258
+ }
259
+ .status.success {
260
+ color: #28a745;
261
+ }
262
+ .typing-indicator {
263
+ display: none;
264
+ padding: 15px 20px;
265
+ background: white;
266
+ border-radius: 18px;
267
+ color: #6c757d;
268
+ font-style: italic;
269
+ }
270
+ @media (max-width: 768px) {
271
+ .controls {
272
+ flex-direction: column;
273
+ align-items: stretch;
274
+ }
275
+ .control-group {
276
+ justify-content: space-between;
277
+ }
278
+ .message-content {
279
+ max-width: 85%;
280
+ }
281
+ }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <div class="container">
286
+ <div class="header">
287
+ <h1>🤖 OpenWebUI</h1>
288
+ <p>Chat with your local Ollama models through Hugging Face Spaces</p>
289
  </div>
290
 
291
+ <div class="controls">
292
+ <div class="control-group">
293
+ <label for="model-select">Model:</label>
294
+ <select id="model-select">
295
+ <option value="">Select a model...</option>
296
+ </select>
297
+ </div>
298
+ <div class="control-group">
299
+ <label for="temperature">Temperature:</label>
300
+ <input type="range" id="temperature" min="0" max="2" step="0.1" value="0.7">
301
+ <span id="temp-value">0.7</span>
302
+ </div>
303
+ <div class="control-group">
304
+ <label for="max-tokens">Max Tokens:</label>
305
+ <input type="number" id="max-tokens" min="1" max="4096" value="2048">
306
+ </div>
307
  </div>
308
 
309
+ <div class="chat-container" id="chat-container">
310
+ <div class="message assistant">
311
+ <div class="message-avatar">AI</div>
312
+ <div class="message-content">
313
+ Hello! I'm your AI assistant powered by Ollama. How can I help you today?
314
+ </div>
315
+ </div>
316
  </div>
317
 
318
+ <div class="typing-indicator" id="typing-indicator">
319
+ AI is thinking...
 
320
  </div>
321
 
322
+ <div class="input-container">
323
+ <form class="input-form" id="chat-form">
324
+ <textarea
325
+ class="input-field"
326
+ id="message-input"
327
+ placeholder="Type your message here..."
328
+ rows="1"
329
+ ></textarea>
330
+ <button type="submit" class="send-button" id="send-button">
331
+ Send
332
+ </button>
333
+ </form>
334
+ </div>
335
 
336
+ <div class="status" id="status"></div>
337
+ </div>
 
 
338
 
339
+ <script>
340
+ let conversationHistory = [];
341
+
342
+ document.addEventListener('DOMContentLoaded', function() {
343
+ loadModels();
344
+ setupEventListeners();
345
+ autoResizeTextarea();
346
+ });
347
+
348
+ async function loadModels() {
349
+ const modelSelect = document.getElementById('model-select');
350
+ modelSelect.innerHTML = '<option value="">Loading models...</option>';
351
+
352
+ try {
353
+ const response = await fetch('/api/models');
354
+ const data = await response.json();
355
+
356
+ modelSelect.innerHTML = '<option value="">Select a model...</option>';
357
+
358
+ if (data.status === 'success' && data.models.length > 0) {
359
+ data.models.forEach(model => {
360
+ const option = document.createElement('option');
361
+ option.value = model;
362
+ option.textContent = model;
363
+ if (model === 'gemma-3-270m') {
364
+ option.selected = true;
365
+ }
366
+ modelSelect.appendChild(option);
367
+ });
368
+ showStatus('Models loaded successfully', 'success');
369
+ } else {
370
+ modelSelect.innerHTML = '<option value="">No models available</option>';
371
+ showStatus('No models available from API', 'error');
372
+ }
373
+ } catch (error) {
374
+ console.error('Error loading models:', error);
375
+ modelSelect.innerHTML = '<option value="">No models available</option>';
376
+ showStatus('Failed to load models: ' + error.message, 'error');
377
+ }
378
+ }
379
+
380
+ function setupEventListeners() {
381
+ document.getElementById('chat-form').addEventListener('submit', handleSubmit);
382
+ document.getElementById('temperature').addEventListener('input', function() {
383
+ document.getElementById('temp-value').textContent = this.value;
384
+ });
385
+ document.getElementById('message-input').addEventListener('input', autoResizeTextarea);
386
+ }
387
+
388
+ function autoResizeTextarea() {
389
+ const textarea = document.getElementById('message-input');
390
+ textarea.style.height = 'auto';
391
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
392
+ }
393
+
394
+ async function handleSubmit(e) {
395
+ e.preventDefault();
396
+
397
+ const messageInput = document.getElementById('message-input');
398
+ const message = messageInput.value.trim();
399
+
400
+ if (!message) return;
401
+
402
+ const model = document.getElementById('model-select').value;
403
+ const temperature = parseFloat(document.getElementById('temperature').value);
404
+ const maxTokens = parseInt(document.getElementById('max-tokens').value);
405
+
406
+ if (!model) {
407
+ showStatus('Please select a model', 'error');
408
+ return;
409
+ }
410
+
411
+ addMessage(message, 'user');
412
+ messageInput.value = '';
413
+ autoResizeTextarea();
414
+ showTypingIndicator(true);
415
+
416
+ try {
417
+ const response = await fetch('/api/chat', {
418
+ method: 'POST',
419
+ headers: { 'Content-Type': 'application/json' },
420
+ body: JSON.stringify({ model, prompt: message, temperature, max_tokens: maxTokens })
421
+ });
422
+ const data = await response.json();
423
+
424
+ showTypingIndicator(false);
425
+
426
+ if (data.status === 'success') {
427
+ addMessage(data.response, 'assistant');
428
+ showStatus(`Response generated using ${model}`, 'success');
429
+ } else {
430
+ addMessage('Sorry, I encountered an error while processing your request.', 'assistant');
431
+ showStatus(`Error: ${data.message}`, 'error');
432
+ }
433
+ } catch (error) {
434
+ showTypingIndicator(false);
435
+ addMessage('Sorry, I encountered a network error.', 'assistant');
436
+ showStatus('Network error: ' + error.message, 'error');
437
+ }
438
+ }
439
+
440
+ function addMessage(content, sender) {
441
+ const chatContainer = document.getElementById('chat-container');
442
+ const messageDiv = document.createElement('div');
443
+ messageDiv.className = `message ${sender}`;
444
+
445
+ const avatar = document.createElement('div');
446
+ avatar.className = 'message-avatar';
447
+ avatar.textContent = sender === 'user' ? 'U' : 'AI';
448
+
449
+ const messageContent = document.createElement('div');
450
+ messageContent.className = 'message-content';
451
+ messageContent.textContent = content;
452
+
453
+ messageDiv.appendChild(avatar);
454
+ messageDiv.appendChild(messageContent);
455
+ chatContainer.appendChild(messageDiv);
456
+ chatContainer.scrollTop = chatContainer.scrollHeight;
457
+
458
+ conversationHistory.push({ role: sender, content: content });
459
+ }
460
+
461
+ function showTypingIndicator(show) {
462
+ const indicator = document.getElementById('typing-indicator');
463
+ indicator.style.display = show ? 'block' : 'none';
464
+ if (show) {
465
+ const chatContainer = document.getElementById('chat-container');
466
+ chatContainer.scrollTop = chatContainer.scrollHeight;
467
+ }
468
+ }
469
+
470
+ function showStatus(message, type = '') {
471
+ const statusDiv = document.getElementById('status');
472
+ statusDiv.textContent = message;
473
+ statusDiv.className = `status ${type}`;
474
+ setTimeout(() => {
475
+ statusDiv.textContent = '';
476
+ statusDiv.className = 'status';
477
+ }, 5000);
478
+ }
479
+ </script>
480
+ </body>
481
+ </html>
482
+ '''
483
+
484
+ @app.route('/')
485
+ def home():
486
+ """Main chat interface."""
487
+ return render_template_string(HTML_TEMPLATE, ollama_base_url=OLLAMA_BASE_URL, default_model=ALLOWED_MODELS)
488
+
489
+ @app.route('/api/chat', methods=['POST'])
490
+ def chat():
491
+ """Chat API endpoint."""
492
+ try:
493
+ data = request.get_json()
494
+ if not data or 'prompt' not in data or 'model' not in data:
495
+ return jsonify({"status": "error", "message": "Prompt and model are required"}), 400
496
+
497
+ prompt = data['prompt']
498
+ model = data['model']
499
+ temperature = data.get('temperature', TEMPERATURE)
500
+ max_tokens = data.get('max_tokens', MAX_TOKENS)
501
+
502
+ result = ollama_manager.generate(model, prompt, temperature=temperature, max_tokens=max_tokens)
503
+ return jsonify(result), 200 if result["status"] == "success" else 500
504
+ except Exception as e:
505
+ logging.error(f"Chat endpoint error: {e}")
506
+ return jsonify({"status": "error", "message": str(e)}), 500
507
 
508
  @app.route('/api/models', methods=['GET'])
509
+ def get_models():
510
+ """Get available models."""
511
  try:
512
  models = ollama_manager.list_models()
513
  return jsonify({
 
516
  "count": len(models)
517
  })
518
  except Exception as e:
519
+ logging.error(f"Models endpoint error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  return jsonify({"status": "error", "message": str(e)}), 500
521
 
522
  @app.route('/health', methods=['GET'])
523
  def health_check():
524
+ """Health check endpoint."""
525
  try:
526
+ ollama_health = ollama_manager.health_check()
527
+ return jsonify({
528
+ "status": "healthy",
529
+ "ollama_api": ollama_health,
530
+ "timestamp": time.time()
531
+ })
 
 
 
 
 
 
 
 
532
  except Exception as e:
533
+ logging.error(f"Health check endpoint error: {e}")
534
  return jsonify({
535
  "status": "unhealthy",
536
+ "error": str(e),
537
+ "timestamp": time.time()
538
+ }), 500
539
 
540
  if __name__ == '__main__':
541
+ app.run(host='0.0.0.0', port=7860, debug=False)