codebyam commited on
Commit
26cf2e1
·
verified ·
1 Parent(s): 0e82c7c

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +55 -0
  2. app.py +398 -0
  3. start.sh +14 -0
Dockerfile ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:22.04
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && \
5
+ apt-get install -y \
6
+ build-essential \
7
+ libssl-dev \
8
+ zlib1g-dev \
9
+ libboost-math-dev \
10
+ libboost-python-dev \
11
+ libboost-timer-dev \
12
+ libboost-thread-dev \
13
+ libboost-system-dev \
14
+ libboost-filesystem-dev \
15
+ libopenblas-dev \
16
+ libomp-dev \
17
+ cmake \
18
+ pkg-config \
19
+ git \
20
+ python3-pip \
21
+ curl \
22
+ libcurl4-openssl-dev \
23
+ wget && \
24
+ rm -rf /var/lib/apt/lists/*
25
+
26
+ # Build llama.cpp with OpenBLAS
27
+ RUN git clone https://github.com/ggerganov/llama.cpp && \
28
+ cd llama.cpp && \
29
+ cmake -B build -S . \
30
+ -DLLAMA_BUILD_SERVER=ON \
31
+ -DLLAMA_BUILD_EXAMPLES=ON \
32
+ -DGGML_BLAS=ON \
33
+ -DGGML_BLAS_VENDOR=OpenBLAS \
34
+ -DCMAKE_BUILD_TYPE=Release && \
35
+ cmake --build build --config Release --target llama-server -j $(nproc)
36
+
37
+ RUN cd /llama.cpp/build && ./bin/llama-server --list-devices
38
+
39
+ # Download model
40
+ RUN mkdir -p /models && \
41
+ wget -O /models/model.q8_0.gguf https://huggingface.co/hugging-quants/Llama-3.2-3B-Instruct-Q8_0-GGUF/resolve/main/llama-3.2-3b-instruct-q8_0.gguf
42
+
43
+
44
+ RUN pip install fastapi uvicorn openai
45
+
46
+ # Copy app and startup script
47
+ COPY app.py /app.py
48
+ COPY start.sh /start.sh
49
+ RUN chmod +x /start.sh
50
+
51
+ # Expose ports
52
+ EXPOSE 7860 8080
53
+
54
+ # Start services
55
+ CMD ["/start.sh"]
app.py ADDED
@@ -0,0 +1,398 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Response, Cookie
2
+ from fastapi.responses import HTMLResponse
3
+ from pydantic import BaseModel, Field
4
+ import time
5
+
6
+ app = FastAPI()
7
+
8
+ Tokens = []
9
+ History = []
10
+
11
+ @app.get("/", response_class=HTMLResponse)
12
+ async def read_root(response: Response):
13
+ token = time.time()
14
+ Tokens.append(token)
15
+ response.set_cookie(key="token", value=token, httponly=True, samesite='strict') # Set cookie
16
+ return '''<!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
+ <title>Chatbot Assistant</title>
22
+ <script src="https://cdn.tailwindcss.com"></script>
23
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
24
+ <style>
25
+ body {
26
+ font-family: 'Inter', sans-serif;
27
+ background-color: #1a202c; /* Dark background for the body */
28
+ display: flex;
29
+ justify-content: center;
30
+ align-items: center;
31
+ min-height: 100vh;
32
+ margin: 0;
33
+ padding: 16px;
34
+ box-sizing: border-box;
35
+ color: #e2e8f0; /* Light text color for general body text */
36
+ }
37
+ .chat-container {
38
+ display: flex;
39
+ flex-direction: column;
40
+ width: 100%;
41
+ max-width: 800px;
42
+ height: 90vh;
43
+ background-color: #2d3748; /* Darker background for chat box */
44
+ border-radius: 1.5rem;
45
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.3); /* More pronounced shadow for dark mode */
46
+ overflow: hidden;
47
+ border: 1px solid #4a5568; /* Subtle border for dark mode */
48
+ }
49
+ .chat-header {
50
+ background: linear-gradient(to right, #2c5282, #4c78a8); /* Darker blue gradient header */
51
+ color: #ffffff;
52
+ padding: 1.5rem;
53
+ text-align: center;
54
+ font-size: 1.75rem;
55
+ font-weight: 700;
56
+ border-top-left-radius: 1.5rem;
57
+ border-top-right-radius: 1.5rem;
58
+ }
59
+ .chat-messages {
60
+ flex-grow: 1;
61
+ padding: 1.5rem;
62
+ overflow-y: auto;
63
+ -webkit-overflow-scrolling: touch;
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 1rem;
67
+ }
68
+ .message {
69
+ max-width: 75%;
70
+ padding: 0.85rem 1.25rem;
71
+ border-radius: 1.25rem;
72
+ word-wrap: break-word;
73
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* Subtle shadow for messages */
74
+ animation: fadeIn 0.3s ease-out;
75
+ }
76
+ .user-message {
77
+ background-color: #4299e1; /* Blue for user messages in dark mode */
78
+ align-self: flex-end;
79
+ color: #ffffff; /* White text for user messages */
80
+ border-bottom-right-radius: 0.5rem;
81
+ }
82
+ .assistant-message {
83
+ background-color: #4a5568; /* Darker gray for assistant messages */
84
+ align-self: flex-start;
85
+ color: #e2e8f0; /* Light text for assistant messages */
86
+ border-bottom-left-radius: 0.5rem;
87
+ }
88
+ .input-area {
89
+ display: flex;
90
+ align-items: center;
91
+ padding: 1.5rem;
92
+ border-top: 1px solid #4a5568; /* Darker border at the top */
93
+ background-color: #2d3748; /* Match chat box background */
94
+ gap: 1rem;
95
+ }
96
+ .input-area textarea {
97
+ flex-grow: 1;
98
+ padding: 0.85rem 1.25rem;
99
+ border: 2px solid #718096; /* Lighter border for contrast */
100
+ border-radius: 1.25rem;
101
+ resize: none;
102
+ outline: none;
103
+ font-size: 1rem;
104
+ line-height: 1.5;
105
+ min-height: 48px;
106
+ max-height: 150px;
107
+ overflow-y: auto;
108
+ transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
109
+ background-color: #2d3748; /* Dark background for textarea */
110
+ color: #e2e8f0; /* Light text color for textarea */
111
+ }
112
+ .input-area textarea:focus {
113
+ border-color: #63b3ed; /* Lighter blue border on focus */
114
+ box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.2); /* Lighter blue shadow on focus */
115
+ }
116
+ .input-area button {
117
+ background: linear-gradient(to right, #4299e1, #3182ce); /* Blue gradient button */
118
+ color: #ffffff;
119
+ padding: 0.85rem 1.75rem;
120
+ border-radius: 1.25rem;
121
+ font-weight: 600;
122
+ cursor: pointer;
123
+ transition: all 0.2s ease-in-out;
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ gap: 0.5rem;
128
+ box-shadow: 0 4px 10px rgba(66, 153, 225, 0.3);
129
+ transform: translateY(0);
130
+ }
131
+ .input-area button:hover {
132
+ background: linear-gradient(to right, #3182ce, #2b6cb0);
133
+ box-shadow: 0 6px 15px rgba(66, 153, 225, 0.4);
134
+ transform: translateY(-2px);
135
+ }
136
+ .input-area button:disabled {
137
+ background: #4a5568; /* Dark gray when disabled */
138
+ cursor: not-allowed;
139
+ box-shadow: none;
140
+ transform: translateY(0);
141
+ }
142
+ /* Summarize button specific styles for dark mode */
143
+ #summarize-button {
144
+ background: linear-gradient(to right, #805ad5, #6b46c1); /* Darker purple gradient */
145
+ box-shadow: 0 4px 10px rgba(128, 90, 213, 0.3);
146
+ }
147
+ #summarize-button:hover {
148
+ background: linear-gradient(to right, #6b46c1, #553c9a);
149
+ box-shadow: 0 6px 15px rgba(128, 90, 213, 0.4);
150
+ }
151
+
152
+ .loading-indicator {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 0.5rem;
156
+ font-style: italic;
157
+ color: #a0aec0; /* Lighter gray for loading text */
158
+ align-self: flex-start;
159
+ animation: fadeIn 0.3s ease-out;
160
+ }
161
+ .loading-dot {
162
+ width: 8px;
163
+ height: 8px;
164
+ background-color: #a0aec0; /* Lighter gray for dots */
165
+ border-radius: 50%;
166
+ animation: bounce 1.4s infinite ease-in-out both;
167
+ }
168
+ .loading-dot:nth-child(1) { animation-delay: -0.32s; }
169
+ .loading-dot:nth-child(2) { animation-delay: -0.16s; }
170
+ .loading-dot:nth-child(3) { animation-delay: 0s; }
171
+
172
+ @keyframes bounce {
173
+ 0%, 80%, 100% { transform: scale(0); }
174
+ 40% { transform: scale(1); }
175
+ }
176
+ @keyframes fadeIn {
177
+ from { opacity: 0; transform: translateY(10px); }
178
+ to { opacity: 1; transform: translateY(0); }
179
+ }
180
+
181
+ /* Responsive adjustments (same as before, unaffected by dark mode colors) */
182
+ @media (max-width: 768px) {
183
+ body {
184
+ padding: 0;
185
+ }
186
+ .chat-container {
187
+ height: 100vh;
188
+ border-radius: 0;
189
+ box-shadow: none;
190
+ border: none;
191
+ }
192
+ .chat-header {
193
+ border-radius: 0;
194
+ font-size: 1.5rem;
195
+ padding: 1rem;
196
+ }
197
+ .chat-messages {
198
+ padding: 1rem;
199
+ gap: 0.75rem;
200
+ }
201
+ .message {
202
+ max-width: 85%;
203
+ padding: 0.75rem 1rem;
204
+ border-radius: 1rem;
205
+ }
206
+ .input-area {
207
+ flex-direction: column;
208
+ padding: 1rem;
209
+ gap: 0.75rem;
210
+ }
211
+ .input-area textarea {
212
+ width: 100%;
213
+ min-height: 40px;
214
+ padding: 0.75rem 1rem;
215
+ border-radius: 1rem;
216
+ }
217
+ .input-area button {
218
+ width: 100%;
219
+ padding: 0.75rem 1rem;
220
+ border-radius: 1rem;
221
+ }
222
+ }
223
+ </style>
224
+ </head>
225
+ <body class="antialiased">
226
+ <div class="chat-container">
227
+ <div class="chat-header">
228
+ Chat Assistant
229
+ </div>
230
+
231
+ <div id="chat-messages" class="chat-messages">
232
+ </div>
233
+
234
+ <div class="input-area">
235
+ <textarea
236
+ id="user-input"
237
+ placeholder="Type your message..."
238
+ rows="1"
239
+ class="shadow-sm block w-full"
240
+ ></textarea>
241
+ <button id="send-button">
242
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
243
+ <path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.553.894l5 1.429a1 1 0 001.169-1.409l-7-14z" />
244
+ </svg>
245
+ Send
246
+ </button>
247
+ <button id="summarize-button" style="display:none;">
248
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
249
+ <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0113 3.414L16.586 7A2 2 0 0117 8.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1-3a1 1 0 000 2h.01a1 1 0 000-2H7zm0 6a1 1 0 000 2h.01a1 1 0 000-2H7zm3-6a1 1 0 000 2h3a1 1 0 100-2h-3zm0 6a1 1 0 000 2h3a1 1 0 100-2h-3z" clip-rule="evenodd" />
250
+ </svg>
251
+ Summarize Chat
252
+ </button>
253
+ </div>
254
+ </div>
255
+
256
+ <script>
257
+ const DOM = {
258
+ chatMessages: document.getElementById('chat-messages'),
259
+ userInput: document.getElementById('user-input'),
260
+ sendButton: document.getElementById('send-button'),
261
+ summarizeButton: document.getElementById('summarize-button'),
262
+ };
263
+
264
+ const chatHistory = [];
265
+
266
+ /**
267
+ * Adds a message to the chat display and updates chat history.
268
+ * @param {string} text - The message content.
269
+ * @param {'user'|'assistant'|'initial-assistant'} sender - The sender of the message.
270
+ */
271
+ function addMessage(text, sender) {
272
+ const messageDiv = document.createElement('div');
273
+ messageDiv.classList.add('message', sender === 'user' ? 'user-message' : 'assistant-message');
274
+ messageDiv.textContent = text;
275
+ DOM.chatMessages.appendChild(messageDiv);
276
+ DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight;
277
+
278
+ if (sender !== 'initial-assistant') {
279
+ chatHistory.push({ role: sender, parts: [{ text: text }] });
280
+ }
281
+ }
282
+
283
+ /** Shows a loading indicator in the chat. */
284
+ function showLoadingIndicator() {
285
+ const loadingDiv = document.createElement('div');
286
+ loadingDiv.id = 'loading-indicator';
287
+ loadingDiv.classList.add('loading-indicator');
288
+ loadingDiv.innerHTML = `
289
+ <span>Assistant is typing</span>
290
+ <div class="loading-dot"></div>
291
+ <div class="loading-dot"></div>
292
+ <div class="loading-dot"></div>
293
+ `;
294
+ DOM.chatMessages.appendChild(loadingDiv);
295
+ DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight;
296
+ }
297
+
298
+ /** Removes the loading indicator from the chat. */
299
+ function removeLoadingIndicator() {
300
+ document.getElementById('loading-indicator')?.remove();
301
+ }
302
+
303
+ /** Sets the disabled state of input and buttons. */
304
+ function setControlsDisabled(disabled) {
305
+ DOM.sendButton.disabled = disabled;
306
+ DOM.summarizeButton.disabled = disabled;
307
+ DOM.userInput.disabled = disabled;
308
+ }
309
+
310
+ /** Sends a user message to the backend API. */
311
+ async function sendMessage() {
312
+ const message = DOM.userInput.value.trim();
313
+ if (!message) return;
314
+
315
+ addMessage(message, 'user');
316
+ DOM.userInput.value = '';
317
+ setControlsDisabled(true);
318
+ showLoadingIndicator();
319
+
320
+ try {
321
+ const response = await fetch(`/response`, {
322
+ method: 'POST',
323
+ headers: { 'Content-Type': 'application/json' },
324
+ body: JSON.stringify({ prompt: message }),
325
+ });
326
+
327
+ if (!response.ok) {
328
+ throw new Error(`HTTP error! status: ${response.status}`);
329
+ }
330
+
331
+ const data = await response.json();
332
+ addMessage(data.text || "Sorry, I couldn't get a response.", 'assistant');
333
+ } catch (error) {
334
+ console.error('Error sending message:', error);
335
+ addMessage('Error: Could not connect to the assistant. Please try again.', 'assistant');
336
+ } finally {
337
+ removeLoadingIndicator();
338
+ setControlsDisabled(false);
339
+ DOM.userInput.focus();
340
+ adjustTextareaHeight();
341
+ }
342
+ }
343
+
344
+ /** Dynamically adjusts the textarea height. */
345
+ function adjustTextareaHeight() {
346
+ DOM.userInput.style.height = 'auto';
347
+ DOM.userInput.style.height = (DOM.userInput.scrollHeight) + 'px';
348
+ }
349
+
350
+ // Event Listeners
351
+ DOM.sendButton.addEventListener('click', sendMessage);
352
+ DOM.summarizeButton.addEventListener('click', summarizeChat);
353
+
354
+ DOM.userInput.addEventListener('keypress', (event) => {
355
+ if (event.key === 'Enter' && !event.shiftKey) {
356
+ event.preventDefault();
357
+ sendMessage();
358
+ }
359
+ });
360
+
361
+ DOM.userInput.addEventListener('input', adjustTextareaHeight);
362
+
363
+ // Initial setup
364
+ document.addEventListener('DOMContentLoaded', () => {
365
+ addMessage('Hello! How can I assist you today?', 'initial-assistant');
366
+ DOM.userInput.focus();
367
+ adjustTextareaHeight(); // Set initial height for empty textarea
368
+ });
369
+ </script>
370
+ </body>
371
+ </html>'''
372
+
373
+ from openai import OpenAI
374
+
375
+ client = OpenAI(base_url="http://localhost:8080/v1", api_key="no-key-required")
376
+
377
+ class ChatRequest(BaseModel):
378
+ """Request model for the chat endpoint."""
379
+ prompt: str
380
+
381
+ @app.post("/response")
382
+ async def handle_chat(chat_request: ChatRequest, token: str = Cookie(None)):
383
+ if token in Tokens:
384
+ i = Tokens.index(token)
385
+ History[i].append({"role": "user", "content": chat_request.prompt})
386
+
387
+ stream = client.chat.completions.create(
388
+ model="",
389
+ messages=History[i],
390
+ )
391
+ History[i].append({"role": "assistant", "content": chat_request.prompt})
392
+ return stream.choices[0].message.content
393
+ else: return 'Please stop. Just refresh the page.'
394
+
395
+
396
+ if __name__ == "__main__":
397
+ import uvicorn
398
+ uvicorn.run(app, host="0.0.0.0", port=7860)
start.sh ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Start llama-server in background
4
+ cd /llama.cpp/build
5
+ ./bin/llama-server --host 0.0.0.0 --port 8080 --model /models/model.q8_0.gguf --ctx-size 8192 --threads 2 &
6
+
7
+ # Wait for server to initialize
8
+ echo "Waiting for server to start..."
9
+ until curl -s "http://localhost:8080/v1/models" >/dev/null; do
10
+ sleep 1
11
+ done
12
+
13
+ cd /
14
+ python3 app.py