Soumik555 commited on
Commit
bac60fc
Β·
0 Parent(s):
Files changed (6) hide show
  1. .dockerignore +0 -0
  2. Dockerfile +60 -0
  3. README.md +29 -0
  4. app.py +454 -0
  5. requirements.txt +13 -0
  6. static/index.html +386 -0
.dockerignore ADDED
File without changes
Dockerfile ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for optimized image
2
+ FROM python:3.9-slim as builder
3
+
4
+ # Install build dependencies
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Copy requirements and install Python dependencies
10
+ COPY requirements.txt .
11
+ RUN pip install --user --no-cache-dir -r requirements.txt
12
+
13
+ # Final stage
14
+ FROM python:3.9-slim
15
+
16
+ # Install runtime dependencies
17
+ RUN apt-get update && apt-get install -y \
18
+ curl \
19
+ git \
20
+ && rm -rf /var/lib/apt/lists/* \
21
+ && apt-get clean
22
+
23
+ # Copy installed packages from builder stage
24
+ COPY --from=builder /root/.local /root/.local
25
+
26
+ # Set working directory
27
+ WORKDIR /app
28
+
29
+ # Set environment variables for model caching
30
+ ENV PYTHONDONTWRITEBYTECODE=1 \
31
+ PYTHONUNBUFFERED=1 \
32
+ GRADIO_SERVER_NAME="0.0.0.0" \
33
+ GRADIO_SERVER_PORT=7860 \
34
+ PATH="/root/.local/bin:$PATH" \
35
+ TRANSFORMERS_CACHE=/app/model_cache \
36
+ HF_HOME=/app/hf_cache \
37
+ HUGGINGFACE_HUB_CACHE=/app/hf_cache
38
+
39
+ # Create cache directories with proper permissions
40
+ RUN mkdir -p /app/model_cache /app/hf_cache /app/static
41
+
42
+ # Copy application code
43
+ COPY . .
44
+
45
+ # Create non-root user and set permissions
46
+ RUN useradd -m -u 1000 user && \
47
+ chown -R user:user /app && \
48
+ chmod -R 755 /app
49
+
50
+ USER user
51
+
52
+ # Expose ports
53
+ EXPOSE 7860 8000
54
+
55
+ # Health check
56
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=90s --retries=3 \
57
+ CMD curl -f http://localhost:7860/health || exit 1
58
+
59
+ # Run the application
60
+ CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: FastAPI Chatbot
3
+ emoji: πŸ€–
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: apache-2.0
9
+ app_port: 7860
10
+ ---
11
+
12
+ # πŸ€– FastAPI Chatbot
13
+
14
+ A modern chatbot application built with FastAPI backend and Gradio frontend, optimized for Hugging Face Spaces deployment.
15
+
16
+ ## ✨ Features
17
+
18
+ - πŸš€ **FastAPI Backend** - RESTful API with automatic documentation
19
+ - 🎨 **Gradio Frontend** - Interactive web interface with advanced controls
20
+ - πŸ€– **Hugging Face Integration** - Easy model switching and caching
21
+ - 🐳 **Docker Deployment** - Optimized for Hugging Face Spaces
22
+ - πŸ“± **Responsive Design** - Works on desktop and mobile
23
+ - ⚑ **Model Caching** - Fast startup after initial load
24
+ - πŸ›‘οΈ **Error Handling** - Robust error handling and logging
25
+ - πŸ“Š **Health Monitoring** - Built-in health checks and status endpoints
26
+
27
+ ## 🎯 Live Demo
28
+
29
+ Visit the [Hugging Face Space](https://huggingface.co/spaces)
app.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from fastapi.staticfiles import StaticFiles
6
+ from fastapi.responses import FileResponse, JSONResponse
7
+ from pydantic import BaseModel
8
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
9
+ import torch
10
+ import logging
11
+ import threading
12
+ import uvicorn
13
+ from pathlib import Path
14
+ import time
15
+
16
+ # Configure logging
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
20
+ )
21
+ logger = logging.getLogger(__name__)
22
+
23
+ # FastAPI app
24
+ app = FastAPI(
25
+ title="FastAPI Chatbot",
26
+ description="Chatbot with FastAPI backend and Gradio frontend",
27
+ version="1.0.0"
28
+ )
29
+
30
+ # Add CORS middleware
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ # Pydantic models
40
+ class ChatRequest(BaseModel):
41
+ message: str
42
+ max_length: int = 100
43
+ temperature: float = 0.7
44
+ top_p: float = 0.9
45
+
46
+ class ChatResponse(BaseModel):
47
+ response: str
48
+ model_used: str
49
+ response_time: float
50
+
51
+ class HealthResponse(BaseModel):
52
+ status: str
53
+ model_loaded: bool
54
+ model_name: str
55
+ cache_directory: str
56
+ startup_time: float
57
+
58
+ # Global variables
59
+ tokenizer = None
60
+ model = None
61
+ generator = None
62
+ startup_time = time.time()
63
+
64
+ # Configuration
65
+ MODEL_NAME = os.getenv("MODEL_NAME", "microsoft/DialoGPT-medium")
66
+ CACHE_DIR = os.getenv("TRANSFORMERS_CACHE", "/app/model_cache")
67
+ MAX_LENGTH = int(os.getenv("MAX_LENGTH", "100"))
68
+ DEFAULT_TEMPERATURE = float(os.getenv("DEFAULT_TEMPERATURE", "0.7"))
69
+
70
+ def ensure_cache_dir():
71
+ """Ensure cache directory exists"""
72
+ Path(CACHE_DIR).mkdir(parents=True, exist_ok=True)
73
+ logger.info(f"Cache directory: {CACHE_DIR}")
74
+
75
+ def is_model_cached(model_name: str) -> bool:
76
+ """Check if model is already cached"""
77
+ model_path = Path(CACHE_DIR) / f"models--{model_name.replace('/', '--')}"
78
+ is_cached = model_path.exists() and any(model_path.iterdir())
79
+ logger.info(f"Model cached: {is_cached}")
80
+ return is_cached
81
+
82
+ def load_model():
83
+ """Load the Hugging Face model with caching"""
84
+ global tokenizer, model, generator
85
+
86
+ try:
87
+ ensure_cache_dir()
88
+
89
+ if is_model_cached(MODEL_NAME):
90
+ logger.info(f"βœ… Loading cached model: {MODEL_NAME}")
91
+ else:
92
+ logger.info(f"πŸ“₯ Downloading and caching model: {MODEL_NAME}")
93
+
94
+ start_time = time.time()
95
+
96
+ # Load tokenizer
97
+ tokenizer = AutoTokenizer.from_pretrained(
98
+ MODEL_NAME,
99
+ cache_dir=CACHE_DIR,
100
+ local_files_only=False
101
+ )
102
+
103
+ # Add padding token if it doesn't exist
104
+ if tokenizer.pad_token is None:
105
+ tokenizer.pad_token = tokenizer.eos_token
106
+
107
+ # Load model with optimization
108
+ model = AutoModelForCausalLM.from_pretrained(
109
+ MODEL_NAME,
110
+ cache_dir=CACHE_DIR,
111
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
112
+ low_cpu_mem_usage=True,
113
+ local_files_only=False
114
+ )
115
+
116
+ # Create text generation pipeline
117
+ device = 0 if torch.cuda.is_available() else -1
118
+ generator = pipeline(
119
+ "text-generation",
120
+ model=model,
121
+ tokenizer=tokenizer,
122
+ device=device,
123
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
124
+ )
125
+
126
+ load_time = time.time() - start_time
127
+ logger.info(f"βœ… Model loaded successfully in {load_time:.2f} seconds!")
128
+
129
+ return True
130
+
131
+ except Exception as e:
132
+ logger.error(f"❌ Error loading model: {str(e)}")
133
+ return False
134
+
135
+ def generate_response(message: str, max_length: int = 100, temperature: float = 0.7, top_p: float = 0.9) -> str:
136
+ """Generate response using the loaded model"""
137
+ if not generator:
138
+ return "❌ Model not loaded. Please wait for initialization..."
139
+
140
+ try:
141
+ start_time = time.time()
142
+
143
+ # Generate response with parameters
144
+ response = generator(
145
+ message,
146
+ max_length=max_length,
147
+ temperature=temperature,
148
+ top_p=top_p,
149
+ num_return_sequences=1,
150
+ pad_token_id=tokenizer.eos_token_id,
151
+ do_sample=True,
152
+ truncation=True,
153
+ repetition_penalty=1.1
154
+ )
155
+
156
+ # Extract generated text
157
+ generated_text = response[0]['generated_text']
158
+
159
+ # Clean up response
160
+ if generated_text.startswith(message):
161
+ bot_response = generated_text[len(message):].strip()
162
+ else:
163
+ bot_response = generated_text.strip()
164
+
165
+ # Fallback if empty response
166
+ if not bot_response:
167
+ bot_response = "I'm not sure how to respond to that. Could you try rephrasing?"
168
+
169
+ response_time = time.time() - start_time
170
+ logger.info(f"Generated response in {response_time:.2f}s")
171
+
172
+ return bot_response, response_time
173
+
174
+ except Exception as e:
175
+ logger.error(f"Error generating response: {str(e)}")
176
+ return f"❌ Error generating response: {str(e)}", 0.0
177
+
178
+ # FastAPI endpoints
179
+ @app.get("/", response_class=FileResponse)
180
+ async def serve_frontend():
181
+ """Serve the frontend HTML file"""
182
+ html_path = Path("static/index.html")
183
+ if html_path.exists():
184
+ return FileResponse("static/index.html")
185
+ else:
186
+ return JSONResponse(
187
+ content={"message": "Frontend not available. Use /docs for API documentation."},
188
+ status_code=200
189
+ )
190
+
191
+ @app.get("/health", response_model=HealthResponse)
192
+ async def health_check():
193
+ """Health check endpoint with detailed information"""
194
+ return HealthResponse(
195
+ status="healthy" if model is not None else "initializing",
196
+ model_loaded=model is not None,
197
+ model_name=MODEL_NAME,
198
+ cache_directory=CACHE_DIR,
199
+ startup_time=time.time() - startup_time
200
+ )
201
+
202
+ @app.post("/chat", response_model=ChatResponse)
203
+ async def chat_endpoint(request: ChatRequest):
204
+ """Chat endpoint for API access"""
205
+ if not generator:
206
+ raise HTTPException(
207
+ status_code=503,
208
+ detail="Model not loaded yet. Please wait for initialization."
209
+ )
210
+
211
+ # Validate input
212
+ if not request.message.strip():
213
+ raise HTTPException(status_code=400, detail="Message cannot be empty")
214
+
215
+ if len(request.message) > 1000:
216
+ raise HTTPException(status_code=400, detail="Message too long (max 1000 characters)")
217
+
218
+ # Generate response
219
+ response_text, response_time = generate_response(
220
+ request.message.strip(),
221
+ request.max_length,
222
+ request.temperature,
223
+ request.top_p
224
+ )
225
+
226
+ return ChatResponse(
227
+ response=response_text,
228
+ model_used=MODEL_NAME,
229
+ response_time=response_time
230
+ )
231
+
232
+ @app.get("/model-info")
233
+ async def get_model_info():
234
+ """Get detailed model information"""
235
+ return {
236
+ "model_name": MODEL_NAME,
237
+ "model_loaded": model is not None,
238
+ "device": "cuda" if torch.cuda.is_available() else "cpu",
239
+ "cache_directory": CACHE_DIR,
240
+ "model_cached": is_model_cached(MODEL_NAME),
241
+ "parameters": {
242
+ "max_length": MAX_LENGTH,
243
+ "default_temperature": DEFAULT_TEMPERATURE
244
+ }
245
+ }
246
+
247
+ @app.get("/status")
248
+ async def get_status():
249
+ """Get current application status"""
250
+ return {
251
+ "status": "running",
252
+ "model_ready": model is not None,
253
+ "uptime": time.time() - startup_time,
254
+ "endpoints": ["/", "/health", "/chat", "/model-info", "/docs"]
255
+ }
256
+
257
+ # Mount static files if directory exists
258
+ if Path("static").exists():
259
+ app.mount("/static", StaticFiles(directory="static"), name="static")
260
+
261
+ # Gradio interface
262
+ def chat_with_bot(message, history, max_length, temperature, top_p):
263
+ """Gradio chat function with advanced parameters"""
264
+ if not message.strip():
265
+ return "Please enter a message."
266
+
267
+ response_text, _ = generate_response(message.strip(), max_length, temperature, top_p)
268
+ return response_text
269
+
270
+ def create_gradio_interface():
271
+ """Create enhanced Gradio interface"""
272
+
273
+ # Custom CSS
274
+ css = """
275
+ .gradio-container {
276
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
277
+ max-width: 1200px;
278
+ margin: 0 auto;
279
+ }
280
+ .chat-message {
281
+ font-size: 14px !important;
282
+ line-height: 1.4;
283
+ }
284
+ .gradio-chatbot {
285
+ height: 500px;
286
+ }
287
+ """
288
+
289
+ # Create interface with advanced controls
290
+ with gr.Blocks(css=css, title="FastAPI Chatbot", theme=gr.themes.Soft()) as demo:
291
+
292
+ gr.HTML("<h1 style='text-align: center; color: #2563eb;'>πŸ€– FastAPI Chatbot</h1>")
293
+ gr.HTML(f"<p style='text-align: center; color: #6b7280;'>Powered by {MODEL_NAME}</p>")
294
+
295
+ with gr.Row():
296
+ with gr.Column(scale=3):
297
+ chatbot = gr.Chatbot(
298
+ height=500,
299
+ show_copy_button=True,
300
+ bubble_full_width=False,
301
+ avatar_images=("πŸ‘€", "πŸ€–")
302
+ )
303
+
304
+ with gr.Row():
305
+ msg = gr.Textbox(
306
+ placeholder="Type your message here...",
307
+ container=False,
308
+ scale=4,
309
+ max_lines=3
310
+ )
311
+ submit_btn = gr.Button("Send πŸ“€", scale=1, variant="primary")
312
+
313
+ with gr.Row():
314
+ clear_btn = gr.Button("Clear Chat πŸ—‘οΈ", scale=1)
315
+ retry_btn = gr.Button("Retry Last ↻", scale=1)
316
+
317
+ with gr.Column(scale=1):
318
+ gr.HTML("<h3>Settings</h3>")
319
+
320
+ max_length = gr.Slider(
321
+ minimum=50,
322
+ maximum=200,
323
+ value=MAX_LENGTH,
324
+ step=10,
325
+ label="Max Response Length"
326
+ )
327
+
328
+ temperature = gr.Slider(
329
+ minimum=0.1,
330
+ maximum=1.5,
331
+ value=DEFAULT_TEMPERATURE,
332
+ step=0.1,
333
+ label="Temperature (Creativity)"
334
+ )
335
+
336
+ top_p = gr.Slider(
337
+ minimum=0.1,
338
+ maximum=1.0,
339
+ value=0.9,
340
+ step=0.05,
341
+ label="Top-p (Focus)"
342
+ )
343
+
344
+ gr.HTML("<h4>Example Messages:</h4>")
345
+ examples = gr.Examples(
346
+ examples=[
347
+ ["Hello! How are you today?"],
348
+ ["Tell me a joke"],
349
+ ["What's your favorite hobby?"],
350
+ ["Can you help me with a creative writing prompt?"],
351
+ ["What do you think about technology?"]
352
+ ],
353
+ inputs=msg,
354
+ label="Click to try:"
355
+ )
356
+
357
+ # Event handlers
358
+ def respond(message, history, max_len, temp, top_p):
359
+ if not message.strip():
360
+ return history, ""
361
+
362
+ # Add user message
363
+ history.append([message, None])
364
+
365
+ # Generate bot response
366
+ bot_response = chat_with_bot(message, history, max_len, temp, top_p)
367
+ history[-1][1] = bot_response
368
+
369
+ return history, ""
370
+
371
+ def clear_chat():
372
+ return [], ""
373
+
374
+ def retry_last(history, max_len, temp, top_p):
375
+ if not history:
376
+ return history
377
+
378
+ last_user_msg = history[-1][0]
379
+ history[-1][1] = "Thinking..."
380
+
381
+ # Regenerate response
382
+ bot_response = chat_with_bot(last_user_msg, history, max_len, temp, top_p)
383
+ history[-1][1] = bot_response
384
+
385
+ return history
386
+
387
+ # Wire up events
388
+ submit_btn.click(
389
+ respond,
390
+ [msg, chatbot, max_length, temperature, top_p],
391
+ [chatbot, msg]
392
+ )
393
+
394
+ msg.submit(
395
+ respond,
396
+ [msg, chatbot, max_length, temperature, top_p],
397
+ [chatbot, msg]
398
+ )
399
+
400
+ clear_btn.click(clear_chat, outputs=[chatbot, msg])
401
+ retry_btn.click(retry_last, [chatbot, max_length, temperature, top_p], chatbot)
402
+
403
+ return demo
404
+
405
+ def run_fastapi():
406
+ """Run FastAPI server"""
407
+ uvicorn.run(
408
+ app,
409
+ host="0.0.0.0",
410
+ port=8000,
411
+ log_level="info",
412
+ access_log=True
413
+ )
414
+
415
+ def main():
416
+ """Main function to run both FastAPI and Gradio"""
417
+ logger.info("πŸš€ Starting FastAPI Chatbot...")
418
+
419
+ # Load model first
420
+ logger.info("πŸ“¦ Loading model...")
421
+ model_loaded = load_model()
422
+
423
+ if not model_loaded:
424
+ logger.error("❌ Failed to load model. Exiting...")
425
+ return
426
+
427
+ logger.info("βœ… Model loaded successfully!")
428
+
429
+ # Create Gradio interface
430
+ logger.info("🎨 Creating Gradio interface...")
431
+ demo = create_gradio_interface()
432
+
433
+ # Start FastAPI server in a separate thread
434
+ logger.info("🌐 Starting FastAPI server...")
435
+ fastapi_thread = threading.Thread(target=run_fastapi, daemon=True)
436
+ fastapi_thread.start()
437
+
438
+ # Launch Gradio interface
439
+ logger.info("πŸš€ Launching Gradio interface...")
440
+ demo.launch(
441
+ server_name="0.0.0.0",
442
+ server_port=7860,
443
+ share=False,
444
+ show_error=True,
445
+ quiet=False,
446
+ show_api=False
447
+ )
448
+
449
+ if __name__ == "__main__":
450
+ main()
451
+
452
+
453
+
454
+ git remote add origin https://cronjob-python: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@huggingface.co/spaces/cronjob-python/chatbot
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ transformers==4.35.2
4
+ torch==2.1.0+cpu
5
+ tokenizers==0.15.0
6
+ accelerate==0.24.1
7
+ gradio==4.7.1
8
+ requests==2.31.0
9
+ numpy==1.24.3
10
+ pydantic==2.4.2
11
+ python-multipart==0.0.6
12
+ jinja2==3.1.2
13
+ aiofiles==23.2.1
static/index.html ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>FastAPI Chatbot</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .chat-container {
25
+ background: rgba(255, 255, 255, 0.95);
26
+ backdrop-filter: blur(10px);
27
+ border-radius: 20px;
28
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
29
+ width: 100%;
30
+ max-width: 800px;
31
+ height: 600px;
32
+ display: flex;
33
+ flex-direction: column;
34
+ overflow: hidden;
35
+ }
36
+
37
+ .chat-header {
38
+ background: linear-gradient(135deg, #2563eb, #1d4ed8);
39
+ color: white;
40
+ padding: 20px;
41
+ text-align: center;
42
+ }
43
+
44
+ .chat-header h1 {
45
+ font-size: 24px;
46
+ margin-bottom: 5px;
47
+ }
48
+
49
+ .chat-header p {
50
+ font-size: 14px;
51
+ opacity: 0.8;
52
+ }
53
+
54
+ .messages {
55
+ flex: 1;
56
+ overflow-y: auto;
57
+ padding: 20px;
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 15px;
61
+ }
62
+
63
+ .message {
64
+ max-width: 80%;
65
+ padding: 12px 16px;
66
+ border-radius: 18px;
67
+ font-size: 14px;
68
+ line-height: 1.4;
69
+ animation: fadeIn 0.3s ease-out;
70
+ }
71
+
72
+ @keyframes fadeIn {
73
+ from { opacity: 0; transform: translateY(10px); }
74
+ to { opacity: 1; transform: translateY(0); }
75
+ }
76
+
77
+ .user-message {
78
+ background: #2563eb;
79
+ color: white;
80
+ align-self: flex-end;
81
+ border-bottom-right-radius: 6px;
82
+ }
83
+
84
+ .bot-message {
85
+ background: #f3f4f6;
86
+ color: #1f2937;
87
+ align-self: flex-start;
88
+ border-bottom-left-radius: 6px;
89
+ border: 1px solid #e5e7eb;
90
+ }
91
+
92
+ .loading-message {
93
+ background: #f3f4f6;
94
+ color: #6b7280;
95
+ align-self: flex-start;
96
+ border-bottom-left-radius: 6px;
97
+ border: 1px solid #e5e7eb;
98
+ font-style: italic;
99
+ }
100
+
101
+ .loading-dots::after {
102
+ content: '';
103
+ animation: loading 1.4s infinite;
104
+ }
105
+
106
+ @keyframes loading {
107
+ 0%, 20% { content: '.'; }
108
+ 40% { content: '..'; }
109
+ 60%, 100% { content: '...'; }
110
+ }
111
+
112
+ .input-container {
113
+ padding: 20px;
114
+ border-top: 1px solid #e5e7eb;
115
+ background: white;
116
+ }
117
+
118
+ .input-row {
119
+ display: flex;
120
+ gap: 12px;
121
+ align-items: flex-end;
122
+ }
123
+
124
+ .input-group {
125
+ flex: 1;
126
+ position: relative;
127
+ }
128
+
129
+ #messageInput {
130
+ width: 100%;
131
+ min-height: 44px;
132
+ max-height: 120px;
133
+ padding: 12px 16px;
134
+ border: 2px solid #e5e7eb;
135
+ border-radius: 22px;
136
+ font-size: 14px;
137
+ resize: none;
138
+ transition: border-color 0.2s;
139
+ font-family: inherit;
140
+ }
141
+
142
+ #messageInput:focus {
143
+ outline: none;
144
+ border-color: #2563eb;
145
+ }
146
+
147
+ .send-button {
148
+ width: 44px;
149
+ height: 44px;
150
+ border: none;
151
+ border-radius: 50%;
152
+ background: #2563eb;
153
+ color: white;
154
+ font-size: 16px;
155
+ cursor: pointer;
156
+ transition: all 0.2s;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ flex-shrink: 0;
161
+ }
162
+
163
+ .send-button:hover:not(:disabled) {
164
+ background: #1d4ed8;
165
+ transform: scale(1.05);
166
+ }
167
+
168
+ .send-button:disabled {
169
+ background: #9ca3af;
170
+ cursor: not-allowed;
171
+ transform: none;
172
+ }
173
+
174
+ .controls {
175
+ display: flex;
176
+ gap: 8px;
177
+ margin-top: 12px;
178
+ justify-content: center;
179
+ }
180
+
181
+ .control-btn {
182
+ padding: 6px 12px;
183
+ border: 1px solid #d1d5db;
184
+ border-radius: 16px;
185
+ background: white;
186
+ color: #6b7280;
187
+ font-size: 12px;
188
+ cursor: pointer;
189
+ transition: all 0.2s;
190
+ }
191
+
192
+ .control-btn:hover {
193
+ background: #f9fafb;
194
+ border-color: #9ca3af;
195
+ }
196
+
197
+ .status-indicator {
198
+ position: absolute;
199
+ top: 10px;
200
+ right: 10px;
201
+ width: 8px;
202
+ height: 8px;
203
+ border-radius: 50%;
204
+ background: #10b981;
205
+ }
206
+
207
+ @media (max-width: 600px) {
208
+ .chat-container {
209
+ height: 100vh;
210
+ border-radius: 0;
211
+ max-width: none;
212
+ }
213
+
214
+ body {
215
+ padding: 0;
216
+ }
217
+
218
+ .message {
219
+ max-width: 90%;
220
+ }
221
+ }
222
+ </style>
223
+ </head>
224
+ <body>
225
+ <div class="chat-container">
226
+ <div class="status-indicator" id="statusIndicator"></div>
227
+
228
+ <div class="chat-header">
229
+ <h1>πŸ€– FastAPI Chatbot</h1>
230
+ <p>Powered by Hugging Face Transformers</p>
231
+ </div>
232
+
233
+ <div id="messages" class="messages">
234
+ <div class="message bot-message">
235
+ πŸ‘‹ Hello! I'm your AI assistant. How can I help you today?
236
+ </div>
237
+ </div>
238
+
239
+ <div class="input-container">
240
+ <div class="input-row">
241
+ <div class="input-group">
242
+ <textarea
243
+ id="messageInput"
244
+ placeholder="Type your message here..."
245
+ rows="1"
246
+ ></textarea>
247
+ </div>
248
+ <button id="sendBtn" class="send-button" title="Send message">
249
+ ➀
250
+ </button>
251
+ </div>
252
+ <div class="controls">
253
+ <button class="control-btn" onclick="clearChat()">πŸ—‘οΈ Clear</button>
254
+ <button class="control-btn" onclick="checkHealth()">πŸ“‘ Status</button>
255
+ <button class="control-btn" onclick="showExamples()">πŸ’‘ Examples</button>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <script>
261
+ const messagesDiv = document.getElementById('messages');
262
+ const messageInput = document.getElementById('messageInput');
263
+ const sendBtn = document.getElementById('sendBtn');
264
+ const statusIndicator = document.getElementById('statusIndicator');
265
+
266
+ // Auto-resize textarea
267
+ messageInput.addEventListener('input', function() {
268
+ this.style.height = 'auto';
269
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
270
+ });
271
+
272
+ function addMessage(message, isUser = false, isLoading = false) {
273
+ const messageDiv = document.createElement('div');
274
+ messageDiv.className = `message ${isUser ? 'user-message' : isLoading ? 'loading-message loading-dots' : 'bot-message'}`;
275
+ messageDiv.textContent = message;
276
+ messagesDiv.appendChild(messageDiv);
277
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
278
+ return messageDiv;
279
+ }
280
+
281
+ function setStatus(online) {
282
+ statusIndicator.style.background = online ? '#10b981' : '#ef4444';
283
+ }
284
+
285
+ async function sendMessage() {
286
+ const message = messageInput.value.trim();
287
+ if (!message) return;
288
+
289
+ // Disable input
290
+ sendBtn.disabled = true;
291
+ messageInput.disabled = true;
292
+
293
+ // Add user message
294
+ addMessage(message, true);
295
+ messageInput.value = '';
296
+ messageInput.style.height = 'auto';
297
+
298
+ // Add loading message
299
+ const loadingDiv = addMessage('Bot is thinking', false, true);
300
+
301
+ try {
302
+ const response = await fetch('/chat', {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/json' },
305
+ body: JSON.stringify({
306
+ message: message,
307
+ max_length: 100,
308
+ temperature: 0.7,
309
+ top_p: 0.9
310
+ })
311
+ });
312
+
313
+ const data = await response.json();
314
+
315
+ // Remove loading message
316
+ messagesDiv.removeChild(loadingDiv);
317
+
318
+ if (response.ok) {
319
+ addMessage(data.response);
320
+ setStatus(true);
321
+ } else {
322
+ addMessage(`❌ Error: ${data.detail}`);
323
+ setStatus(false);
324
+ }
325
+ } catch (error) {
326
+ messagesDiv.removeChild(loadingDiv);
327
+ addMessage('❌ Connection error. Please try again.');
328
+ setStatus(false);
329
+ }
330
+
331
+ // Re-enable input
332
+ sendBtn.disabled = false;
333
+ messageInput.disabled = false;
334
+ messageInput.focus();
335
+ }
336
+
337
+ function clearChat() {
338
+ const messages = messagesDiv.querySelectorAll('.message:not(:first-child)');
339
+ messages.forEach(msg => msg.remove());
340
+ }
341
+
342
+ async function checkHealth() {
343
+ try {
344
+ const response = await fetch('/health');
345
+ const data = await response.json();
346
+ const status = data.model_loaded ? 'βœ… Online' : '⏳ Loading';
347
+ addMessage(`Status: ${status} | Model: ${data.model_name}`);
348
+ setStatus(data.model_loaded);
349
+ } catch (error) {
350
+ addMessage('❌ Unable to check status');
351
+ setStatus(false);
352
+ }
353
+ }
354
+
355
+ function showExamples() {
356
+ const examples = [
357
+ "Hello! How are you today?",
358
+ "Tell me a joke",
359
+ "What's your favorite hobby?",
360
+ "Can you help me brainstorm ideas?",
361
+ "What do you think about AI?"
362
+ ];
363
+
364
+ const randomExample = examples[Math.floor(Math.random() * examples.length)];
365
+ messageInput.value = randomExample;
366
+ messageInput.focus();
367
+ }
368
+
369
+ // Event listeners
370
+ sendBtn.addEventListener('click', sendMessage);
371
+
372
+ messageInput.addEventListener('keydown', function(e) {
373
+ if (e.key === 'Enter' && !e.shiftKey) {
374
+ e.preventDefault();
375
+ sendMessage();
376
+ }
377
+ });
378
+
379
+ // Initial health check
380
+ setTimeout(checkHealth, 1000);
381
+
382
+ // Focus input on load
383
+ messageInput.focus();
384
+ </script>
385
+ </body>
386
+ </html>