tommytracx commited on
Commit
20a7b41
·
verified ·
1 Parent(s): c701ddd

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +25 -0
  2. README.md +211 -8
  3. app.py +611 -0
  4. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ curl \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copy requirements and install Python dependencies
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy application code
15
+ COPY . .
16
+
17
+ # Expose port
18
+ EXPOSE 7860
19
+
20
+ # Health check
21
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
22
+ CMD curl -f http://localhost:7860/health || exit 1
23
+
24
+ # Run the application
25
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "1", "--timeout", "120", "app:app"]
README.md CHANGED
@@ -1,12 +1,215 @@
1
  ---
2
- title: Openwebui Ollama
3
- emoji: 🦀
4
- colorFrom: yellow
5
- colorTo: indigo
6
  sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: openwebui-ollama
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: OpenWebUI - Ollama Chat
3
+ emoji: 🤖
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
 
 
8
  ---
9
 
10
+ # 🤖 OpenWebUI - Ollama Chat
11
+
12
+ A beautiful, modern chat interface for Ollama models, deployed as a Hugging Face Space. This Space provides a full-featured web UI that connects to your Ollama API Space for an interactive chat experience.
13
+
14
+ ## 🌟 Features
15
+
16
+ - **Beautiful Chat Interface**: Modern, responsive design with gradient backgrounds and smooth animations
17
+ - **Model Selection**: Choose from any available Ollama model
18
+ - **Parameter Control**: Adjust temperature, max tokens, and other generation parameters
19
+ - **Real-time Chat**: Interactive chat experience with typing indicators
20
+ - **Mobile Responsive**: Works perfectly on desktop and mobile devices
21
+ - **Ollama Integration**: Seamlessly connects to your Ollama API Space
22
+ - **Health Monitoring**: Built-in health checks and status monitoring
23
+
24
+ ## 🚀 Quick Start
25
+
26
+ ### 1. Deploy to Hugging Face Spaces
27
+
28
+ 1. Fork this repository or create a new Space
29
+ 2. Upload these files to your Space
30
+ 3. Set the following environment variables in your Space settings:
31
+ - `OLLAMA_API_URL`: URL to your Ollama Space (e.g., `https://your-ollama-space.hf.space`)
32
+ - `DEFAULT_MODEL`: Default model to use (e.g., `llama2`)
33
+ - `MAX_TOKENS`: Maximum tokens for generation (default: `2048`)
34
+ - `TEMPERATURE`: Default temperature (default: `0.7`)
35
+
36
+ ### 2. Local Development
37
+
38
+ ```bash
39
+ # Clone the repository
40
+ git clone <your-repo-url>
41
+ cd openwebui-space
42
+
43
+ # Install dependencies
44
+ pip install -r requirements.txt
45
+
46
+ # Set environment variables
47
+ export OLLAMA_API_URL=https://your-ollama-space.hf.space
48
+ export DEFAULT_MODEL=llama2
49
+
50
+ # Run the application
51
+ python app.py
52
+ ```
53
+
54
+ ## 🔧 Configuration
55
+
56
+ ### Environment Variables
57
+
58
+ - `OLLAMA_API_URL`: **Required** - URL to your Ollama Space (e.g., `https://your-ollama-space.hf.space`)
59
+ - `DEFAULT_MODEL`: Default model to use (default: `llama2`)
60
+ - `MAX_TOKENS`: Maximum tokens for generation (default: `2048`)
61
+ - `TEMPERATURE`: Default temperature setting (default: `0.7`)
62
+
63
+ ### Prerequisites
64
+
65
+ Before using this OpenWebUI Space, you need:
66
+
67
+ 1. **Ollama Space**: A deployed Ollama API Space (see the `ollama-space` directory)
68
+ 2. **Ollama Instance**: A running Ollama instance that your Ollama Space can connect to
69
+ 3. **Network Access**: The OpenWebUI Space must be able to reach your Ollama Space
70
+
71
+ ## 📡 API Endpoints
72
+
73
+ ### GET `/`
74
+ Main chat interface - the beautiful web UI.
75
+
76
+ ### POST `/api/chat`
77
+ Chat API endpoint for programmatic access.
78
+
79
+ **Request Body:**
80
+ ```json
81
+ {
82
+ "message": "Hello, how are you?",
83
+ "model": "llama2",
84
+ "temperature": 0.7,
85
+ "max_tokens": 2048
86
+ }
87
+ ```
88
+
89
+ **Response:**
90
+ ```json
91
+ {
92
+ "status": "success",
93
+ "response": "Hello! I'm doing well, thank you for asking...",
94
+ "model": "llama2",
95
+ "usage": {
96
+ "prompt_tokens": 7,
97
+ "completion_tokens": 15,
98
+ "total_tokens": 22
99
+ }
100
+ }
101
+ ```
102
+
103
+ ### GET `/api/models`
104
+ Get available models from the connected Ollama Space.
105
+
106
+ ### GET `/health`
107
+ Health check endpoint that also checks the Ollama Space connection.
108
+
109
+ ## 🌐 Integration with Ollama Space
110
+
111
+ This OpenWebUI Space is designed to work seamlessly with the Ollama Space:
112
+
113
+ 1. **API Communication**: The OpenWebUI Space communicates with your Ollama Space via HTTP API calls
114
+ 2. **Model Discovery**: Automatically discovers and lists available models from your Ollama Space
115
+ 3. **Text Generation**: Sends generation requests to your Ollama Space and displays responses
116
+ 4. **Health Monitoring**: Monitors the health of both the OpenWebUI Space and the Ollama Space
117
+
118
+ ### Architecture
119
+
120
+ ```
121
+ User → OpenWebUI Space → Ollama Space → Ollama Instance
122
+ ```
123
+
124
+ ## 🎨 UI Features
125
+
126
+ ### Chat Interface
127
+ - **Message Bubbles**: Distinct styling for user and AI messages
128
+ - **Avatars**: Visual indicators for message sender
129
+ - **Auto-scroll**: Automatically scrolls to new messages
130
+ - **Typing Indicators**: Shows when AI is generating a response
131
+
132
+ ### Controls
133
+ - **Model Selection**: Dropdown to choose from available models
134
+ - **Temperature Slider**: Adjust creativity/randomness (0.0 - 2.0)
135
+ - **Max Tokens**: Set maximum response length
136
+ - **Real-time Updates**: Parameter changes are applied immediately
137
+
138
+ ### Responsive Design
139
+ - **Mobile Optimized**: Touch-friendly interface for mobile devices
140
+ - **Adaptive Layout**: Automatically adjusts to different screen sizes
141
+ - **Modern CSS**: Uses CSS Grid and Flexbox for optimal layouts
142
+
143
+ ## 🐳 Docker Support
144
+
145
+ The Space includes a Dockerfile for containerized deployment:
146
+
147
+ ```bash
148
+ # Build the image
149
+ docker build -t openwebui-space .
150
+
151
+ # Run the container
152
+ docker run -p 7860:7860 \
153
+ -e OLLAMA_API_URL=https://your-ollama-space.hf.space \
154
+ -e DEFAULT_MODEL=llama2 \
155
+ openwebui-space
156
+ ```
157
+
158
+ ## 🔒 Security Considerations
159
+
160
+ - **Public Access**: The Space is publicly accessible - consider adding authentication for production use
161
+ - **API Communication**: All communication with the Ollama Space is over HTTPS (when using HF Spaces)
162
+ - **Input Validation**: User inputs are validated before being sent to the Ollama Space
163
+
164
+ ## 🚨 Troubleshooting
165
+
166
+ ### Common Issues
167
+
168
+ 1. **Cannot connect to Ollama Space**: Check the `OLLAMA_API_URL` environment variable
169
+ 2. **No models available**: Ensure your Ollama Space is running and has models
170
+ 3. **Generation errors**: Check the Ollama Space logs for detailed error messages
171
+ 4. **Slow responses**: Large models may take time to generate responses
172
+
173
+ ### Health Checks
174
+
175
+ Use the `/health` endpoint to monitor:
176
+ - OpenWebUI Space status
177
+ - Connection to Ollama Space
178
+ - Ollama Space health status
179
+
180
+ ### Debug Mode
181
+
182
+ For local development, you can enable debug mode by setting `debug=True` in the Flask app.
183
+
184
+ ## 📱 Mobile Experience
185
+
186
+ The interface is fully optimized for mobile devices:
187
+ - Touch-friendly controls
188
+ - Responsive design that adapts to screen size
189
+ - Optimized for portrait and landscape orientations
190
+ - Fast loading and smooth scrolling
191
+
192
+ ## 🎯 Use Cases
193
+
194
+ - **Personal AI Assistant**: Chat with your local models
195
+ - **Development Testing**: Test model responses and parameters
196
+ - **Demo and Showcase**: Present your Ollama setup to others
197
+ - **API Gateway**: Use the chat interface as a frontend for your Ollama API
198
+
199
+ ## 📝 License
200
+
201
+ This project is open source and available under the MIT License.
202
+
203
+ ## 🤝 Contributing
204
+
205
+ Contributions are welcome! Please feel free to submit a Pull Request.
206
+
207
+ ## 📞 Support
208
+
209
+ If you encounter any issues or have questions, please open an issue on the repository.
210
+
211
+ ## 🔗 Related Projects
212
+
213
+ - **Ollama Space**: The backend API Space that this UI connects to
214
+ - **Ollama**: The local LLM runner that powers everything
215
+ - **Hugging Face Spaces**: The deployment platform for both Spaces
app.py ADDED
@@ -0,0 +1,611 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify, render_template_string
2
+ import os
3
+ import requests
4
+ import json
5
+ import logging
6
+ from typing import Dict, Any, List
7
+ import time
8
+
9
+ app = Flask(__name__)
10
+ logging.basicConfig(level=logging.INFO)
11
+
12
+ # Configuration
13
+ OLLAMA_API_URL = os.getenv('OLLAMA_API_URL', 'https://your-ollama-space.hf.space')
14
+ DEFAULT_MODEL = os.getenv('DEFAULT_MODEL', 'llama2')
15
+ MAX_TOKENS = int(os.getenv('MAX_TOKENS', '2048'))
16
+ TEMPERATURE = float(os.getenv('TEMPERATURE', '0.7'))
17
+
18
+ class OllamaClient:
19
+ def __init__(self, api_url: str):
20
+ self.api_url = api_url.rstrip('/')
21
+ self.available_models = []
22
+ self.refresh_models()
23
+
24
+ def refresh_models(self):
25
+ """Refresh the list of available models"""
26
+ try:
27
+ response = requests.get(f"{self.api_url}/api/models", timeout=10)
28
+ if response.status_code == 200:
29
+ data = response.json()
30
+ if data.get('status') == 'success':
31
+ self.available_models = data.get('models', [])
32
+ else:
33
+ self.available_models = []
34
+ else:
35
+ self.available_models = []
36
+ except Exception as e:
37
+ logging.error(f"Error refreshing models: {e}")
38
+ self.available_models = []
39
+
40
+ def list_models(self) -> List[str]:
41
+ """List all available models"""
42
+ self.refresh_models()
43
+ return self.available_models
44
+
45
+ def generate(self, model_name: str, prompt: str, **kwargs) -> Dict[str, Any]:
46
+ """Generate text using a model"""
47
+ try:
48
+ payload = {
49
+ "model": model_name,
50
+ "prompt": prompt,
51
+ "stream": False
52
+ }
53
+ payload.update(kwargs)
54
+
55
+ response = requests.post(f"{self.api_url}/api/generate",
56
+ json=payload,
57
+ timeout=120)
58
+
59
+ if response.status_code == 200:
60
+ data = response.json()
61
+ if data.get('status') == 'success':
62
+ return {
63
+ "status": "success",
64
+ "response": data.get('response', ''),
65
+ "model": model_name,
66
+ "usage": data.get('usage', {})
67
+ }
68
+ else:
69
+ return {"status": "error", "message": data.get('message', 'Unknown error')}
70
+ else:
71
+ return {"status": "error", "message": f"Generation failed: {response.text}"}
72
+ except Exception as e:
73
+ return {"status": "error", "message": str(e)}
74
+
75
+ def health_check(self) -> Dict[str, Any]:
76
+ """Check the health of the Ollama API"""
77
+ try:
78
+ response = requests.get(f"{self.api_url}/health", timeout=10)
79
+ if response.status_code == 200:
80
+ return response.json()
81
+ else:
82
+ return {"status": "unhealthy", "error": f"HTTP {response.status_code}"}
83
+ except Exception as e:
84
+ return {"status": "unhealthy", "error": str(e)}
85
+
86
+ # Initialize Ollama client
87
+ ollama_client = OllamaClient(OLLAMA_API_URL)
88
+
89
+ # HTML template for the chat interface
90
+ HTML_TEMPLATE = '''
91
+ <!DOCTYPE html>
92
+ <html lang="en">
93
+ <head>
94
+ <meta charset="UTF-8">
95
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
96
+ <title>OpenWebUI - Ollama Chat</title>
97
+ <style>
98
+ * {
99
+ margin: 0;
100
+ padding: 0;
101
+ box-sizing: border-box;
102
+ }
103
+
104
+ body {
105
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
106
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
107
+ min-height: 100vh;
108
+ padding: 20px;
109
+ }
110
+
111
+ .container {
112
+ max-width: 1200px;
113
+ margin: 0 auto;
114
+ background: white;
115
+ border-radius: 20px;
116
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
117
+ overflow: hidden;
118
+ }
119
+
120
+ .header {
121
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
122
+ color: white;
123
+ padding: 30px;
124
+ text-align: center;
125
+ }
126
+
127
+ .header h1 {
128
+ font-size: 2.5rem;
129
+ margin-bottom: 10px;
130
+ font-weight: 700;
131
+ }
132
+
133
+ .header p {
134
+ font-size: 1.1rem;
135
+ opacity: 0.9;
136
+ }
137
+
138
+ .controls {
139
+ padding: 20px 30px;
140
+ background: #f8f9fa;
141
+ border-bottom: 1px solid #e9ecef;
142
+ display: flex;
143
+ gap: 15px;
144
+ align-items: center;
145
+ flex-wrap: wrap;
146
+ }
147
+
148
+ .control-group {
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 8px;
152
+ }
153
+
154
+ .control-group label {
155
+ font-weight: 600;
156
+ color: #495057;
157
+ min-width: 80px;
158
+ }
159
+
160
+ .control-group select,
161
+ .control-group input {
162
+ padding: 8px 12px;
163
+ border: 2px solid #e9ecef;
164
+ border-radius: 8px;
165
+ font-size: 14px;
166
+ transition: border-color 0.3s;
167
+ }
168
+
169
+ .control-group select:focus,
170
+ .control-group input:focus {
171
+ outline: none;
172
+ border-color: #667eea;
173
+ }
174
+
175
+ .chat-container {
176
+ height: 500px;
177
+ overflow-y: auto;
178
+ padding: 20px;
179
+ background: #fafbfc;
180
+ }
181
+
182
+ .message {
183
+ margin-bottom: 20px;
184
+ display: flex;
185
+ gap: 15px;
186
+ }
187
+
188
+ .message.user {
189
+ flex-direction: row-reverse;
190
+ }
191
+
192
+ .message-avatar {
193
+ width: 40px;
194
+ height: 40px;
195
+ border-radius: 50%;
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ font-weight: bold;
200
+ color: white;
201
+ flex-shrink: 0;
202
+ }
203
+
204
+ .message.user .message-avatar {
205
+ background: #667eea;
206
+ }
207
+
208
+ .message.assistant .message-avatar {
209
+ background: #28a745;
210
+ }
211
+
212
+ .message-content {
213
+ background: white;
214
+ padding: 15px 20px;
215
+ border-radius: 18px;
216
+ max-width: 70%;
217
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
218
+ line-height: 1.5;
219
+ }
220
+
221
+ .message.user .message-content {
222
+ background: #667eea;
223
+ color: white;
224
+ }
225
+
226
+ .message.assistant .message-content {
227
+ background: white;
228
+ color: #333;
229
+ }
230
+
231
+ .input-container {
232
+ padding: 20px 30px;
233
+ background: white;
234
+ border-top: 1px solid #e9ecef;
235
+ }
236
+
237
+ .input-form {
238
+ display: flex;
239
+ gap: 15px;
240
+ }
241
+
242
+ .input-field {
243
+ flex: 1;
244
+ padding: 15px 20px;
245
+ border: 2px solid #e9ecef;
246
+ border-radius: 25px;
247
+ font-size: 16px;
248
+ transition: border-color 0.3s;
249
+ resize: none;
250
+ min-height: 50px;
251
+ max-height: 120px;
252
+ }
253
+
254
+ .input-field:focus {
255
+ outline: none;
256
+ border-color: #667eea;
257
+ }
258
+
259
+ .send-button {
260
+ padding: 15px 30px;
261
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
262
+ color: white;
263
+ border: none;
264
+ border-radius: 25px;
265
+ font-size: 16px;
266
+ font-weight: 600;
267
+ cursor: pointer;
268
+ transition: transform 0.2s;
269
+ min-width: 100px;
270
+ }
271
+
272
+ .send-button:hover {
273
+ transform: translateY(-2px);
274
+ }
275
+
276
+ .send-button:disabled {
277
+ opacity: 0.6;
278
+ cursor: not-allowed;
279
+ transform: none;
280
+ }
281
+
282
+ .status {
283
+ text-align: center;
284
+ padding: 10px;
285
+ font-size: 14px;
286
+ color: #6c757d;
287
+ }
288
+
289
+ .status.error {
290
+ color: #dc3545;
291
+ }
292
+
293
+ .status.success {
294
+ color: #28a745;
295
+ }
296
+
297
+ .typing-indicator {
298
+ display: none;
299
+ padding: 15px 20px;
300
+ background: white;
301
+ border-radius: 18px;
302
+ color: #6c757d;
303
+ font-style: italic;
304
+ }
305
+
306
+ @media (max-width: 768px) {
307
+ .controls {
308
+ flex-direction: column;
309
+ align-items: stretch;
310
+ }
311
+
312
+ .control-group {
313
+ justify-content: space-between;
314
+ }
315
+
316
+ .message-content {
317
+ max-width: 85%;
318
+ }
319
+ }
320
+ </style>
321
+ </head>
322
+ <body>
323
+ <div class="container">
324
+ <div class="header">
325
+ <h1>🤖 OpenWebUI</h1>
326
+ <p>Chat with your local Ollama models through Hugging Face Spaces</p>
327
+ </div>
328
+
329
+ <div class="controls">
330
+ <div class="control-group">
331
+ <label for="model-select">Model:</label>
332
+ <select id="model-select">
333
+ <option value="">Loading models...</option>
334
+ </select>
335
+ </div>
336
+
337
+ <div class="control-group">
338
+ <label for="temperature">Temperature:</label>
339
+ <input type="range" id="temperature" min="0" max="2" step="0.1" value="0.7">
340
+ <span id="temp-value">0.7</span>
341
+ </div>
342
+
343
+ <div class="control-group">
344
+ <label for="max-tokens">Max Tokens:</label>
345
+ <input type="number" id="max-tokens" min="1" max="4096" value="2048">
346
+ </div>
347
+ </div>
348
+
349
+ <div class="chat-container" id="chat-container">
350
+ <div class="message assistant">
351
+ <div class="message-avatar">AI</div>
352
+ <div class="message-content">
353
+ Hello! I'm your AI assistant powered by Ollama. How can I help you today?
354
+ </div>
355
+ </div>
356
+ </div>
357
+
358
+ <div class="typing-indicator" id="typing-indicator">
359
+ AI is thinking...
360
+ </div>
361
+
362
+ <div class="input-container">
363
+ <form class="input-form" id="chat-form">
364
+ <textarea
365
+ class="input-field"
366
+ id="message-input"
367
+ placeholder="Type your message here..."
368
+ rows="1"
369
+ ></textarea>
370
+ <button type="submit" class="send-button" id="send-button">
371
+ Send
372
+ </button>
373
+ </form>
374
+ </div>
375
+
376
+ <div class="status" id="status"></div>
377
+ </div>
378
+
379
+ <script>
380
+ let conversationHistory = [];
381
+
382
+ // Initialize the interface
383
+ document.addEventListener('DOMContentLoaded', function() {
384
+ loadModels();
385
+ setupEventListeners();
386
+ autoResizeTextarea();
387
+ });
388
+
389
+ function loadModels() {
390
+ fetch('{{ ollama_api_url }}/api/models')
391
+ .then(response => response.json())
392
+ .then(data => {
393
+ const modelSelect = document.getElementById('model-select');
394
+ modelSelect.innerHTML = '';
395
+
396
+ if (data.status === 'success' && data.models.length > 0) {
397
+ data.models.forEach(model => {
398
+ const option = document.createElement('option');
399
+ option.value = model;
400
+ option.textContent = model;
401
+ if (model === '{{ default_model }}') {
402
+ option.selected = true;
403
+ }
404
+ modelSelect.appendChild(option);
405
+ });
406
+ } else {
407
+ modelSelect.innerHTML = '<option value="">No models available</option>';
408
+ }
409
+ })
410
+ .catch(error => {
411
+ console.error('Error loading models:', error);
412
+ showStatus('Error loading models', 'error');
413
+ });
414
+ }
415
+
416
+ function setupEventListeners() {
417
+ // Chat form submission
418
+ document.getElementById('chat-form').addEventListener('submit', handleSubmit);
419
+
420
+ // Temperature slider
421
+ document.getElementById('temperature').addEventListener('input', function() {
422
+ document.getElementById('temp-value').textContent = this.value;
423
+ });
424
+
425
+ // Auto-resize textarea
426
+ document.getElementById('message-input').addEventListener('input', autoResizeTextarea);
427
+ }
428
+
429
+ function autoResizeTextarea() {
430
+ const textarea = document.getElementById('message-input');
431
+ textarea.style.height = 'auto';
432
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
433
+ }
434
+
435
+ function handleSubmit(e) {
436
+ e.preventDefault();
437
+
438
+ const messageInput = document.getElementById('message-input');
439
+ const message = messageInput.value.trim();
440
+
441
+ if (!message) return;
442
+
443
+ const model = document.getElementById('model-select').value;
444
+ const temperature = parseFloat(document.getElementById('temperature').value);
445
+ const maxTokens = parseInt(document.getElementById('max-tokens').value);
446
+
447
+ if (!model) {
448
+ showStatus('Please select a model', 'error');
449
+ return;
450
+ }
451
+
452
+ // Add user message to chat
453
+ addMessage(message, 'user');
454
+ messageInput.value = '';
455
+ autoResizeTextarea();
456
+
457
+ // Show typing indicator
458
+ showTypingIndicator(true);
459
+
460
+ // Send message to API
461
+ sendMessage(message, model, temperature, maxTokens);
462
+ }
463
+
464
+ function addMessage(content, sender) {
465
+ const chatContainer = document.getElementById('chat-container');
466
+ const messageDiv = document.createElement('div');
467
+ messageDiv.className = `message ${sender}`;
468
+
469
+ const avatar = document.createElement('div');
470
+ avatar.className = 'message-avatar';
471
+ avatar.textContent = sender === 'user' ? 'U' : 'AI';
472
+
473
+ const messageContent = document.createElement('div');
474
+ messageContent.className = 'message-content';
475
+ messageContent.textContent = content;
476
+
477
+ messageDiv.appendChild(avatar);
478
+ messageDiv.appendChild(messageContent);
479
+ chatContainer.appendChild(messageDiv);
480
+
481
+ // Scroll to bottom
482
+ chatContainer.scrollTop = chatContainer.scrollHeight;
483
+
484
+ // Add to conversation history
485
+ conversationHistory.push({ role: sender, content: content });
486
+ }
487
+
488
+ function sendMessage(message, model, temperature, maxTokens) {
489
+ const payload = {
490
+ model: model,
491
+ prompt: message,
492
+ temperature: temperature,
493
+ max_tokens: maxTokens
494
+ };
495
+
496
+ fetch('{{ ollama_api_url }}/api/generate', {
497
+ method: 'POST',
498
+ headers: {
499
+ 'Content-Type': 'application/json'
500
+ },
501
+ body: JSON.stringify(payload)
502
+ })
503
+ .then(response => response.json())
504
+ .then(data => {
505
+ showTypingIndicator(false);
506
+
507
+ if (data.status === 'success') {
508
+ addMessage(data.response, 'assistant');
509
+ showStatus(`Response generated using ${model}`, 'success');
510
+ } else {
511
+ addMessage('Sorry, I encountered an error while processing your request.', 'assistant');
512
+ showStatus(`Error: ${data.message}`, 'error');
513
+ }
514
+ })
515
+ .catch(error => {
516
+ showTypingIndicator(false);
517
+ addMessage('Sorry, I encountered an error while processing your request.', 'assistant');
518
+ showStatus('Network error occurred', 'error');
519
+ console.error('Error:', error);
520
+ });
521
+ }
522
+
523
+ function showTypingIndicator(show) {
524
+ const indicator = document.getElementById('typing-indicator');
525
+ indicator.style.display = show ? 'block' : 'none';
526
+
527
+ if (show) {
528
+ const chatContainer = document.getElementById('chat-container');
529
+ chatContainer.scrollTop = chatContainer.scrollHeight;
530
+ }
531
+ }
532
+
533
+ function showStatus(message, type = '') {
534
+ const statusDiv = document.getElementById('status');
535
+ statusDiv.textContent = message;
536
+ statusDiv.className = `status ${type}`;
537
+
538
+ // Clear status after 5 seconds
539
+ setTimeout(() => {
540
+ statusDiv.textContent = '';
541
+ statusDiv.className = 'status';
542
+ }, 5000);
543
+ }
544
+ </script>
545
+ </body>
546
+ </html>
547
+ '''
548
+
549
+ @app.route('/')
550
+ def home():
551
+ """Main chat interface"""
552
+ return render_template_string(HTML_TEMPLATE,
553
+ ollama_api_url=OLLAMA_API_URL,
554
+ default_model=DEFAULT_MODEL)
555
+
556
+ @app.route('/api/chat', methods=['POST'])
557
+ def chat():
558
+ """Chat API endpoint"""
559
+ try:
560
+ data = request.get_json()
561
+ if not data or 'message' not in data or 'model' not in data:
562
+ return jsonify({"status": "error", "message": "Message and model are required"}), 400
563
+
564
+ message = data['message']
565
+ model = data['model']
566
+ temperature = data.get('temperature', TEMPERATURE)
567
+ max_tokens = data.get('max_tokens', MAX_TOKENS)
568
+
569
+ result = ollama_client.generate(model, message,
570
+ temperature=temperature,
571
+ max_tokens=max_tokens)
572
+
573
+ if result["status"] == "success":
574
+ return jsonify(result)
575
+ else:
576
+ return jsonify(result), 500
577
+ except Exception as e:
578
+ return jsonify({"status": "error", "message": str(e)}), 500
579
+
580
+ @app.route('/api/models', methods=['GET'])
581
+ def get_models():
582
+ """Get available models"""
583
+ try:
584
+ models = ollama_client.list_models()
585
+ return jsonify({
586
+ "status": "success",
587
+ "models": models,
588
+ "count": len(models)
589
+ })
590
+ except Exception as e:
591
+ return jsonify({"status": "error", "message": str(e)}), 500
592
+
593
+ @app.route('/health', methods=['GET'])
594
+ def health_check():
595
+ """Health check endpoint"""
596
+ try:
597
+ ollama_health = ollama_client.health_check()
598
+ return jsonify({
599
+ "status": "healthy",
600
+ "ollama_api": ollama_health,
601
+ "timestamp": time.time()
602
+ })
603
+ except Exception as e:
604
+ return jsonify({
605
+ "status": "unhealthy",
606
+ "error": str(e),
607
+ "timestamp": time.time()
608
+ }), 500
609
+
610
+ if __name__ == '__main__':
611
+ app.run(host='0.0.0.0', port=7860, debug=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask==2.3.3
2
+ requests==2.31.0
3
+ gunicorn==21.2.0