Diego Adame commited on
Commit
1eaa5ce
·
1 Parent(s): 5422771

Complete Code

Browse files
Files changed (8) hide show
  1. .gradio/certificate.pem +31 -0
  2. README.md +155 -1
  3. render.yaml +9 -0
  4. requirements.txt +23 -0
  5. runtime.txt +1 -0
  6. server.log +13 -0
  7. server.py +154 -0
  8. static/index.html +295 -0
.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
31
+ -----END CERTIFICATE-----
README.md CHANGED
@@ -1 +1,155 @@
1
- # LocalInference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI Chat & Summarization Web App 🤖
2
+
3
+ A beautiful web-based AI application featuring **Chat Generation** and **Text Summarization** powered by Hugging Face models.
4
+
5
+ ## Features ✨
6
+
7
+ - 💬 **Chat Generation**: Interactive AI chat using Qwen 1.5 0.5B Chat model
8
+ - 📝 **Text Summarization**: Summarize long texts using DistilBART model
9
+ - 🎨 **Beautiful UI**: Modern gradient design with smooth animations
10
+ - 🌐 **Accessible**: Publicly deployable and accessible to everyone
11
+ - ⚡ **Fast**: Lightweight models optimized for quick responses
12
+
13
+ ## Models Used
14
+
15
+ - **Chat**: `Qwen/Qwen1.5-0.5B-Chat` - Lightweight conversational AI
16
+ - **Summarization**: `sshleifer/distilbart-cnn-6-6` - Efficient text summarization
17
+
18
+ ## Local Development
19
+
20
+ ### Prerequisites
21
+ - Python 3.12+
22
+ - pip
23
+
24
+ ### Installation
25
+
26
+ 1. Clone the repository:
27
+ ```bash
28
+ git clone https://github.com/DiegoAdame13322/LocalInference.git
29
+ cd LocalInference
30
+ ```
31
+
32
+ 2. Install dependencies:
33
+ ```bash
34
+ pip install -r requirements.txt
35
+ ```
36
+
37
+ 3. Run the server:
38
+ ```bash
39
+ python server.py
40
+ ```
41
+
42
+ 4. Open your browser to `http://localhost:8000`
43
+
44
+ ## Deploy to Render 🚀
45
+
46
+ ### Option 1: One-Click Deploy (Recommended)
47
+
48
+ 1. Fork this repository to your GitHub account
49
+ 2. Go to [Render Dashboard](https://dashboard.render.com/)
50
+ 3. Click "New +" → "Web Service"
51
+ 4. Connect your GitHub repository
52
+ 5. Render will automatically detect the `render.yaml` file
53
+ 6. Click "Create Web Service"
54
+
55
+ ### Option 2: Manual Deploy
56
+
57
+ 1. Go to [Render Dashboard](https://dashboard.render.com/)
58
+ 2. Click "New +" → "Web Service"
59
+ 3. Connect your repository
60
+ 4. Configure:
61
+ - **Name**: `ai-chat-summarization`
62
+ - **Environment**: `Python`
63
+ - **Build Command**: `pip install -r requirements.txt`
64
+ - **Start Command**: `python server.py`
65
+ - **Instance Type**: Free or Starter (Starter recommended for better performance)
66
+
67
+ 5. Click "Create Web Service"
68
+
69
+ ### Important Notes for Render Deployment
70
+
71
+ - ⚠️ **First startup takes 5-10 minutes** as models download (1.5GB+)
72
+ - 💾 **Disk space**: Free tier has 512MB, models need ~1.5GB. Use **Starter plan** or higher
73
+ - 🔄 **Auto-sleep**: Free tier sleeps after 15min of inactivity, takes ~30s to wake up
74
+ - 🎯 **Recommendation**: Use **Starter plan ($7/month)** for:
75
+ - More disk space
76
+ - Better performance
77
+ - No auto-sleep
78
+
79
+ ## API Endpoints
80
+
81
+ ### Chat Generation
82
+ ```bash
83
+ POST /api/chat
84
+ Content-Type: application/json
85
+
86
+ {
87
+ "message": "What is machine learning?",
88
+ "max_new_tokens": 150,
89
+ "temperature": 0.7
90
+ }
91
+ ```
92
+
93
+ ### Text Summarization
94
+ ```bash
95
+ POST /api/summarize
96
+ Content-Type: application/json
97
+
98
+ {
99
+ "text": "Your long text here...",
100
+ "max_length": 130,
101
+ "min_length": 30
102
+ }
103
+ ```
104
+
105
+ ### Health Check
106
+ ```bash
107
+ GET /api/health
108
+ ```
109
+
110
+ ## Project Structure
111
+
112
+ ```
113
+ LocalInference/
114
+ ├── server.py # FastAPI backend with model loading
115
+ ├── static/
116
+ │ └── index.html # Frontend web interface
117
+ ├── requirements.txt # Python dependencies
118
+ ├── render.yaml # Render deployment config
119
+ ├── runtime.txt # Python version specification
120
+ └── README.md # This file
121
+ ```
122
+
123
+ ## Tech Stack
124
+
125
+ - **Backend**: FastAPI, PyTorch, Transformers
126
+ - **Frontend**: HTML5, CSS3, JavaScript (Vanilla)
127
+ - **Models**: Hugging Face Transformers
128
+ - **Deployment**: Render
129
+
130
+ ## Troubleshooting
131
+
132
+ ### Models not loading on Render
133
+ - Upgrade to Starter plan for more disk space
134
+ - Check logs in Render dashboard
135
+
136
+ ### Slow first response
137
+ - Models load on first request, subsequent requests are faster
138
+ - Consider keeping the service warm with periodic requests
139
+
140
+ ### Out of memory errors
141
+ - Reduce `max_new_tokens` in chat requests
142
+ - Use Starter plan or higher for more RAM
143
+
144
+ ## License
145
+
146
+ MIT License - feel free to use and modify!
147
+
148
+ ## Contributing
149
+
150
+ Pull requests are welcome! For major changes, please open an issue first.
151
+
152
+ ---
153
+
154
+ Made with ❤️ using Hugging Face Transformers
155
+
render.yaml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ - type: web
3
+ name: ai-chat-summarization
4
+ env: python
5
+ buildCommand: pip install -r requirements.txt
6
+ startCommand: python server.py
7
+ envVars:
8
+ - key: PYTHON_VERSION
9
+ value: 3.12.0
requirements.txt ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastAPI and web server
2
+ fastapi==0.115.5
3
+ uvicorn[standard]==0.32.1
4
+ pydantic==2.10.2
5
+
6
+ # Transformers and ML
7
+ transformers==4.46.3
8
+ torch==2.5.1
9
+ accelerate==1.1.1
10
+
11
+ # Tokenizers
12
+ sentencepiece==0.2.0
13
+ tokenizers==0.20.3
14
+
15
+ # Additional dependencies for the models
16
+ safetensors==0.4.5
17
+ huggingface-hub==0.26.2
18
+
19
+ # For Python multipart support (if needed for file uploads)
20
+ python-multipart==0.0.12
21
+
22
+ # Optional but recommended for better performance
23
+ einops==0.8.0
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.12.0
server.log ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ nohup: ignoring input
2
+ Loading chat generation model...
3
+ Device set to use cpu
4
+ Loading summarization model...
5
+ Device set to use cpu
6
+ INFO: Started server process [27577]
7
+ INFO: Waiting for application startup.
8
+ INFO: Application startup complete.
9
+ INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
10
+ INFO: 127.0.0.1:51690 - "GET /api/health HTTP/1.1" 200 OK
11
+ INFO: 127.0.0.1:52708 - "POST /api/chat HTTP/1.1" 200 OK
12
+ INFO: 127.0.0.1:48190 - "POST /api/summarize HTTP/1.1" 200 OK
13
+ INFO: 127.0.0.1:50980 - "GET / HTTP/1.1" 200 OK
server.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
+ from pydantic import BaseModel
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
7
+ import torch, uvicorn, os, subprocess, threading, shutil, time
8
+
9
+ # =====================================================
10
+ # FastAPI App Setup
11
+ # =====================================================
12
+ app = FastAPI(title="AI Chat + Summarization API")
13
+
14
+ # Allow frontend requests
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"],
18
+ allow_credentials=True,
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ # =====================================================
24
+ # Automatic Disk Cleanup (safety for Codespaces)
25
+ # =====================================================
26
+ def check_disk_space(min_gb=2):
27
+ stat = shutil.disk_usage("/")
28
+ free_gb = stat.free / (1024 ** 3)
29
+ if free_gb < min_gb:
30
+ print(f"⚠️ Low disk space ({free_gb:.2f} GB). Clearing Hugging Face cache...")
31
+ os.system("rm -rf ~/.cache/huggingface/*")
32
+
33
+ def background_health_monitor():
34
+ while True:
35
+ check_disk_space()
36
+ time.sleep(600) # every 10 minutes
37
+
38
+ threading.Thread(target=background_health_monitor, daemon=True).start()
39
+
40
+ # =====================================================
41
+ # Load Chat Model (Lightweight Qwen)
42
+ # =====================================================
43
+ print("Loading lightweight chat model (Qwen 1.5 0.5B Chat)…")
44
+ chat_model_name = "Qwen/Qwen1.5-0.5B-Chat"
45
+ chat_tokenizer = AutoTokenizer.from_pretrained(chat_model_name)
46
+ chat_model = AutoModelForCausalLM.from_pretrained(
47
+ chat_model_name,
48
+ dtype=torch.bfloat16,
49
+ low_cpu_mem_usage=True,
50
+ ).eval()
51
+
52
+ # =====================================================
53
+ # Load Summarization Model
54
+ # =====================================================
55
+ print("Loading summarization model...")
56
+ summary_pipe = pipeline(
57
+ "summarization",
58
+ model="sshleifer/distilbart-cnn-6-6",
59
+ device=0 if torch.cuda.is_available() else -1
60
+ )
61
+
62
+ # =====================================================
63
+ # Request Models
64
+ # =====================================================
65
+ class ChatRequest(BaseModel):
66
+ message: str
67
+ max_new_tokens: int = 80
68
+ temperature: float = 0.7
69
+
70
+ class SummaryRequest(BaseModel):
71
+ text: str
72
+ max_length: int = 100
73
+ min_length: int = 25
74
+
75
+ # =====================================================
76
+ # Chat Endpoint (Fixed for Qwen 1.5 Chat)
77
+ # =====================================================
78
+ @app.post("/api/chat")
79
+ def chat_generate(req: ChatRequest):
80
+ try:
81
+ # Proper message template for Qwen 1.5 Chat
82
+ prompt = (
83
+ "<|im_start|>system\nYou are a helpful AI assistant.<|im_end|>\n"
84
+ f"<|im_start|>user\n{req.message}<|im_end|>\n"
85
+ "<|im_start|>assistant\n"
86
+ )
87
+
88
+ # Tokenize and run inference
89
+ inputs = chat_tokenizer(prompt, return_tensors="pt").to(chat_model.device)
90
+ outputs = chat_model.generate(
91
+ **inputs,
92
+ max_new_tokens=req.max_new_tokens,
93
+ temperature=req.temperature,
94
+ do_sample=True,
95
+ top_p=0.9,
96
+ eos_token_id=chat_tokenizer.eos_token_id,
97
+ pad_token_id=chat_tokenizer.eos_token_id,
98
+ )
99
+
100
+ # Decode only newly generated tokens
101
+ new_tokens = outputs[0][inputs["input_ids"].size(1):]
102
+ reply = chat_tokenizer.decode(new_tokens, skip_special_tokens=True).strip()
103
+
104
+ # Fallback in case of empty output
105
+ if not reply:
106
+ reply = chat_tokenizer.decode(outputs[0], skip_special_tokens=True).strip()
107
+
108
+ return {"success": True, "response": reply}
109
+
110
+ except Exception as e:
111
+ return {"success": False, "error": str(e)}
112
+
113
+ # =====================================================
114
+ # Summarization Endpoint
115
+ # =====================================================
116
+ @app.post("/api/summarize")
117
+ def summarize_text(req: SummaryRequest):
118
+ try:
119
+ result = summary_pipe(
120
+ req.text,
121
+ max_length=req.max_length,
122
+ min_length=min(req.min_length, req.max_length // 2),
123
+ truncation=True,
124
+ )
125
+ key = "summary_text" if "summary_text" in result[0] else "generated_text"
126
+ return {"success": True, "summary": result[0][key].strip()}
127
+ except Exception as e:
128
+ return {"success": False, "error": str(e)}
129
+
130
+ # =====================================================
131
+ # Health + Static Routes
132
+ # =====================================================
133
+ @app.get("/api/health")
134
+ def health_check():
135
+ return {"status": "healthy", "models": ["chat: Qwen-0.5B-Chat", "summarization: DistilBART-6-6"]}
136
+
137
+ if os.path.exists("static"):
138
+ app.mount("/static", StaticFiles(directory="static"), name="static")
139
+
140
+ @app.get("/")
141
+ def read_root():
142
+ if os.path.exists("static/index.html"):
143
+ return FileResponse("static/index.html")
144
+ return {"message": "AI Chat & Summarization API running!"}
145
+
146
+ # =====================================================
147
+ # Run FastAPI Server
148
+ # =====================================================
149
+ if __name__ == "__main__":
150
+ # Get port from environment variable (Render provides this) or default to 8000
151
+ port = int(os.environ.get("PORT", 8000))
152
+
153
+ print(f"🚀 Starting FastAPI server on http://0.0.0.0:{port}")
154
+ uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")
static/index.html ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>AI Chat & Summarization</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ min-height: 100vh;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ padding: 20px;
17
+ }
18
+ .container {
19
+ background: white;
20
+ border-radius: 20px;
21
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
22
+ max-width: 900px;
23
+ width: 100%;
24
+ overflow: hidden;
25
+ }
26
+ .header {
27
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
28
+ color: white;
29
+ padding: 30px;
30
+ text-align: center;
31
+ }
32
+ .header h1 { font-size: 2.5em; margin-bottom: 10px; }
33
+ .header p { font-size: 1.1em; opacity: 0.9; }
34
+ .tabs {
35
+ display: flex;
36
+ background: #f5f5f5;
37
+ border-bottom: 2px solid #e0e0e0;
38
+ }
39
+ .tab {
40
+ flex: 1;
41
+ padding: 20px;
42
+ text-align: center;
43
+ cursor: pointer;
44
+ font-size: 1.1em;
45
+ font-weight: 600;
46
+ transition: all 0.3s;
47
+ border: none;
48
+ background: transparent;
49
+ color: #666;
50
+ }
51
+ .tab:hover { background: #e8e8e8; }
52
+ .tab.active {
53
+ background: white;
54
+ color: #667eea;
55
+ border-bottom: 3px solid #667eea;
56
+ }
57
+ .content { padding: 30px; }
58
+ .tab-content { display: none; animation: fadeIn 0.3s; }
59
+ .tab-content.active { display: block; }
60
+ @keyframes fadeIn {
61
+ from { opacity: 0; transform: translateY(10px); }
62
+ to { opacity: 1; transform: translateY(0); }
63
+ }
64
+ .input-group { margin-bottom: 20px; }
65
+ label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; }
66
+ textarea, input {
67
+ width: 100%; padding: 15px; border: 2px solid #e0e0e0;
68
+ border-radius: 10px; font-size: 1em; font-family: inherit;
69
+ transition: border-color 0.3s; resize: vertical;
70
+ }
71
+ textarea:focus, input:focus { outline: none; border-color: #667eea; }
72
+ .btn {
73
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
74
+ color: white; padding: 15px 40px; border: none; border-radius: 10px;
75
+ font-size: 1.1em; font-weight: 600; cursor: pointer;
76
+ transition: transform 0.2s, box-shadow 0.2s; width: 100%;
77
+ }
78
+ .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 20px rgba(102,126,234,0.4); }
79
+ .btn:active { transform: translateY(0); }
80
+ .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
81
+ .response-box {
82
+ margin-top: 20px; padding: 20px; background: #f8f9fa;
83
+ border-radius: 10px; border-left: 4px solid #667eea;
84
+ display: none; animation: slideIn 0.3s;
85
+ }
86
+ @keyframes slideIn {
87
+ from { opacity: 0; transform: translateX(-10px); }
88
+ to { opacity: 1; transform: translateX(0); }
89
+ }
90
+ .response-box.show { display: block; }
91
+ .response-box h3 { margin-bottom: 10px; color: #667eea; }
92
+ .response-text { color: #333; line-height: 1.6; white-space: pre-wrap; }
93
+ .error { background: #fee; border-left-color: #f44; }
94
+ .error h3 { color: #f44; }
95
+ .loading { text-align: center; padding: 20px; color: #667eea; display: none; }
96
+ .loading.show { display: block; }
97
+ .spinner {
98
+ border: 4px solid #f3f3f3; border-top: 4px solid #667eea;
99
+ border-radius: 50%; width: 40px; height: 40px;
100
+ animation: spin 1s linear infinite; margin: 0 auto 10px;
101
+ }
102
+ @keyframes spin { 0% { transform: rotate(0deg);} 100% {transform: rotate(360deg);} }
103
+ .chat-history {
104
+ max-height: 400px; overflow-y: auto; margin-bottom: 20px;
105
+ padding: 15px; background: #f8f9fa; border-radius: 10px;
106
+ }
107
+ .chat-message { margin-bottom: 15px; padding: 10px 15px; border-radius: 10px; }
108
+ .chat-message.user {
109
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
110
+ color: white; margin-left: 20%;
111
+ }
112
+ .chat-message.assistant {
113
+ background: white; border: 2px solid #e0e0e0; margin-right: 20%;
114
+ }
115
+ .settings {
116
+ display: grid; grid-template-columns: 1fr 1fr;
117
+ gap: 15px; margin-bottom: 20px;
118
+ }
119
+ .settings .input-group { margin-bottom: 0; }
120
+ @media (max-width: 600px) {
121
+ .settings { grid-template-columns: 1fr; }
122
+ .chat-message.user { margin-left: 10%; }
123
+ .chat-message.assistant { margin-right: 10%; }
124
+ }
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <div class="container">
129
+ <div class="header">
130
+ <h1>🤖 AI Assistant</h1>
131
+ <p>Chat Generation & Text Summarization</p>
132
+ </div>
133
+
134
+ <div class="tabs">
135
+ <button class="tab active" onclick="switchTab('chat')">💬 Chat Generation</button>
136
+ <button class="tab" onclick="switchTab('summarize')">📝 Text Summarization</button>
137
+ </div>
138
+
139
+ <div class="content">
140
+ <!-- Chat Tab -->
141
+ <div id="chat" class="tab-content active">
142
+ <div class="chat-history" id="chatHistory"></div>
143
+ <div class="input-group">
144
+ <label for="chatMessage">Your Message</label>
145
+ <textarea id="chatMessage" rows="3" placeholder="Type your message here..."></textarea>
146
+ </div>
147
+ <div class="settings">
148
+ <div class="input-group">
149
+ <label for="maxTokens">Max Tokens</label>
150
+ <input type="number" id="maxTokens" value="150" min="50" max="500">
151
+ </div>
152
+ <div class="input-group">
153
+ <label for="temperature">Temperature</label>
154
+ <input type="number" id="temperature" value="0.7" min="0" max="2" step="0.1">
155
+ </div>
156
+ </div>
157
+ <button class="btn" onclick="sendChat()">Send Message</button>
158
+ <div class="loading" id="chatLoading">
159
+ <div class="spinner"></div>
160
+ <p>Generating response...</p>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Summarize Tab -->
165
+ <div id="summarize" class="tab-content">
166
+ <div class="input-group">
167
+ <label for="summaryText">Text to Summarize</label>
168
+ <textarea id="summaryText" rows="8" placeholder="Paste your text here for summarization..."></textarea>
169
+ </div>
170
+
171
+ <div class="settings">
172
+ <div class="input-group">
173
+ <label for="maxLength">Max Length</label>
174
+ <input type="number" id="maxLength" value="130" min="30" max="300">
175
+ </div>
176
+ <div class="input-group">
177
+ <label for="minLength">Min Length</label>
178
+ <input type="number" id="minLength" value="30" min="10" max="100">
179
+ </div>
180
+ </div>
181
+
182
+ <button class="btn" onclick="summarizeText()">Summarize</button>
183
+
184
+ <div class="loading" id="summaryLoading">
185
+ <div class="spinner"></div>
186
+ <p>Summarizing text...</p>
187
+ </div>
188
+
189
+ <div class="response-box" id="summaryResponse">
190
+ <h3>Summary</h3>
191
+ <div class="response-text" id="summaryOutput"></div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+
197
+ <script>
198
+ let chatHistory = [];
199
+
200
+ function switchTab(tabName) {
201
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
202
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
203
+ document.getElementById(tabName).classList.add('active');
204
+ event.target.classList.add('active');
205
+ }
206
+
207
+ async function sendChat() {
208
+ const message = document.getElementById('chatMessage').value;
209
+ const maxTokens = parseInt(document.getElementById('maxTokens').value);
210
+ const temperature = parseFloat(document.getElementById('temperature').value);
211
+ if (!message.trim()) { alert('Please enter a message'); return; }
212
+ addMessageToHistory('user', message);
213
+ document.getElementById('chatLoading').classList.add('show');
214
+ document.querySelector('#chat .btn').disabled = true;
215
+ try {
216
+ const res = await fetch('/api/chat', {
217
+ method: 'POST',
218
+ headers: {'Content-Type': 'application/json'},
219
+ body: JSON.stringify({ message, max_new_tokens: maxTokens, temperature })
220
+ });
221
+ const data = await res.json();
222
+ if (data.success) {
223
+ addMessageToHistory('assistant', data.response);
224
+ document.getElementById('chatMessage').value = '';
225
+ } else {
226
+ addMessageToHistory('assistant', `Error: ${data.error}`);
227
+ }
228
+ } catch (err) {
229
+ addMessageToHistory('assistant', `Error: ${err.message}`);
230
+ } finally {
231
+ document.getElementById('chatLoading').classList.remove('show');
232
+ document.querySelector('#chat .btn').disabled = false;
233
+ }
234
+ }
235
+
236
+ function addMessageToHistory(role, content) {
237
+ const history = document.getElementById('chatHistory');
238
+ const msg = document.createElement('div');
239
+ msg.className = `chat-message ${role}`;
240
+ msg.textContent = content;
241
+ history.appendChild(msg);
242
+ history.scrollTop = history.scrollHeight;
243
+ }
244
+
245
+ async function summarizeText() {
246
+ const text = document.getElementById('summaryText').value;
247
+ const maxLength = parseInt(document.getElementById('maxLength').value);
248
+ const minLength = parseInt(document.getElementById('minLength').value);
249
+ if (!text.trim()) { alert('Please enter text to summarize'); return; }
250
+
251
+ const summaryLoading = document.getElementById('summaryLoading');
252
+ const summaryResponse = document.getElementById('summaryResponse');
253
+ const summaryOutput = document.getElementById('summaryOutput');
254
+ const summarizeBtn = document.querySelector('#summarize .btn');
255
+
256
+ summaryLoading.classList.add('show');
257
+ summaryResponse.classList.remove('show', 'error');
258
+ summarizeBtn.disabled = true;
259
+
260
+ try {
261
+ const res = await fetch('/api/summarize', {
262
+ method: 'POST',
263
+ headers: {'Content-Type': 'application/json'},
264
+ body: JSON.stringify({ text, max_length: maxLength, min_length: minLength })
265
+ });
266
+ const data = await res.json();
267
+
268
+ if (data.success && data.summary && data.summary.trim()) {
269
+ summaryOutput.textContent = data.summary.trim();
270
+ summaryResponse.classList.remove('error');
271
+ } else {
272
+ summaryOutput.textContent = `Error: ${data.error || 'No summary returned.'}`;
273
+ summaryResponse.classList.add('error');
274
+ }
275
+
276
+ summaryResponse.classList.add('show');
277
+ } catch (err) {
278
+ summaryResponse.classList.add('error', 'show');
279
+ summaryOutput.textContent = `Error: ${err.message}`;
280
+ } finally {
281
+ summaryLoading.classList.remove('show');
282
+ summarizeBtn.disabled = false;
283
+ }
284
+ }
285
+
286
+ document.getElementById('chatMessage').addEventListener('keypress', e => {
287
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); }
288
+ });
289
+
290
+ window.onload = () => {
291
+ addMessageToHistory('assistant', "Hello! I'm your AI assistant. How can I help you today?");
292
+ };
293
+ </script>
294
+ </body>
295
+ </html>