ruthran commited on
Commit
4a49bc3
·
verified ·
1 Parent(s): 2afc84a

Upload folder using huggingface_hub

Browse files
Files changed (9) hide show
  1. .gitignore +22 -0
  2. Dockerfile +22 -0
  3. README.md +33 -5
  4. app.py +94 -0
  5. branding.json +29 -0
  6. requirements.txt +3 -0
  7. static/css/style.css +505 -0
  8. static/js/chat.js +150 -0
  9. templates/index.html +162 -0
.gitignore ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ venv/
8
+ env/
9
+ .env
10
+
11
+ # IDE
12
+ .vscode/
13
+ .idea/
14
+ *.swp
15
+ *.swo
16
+
17
+ # OS
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+ # Logs
22
+ *.log
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1
7
+
8
+ # Install dependencies
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ # Copy application
13
+ COPY . .
14
+
15
+ # Create non-root user
16
+ RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
17
+ USER appuser
18
+
19
+ EXPOSE 7860
20
+
21
+ # Run with gunicorn for production
22
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--threads", "4", "app:app"]
README.md CHANGED
@@ -1,10 +1,38 @@
1
  ---
2
- title: Chatbot About Hereandnowai
3
- emoji: 📈
4
  colorFrom: yellow
5
- colorTo: blue
6
  sdk: docker
7
- pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: HERE AND NOW AI Chatbot
3
+ emoji: 🤖
4
  colorFrom: yellow
5
+ colorTo: green
6
  sdk: docker
7
+ pinned: true
8
+ license: apache-2.0
9
  ---
10
 
11
+ # HERE AND NOW AI Chatbot
12
+
13
+ A beautiful, hand-drawn style AI chatbot powered by Gemma-3-27b, built with a unique wax crayon design aesthetic.
14
+
15
+ ## Features
16
+
17
+ - 🎨 **Wax Crayon Design** - Beautiful hand-drawn aesthetic with notebook paper effects
18
+ - 💬 **Conversational AI** - Powered by Google's Gemma-3-27b model
19
+ - 🐕 **Meet Caramel** - Our friendly AI assistant avatar
20
+ - 📱 **Responsive** - Works beautifully on all devices
21
+
22
+ ## About HERE AND NOW AI
23
+
24
+ We believe **AI is Good**. Visit us at [hereandnowai.com](https://hereandnowai.com)
25
+
26
+ ## Tech Stack
27
+
28
+ - **Frontend**: HTML, CSS (Wax Crayon Theme), JavaScript
29
+ - **Backend**: Python Flask
30
+ - **AI Model**: Gemma-3-27b via Google Gemini API
31
+ - **Fonts**: Caveat, Patrick Hand (Google Fonts)
32
+
33
+ ## Contact
34
+
35
+ - Email: info@hereandnowai.com
36
+ - Phone: +91 996 296 1000
37
+
38
+ © 2025 HERE AND NOW AI. All rights reserved.
app.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HERE AND NOW AI Chatbot
3
+ Wax Crayon Design - Powered by Gemma-3-27b via Google Gemini API
4
+ """
5
+
6
+ import os
7
+ import json
8
+ from flask import Flask, render_template, request, jsonify, send_from_directory
9
+
10
+ # Check environment
11
+ IS_HF_SPACE = os.environ.get("SPACE_ID") is not None
12
+
13
+ if IS_HF_SPACE:
14
+ import google.generativeai as genai
15
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
16
+ genai.configure(api_key=GEMINI_API_KEY)
17
+ model = genai.GenerativeModel("gemma-3-27b-it")
18
+
19
+ app = Flask(__name__, static_folder='static', template_folder='templates')
20
+
21
+ # Load branding
22
+ with open('branding.json', 'r') as f:
23
+ branding = json.load(f)['brand']
24
+
25
+ # System prompt
26
+ SYSTEM_PROMPT = f"""You are Caramel, a friendly AI assistant for {branding['organizationName']}.
27
+ You're warm, helpful, and knowledgeable about AI, technology, and how AI can help businesses.
28
+ {branding['slogan']} - this is our philosophy. We believe AI should empower people.
29
+ Keep responses concise but friendly. Use emojis occasionally to be personable.
30
+ Website: {branding['website']}"""
31
+
32
+ # Store conversation history in memory (for demo purposes)
33
+ conversations = {}
34
+
35
+ @app.route('/')
36
+ def index():
37
+ return render_template('index.html', branding=branding)
38
+
39
+ @app.route('/static/<path:filename>')
40
+ def serve_static(filename):
41
+ return send_from_directory('static', filename)
42
+
43
+ @app.route('/api/chat', methods=['POST'])
44
+ def chat():
45
+ data = request.json
46
+ message = data.get('message', '')
47
+ session_id = data.get('session_id', 'default')
48
+
49
+ if not message:
50
+ return jsonify({'error': 'No message provided'}), 400
51
+
52
+ # Get or create conversation history
53
+ if session_id not in conversations:
54
+ conversations[session_id] = []
55
+
56
+ history = conversations[session_id]
57
+
58
+ try:
59
+ if IS_HF_SPACE:
60
+ # Use Gemini API
61
+ chat_history = []
62
+ for msg in history:
63
+ chat_history.append({"role": msg['role'], "parts": [msg['content']]})
64
+
65
+ chat_session = model.start_chat(history=chat_history)
66
+
67
+ # Add system prompt for first message
68
+ if not history:
69
+ full_message = f"{SYSTEM_PROMPT}\n\nUser: {message}"
70
+ else:
71
+ full_message = message
72
+
73
+ response = chat_session.send_message(full_message)
74
+ assistant_message = response.text
75
+ else:
76
+ # For local testing, return a placeholder
77
+ assistant_message = f"👋 Hello! I'm Caramel, your AI assistant from HERE AND NOW AI. I received your message: '{message}'. (This is a demo response - connect to Gemini API for real responses!)"
78
+
79
+ # Store in history
80
+ history.append({"role": "user", "content": message})
81
+ history.append({"role": "model", "content": assistant_message})
82
+ conversations[session_id] = history[-20:] # Keep last 20 messages
83
+
84
+ return jsonify({'response': assistant_message})
85
+
86
+ except Exception as e:
87
+ return jsonify({'error': str(e)}), 500
88
+
89
+ @app.route('/api/branding')
90
+ def get_branding():
91
+ return jsonify(branding)
92
+
93
+ if __name__ == '__main__':
94
+ app.run(host='0.0.0.0', port=7860, debug=True)
branding.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "brand": {
3
+ "organizationName": "HERE AND NOW AI",
4
+ "website": "https://hereandnowai.com",
5
+ "email": "info@hereandnowai.com",
6
+ "mobile": "+91 996 296 1000",
7
+ "slogan": "AI is Good",
8
+ "colors": {
9
+ "primary": "#FFDF00",
10
+ "secondary": "#004040"
11
+ },
12
+ "logo": {
13
+ "title": "https://raw.githubusercontent.com/hereandnowai/images/refs/heads/main/logos/logo-of-here-and-now-ai.png",
14
+ "favicon": "https://raw.githubusercontent.com/hereandnowai/images/refs/heads/main/logos/favicon-logo-with-name.png"
15
+ },
16
+ "chatbot": {
17
+ "avatar": "https://raw.githubusercontent.com/hereandnowai/images/refs/heads/main/logos/caramel.jpeg",
18
+ "face": "https://raw.githubusercontent.com/hereandnowai/images/refs/heads/main/logos/caramel-face.jpeg"
19
+ },
20
+ "socialMedia": {
21
+ "blog": "https://hereandnowai.com/blog",
22
+ "linkedin": "https://www.linkedin.com/company/hereandnowai/",
23
+ "instagram": "https://instagram.com/hereandnow_ai",
24
+ "github": "https://github.com/hereandnowai",
25
+ "x": "https://x.com/hereandnow_ai",
26
+ "youtube": "https://youtube.com/@hereandnow_ai"
27
+ }
28
+ }
29
+ }
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask>=3.0.0
2
+ google-generativeai>=0.8.0
3
+ gunicorn>=21.0.0
static/css/style.css ADDED
@@ -0,0 +1,505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===========================================
2
+ HERE AND NOW AI Chatbot - Wax Crayon Theme
3
+ =========================================== */
4
+
5
+ :root {
6
+ --primary: #ffdf00;
7
+ --secondary: #004040;
8
+ --bg-cream: #fff8e7;
9
+ --bg-paper: #fffef5;
10
+ --text-dark: #2d2d2d;
11
+ --text-light: #666;
12
+ --shadow: rgba(0, 64, 64, 0.15);
13
+ --crayon-stroke: 3px;
14
+ }
15
+
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ font-family: "Patrick Hand", cursive;
24
+ background: var(--bg-cream);
25
+ min-height: 100vh;
26
+ color: var(--text-dark);
27
+ overflow-x: hidden;
28
+ }
29
+
30
+ /* Crayon texture background */
31
+ .crayon-bg {
32
+ position: fixed;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ background:
38
+ repeating-linear-gradient(
39
+ 0deg,
40
+ transparent,
41
+ transparent 2px,
42
+ rgba(255, 223, 0, 0.03) 2px,
43
+ rgba(255, 223, 0, 0.03) 4px
44
+ ),
45
+ repeating-linear-gradient(
46
+ 90deg,
47
+ transparent,
48
+ transparent 2px,
49
+ rgba(0, 64, 64, 0.02) 2px,
50
+ rgba(0, 64, 64, 0.02) 4px
51
+ );
52
+ pointer-events: none;
53
+ z-index: -1;
54
+ }
55
+
56
+ .container {
57
+ max-width: 900px;
58
+ margin: 0 auto;
59
+ padding: 20px;
60
+ min-height: 100vh;
61
+ display: flex;
62
+ flex-direction: column;
63
+ }
64
+
65
+ /* ===========================================
66
+ Header - Hand-drawn style
67
+ =========================================== */
68
+ .header {
69
+ text-align: center;
70
+ padding: 20px 0;
71
+ margin-bottom: 20px;
72
+ }
73
+
74
+ .logo-container {
75
+ display: inline-block;
76
+ padding: 15px 25px;
77
+ background: var(--primary);
78
+ border: var(--crayon-stroke) solid var(--secondary);
79
+ border-radius: 20px 5px 20px 5px;
80
+ box-shadow:
81
+ 4px 4px 0 var(--secondary),
82
+ 8px 8px 15px var(--shadow);
83
+ transform: rotate(-1deg);
84
+ transition: transform 0.3s ease;
85
+ }
86
+
87
+ .logo-container:hover {
88
+ transform: rotate(0deg) scale(1.02);
89
+ }
90
+
91
+ .logo {
92
+ height: 50px;
93
+ width: auto;
94
+ }
95
+
96
+ .slogan {
97
+ font-family: "Caveat", cursive;
98
+ font-size: 1.8rem;
99
+ color: var(--secondary);
100
+ margin-top: 15px;
101
+ font-weight: 600;
102
+ text-shadow: 2px 2px 0 var(--primary);
103
+ }
104
+
105
+ /* ===========================================
106
+ Chat Container - Notebook paper style
107
+ =========================================== */
108
+ .chat-container {
109
+ flex: 1;
110
+ background: var(--bg-paper);
111
+ border: var(--crayon-stroke) solid var(--secondary);
112
+ border-radius: 15px 30px 15px 30px;
113
+ box-shadow:
114
+ 6px 6px 0 var(--primary),
115
+ 12px 12px 0 var(--secondary),
116
+ 15px 15px 30px var(--shadow);
117
+ display: flex;
118
+ flex-direction: column;
119
+ overflow: hidden;
120
+ position: relative;
121
+ }
122
+
123
+ /* Notebook lines effect */
124
+ .chat-container::before {
125
+ content: "";
126
+ position: absolute;
127
+ top: 0;
128
+ left: 0;
129
+ right: 0;
130
+ bottom: 0;
131
+ background: repeating-linear-gradient(
132
+ transparent,
133
+ transparent 30px,
134
+ rgba(0, 64, 64, 0.05) 30px,
135
+ rgba(0, 64, 64, 0.05) 31px
136
+ );
137
+ pointer-events: none;
138
+ }
139
+
140
+ /* Red margin line */
141
+ .chat-container::after {
142
+ content: "";
143
+ position: absolute;
144
+ top: 0;
145
+ left: 60px;
146
+ width: 2px;
147
+ height: 100%;
148
+ background: rgba(255, 100, 100, 0.3);
149
+ pointer-events: none;
150
+ }
151
+
152
+ .chat-header {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 15px;
156
+ padding: 20px 25px;
157
+ background: linear-gradient(135deg, var(--primary) 0%, #ffe94d 100%);
158
+ border-bottom: var(--crayon-stroke) solid var(--secondary);
159
+ position: relative;
160
+ z-index: 1;
161
+ }
162
+
163
+ .avatar {
164
+ width: 60px;
165
+ height: 60px;
166
+ border-radius: 50%;
167
+ border: 3px solid var(--secondary);
168
+ box-shadow: 3px 3px 0 var(--secondary);
169
+ object-fit: cover;
170
+ }
171
+
172
+ .chat-header-info h2 {
173
+ font-family: "Caveat", cursive;
174
+ font-size: 1.8rem;
175
+ color: var(--secondary);
176
+ margin-bottom: 2px;
177
+ }
178
+
179
+ .status {
180
+ font-size: 0.9rem;
181
+ color: var(--text-light);
182
+ }
183
+
184
+ /* ===========================================
185
+ Messages - Speech bubbles with crayon feel
186
+ =========================================== */
187
+ .chat-messages {
188
+ flex: 1;
189
+ overflow-y: auto;
190
+ padding: 25px;
191
+ display: flex;
192
+ flex-direction: column;
193
+ gap: 20px;
194
+ position: relative;
195
+ z-index: 1;
196
+ }
197
+
198
+ .message {
199
+ display: flex;
200
+ gap: 12px;
201
+ max-width: 85%;
202
+ animation: messageSlide 0.3s ease-out;
203
+ }
204
+
205
+ @keyframes messageSlide {
206
+ from {
207
+ opacity: 0;
208
+ transform: translateY(10px);
209
+ }
210
+ to {
211
+ opacity: 1;
212
+ transform: translateY(0);
213
+ }
214
+ }
215
+
216
+ .message.user {
217
+ align-self: flex-end;
218
+ flex-direction: row-reverse;
219
+ }
220
+
221
+ .message-avatar {
222
+ width: 40px;
223
+ height: 40px;
224
+ border-radius: 50%;
225
+ border: 2px solid var(--secondary);
226
+ flex-shrink: 0;
227
+ object-fit: cover;
228
+ }
229
+
230
+ .message-content {
231
+ padding: 15px 20px;
232
+ border-radius: 20px 20px 20px 5px;
233
+ position: relative;
234
+ line-height: 1.5;
235
+ font-size: 1.1rem;
236
+ }
237
+
238
+ .message.bot .message-content {
239
+ background: white;
240
+ border: 2px solid var(--secondary);
241
+ box-shadow: 3px 3px 0 var(--primary);
242
+ }
243
+
244
+ .message.user .message-content {
245
+ background: var(--primary);
246
+ border: 2px solid var(--secondary);
247
+ border-radius: 20px 20px 5px 20px;
248
+ box-shadow: 3px 3px 0 var(--secondary);
249
+ }
250
+
251
+ /* ===========================================
252
+ Input Area - Torn paper effect
253
+ =========================================== */
254
+ .chat-input-container {
255
+ padding: 20px 25px;
256
+ background: linear-gradient(to top, var(--bg-cream), var(--bg-paper));
257
+ border-top: 3px dashed var(--secondary);
258
+ position: relative;
259
+ z-index: 1;
260
+ }
261
+
262
+ .suggestions {
263
+ display: flex;
264
+ flex-wrap: wrap;
265
+ gap: 10px;
266
+ margin-bottom: 15px;
267
+ }
268
+
269
+ .suggestion-btn {
270
+ font-family: "Patrick Hand", cursive;
271
+ font-size: 0.95rem;
272
+ padding: 8px 16px;
273
+ background: white;
274
+ border: 2px solid var(--secondary);
275
+ border-radius: 20px;
276
+ cursor: pointer;
277
+ transition: all 0.2s ease;
278
+ color: var(--text-dark);
279
+ }
280
+
281
+ .suggestion-btn:hover {
282
+ background: var(--primary);
283
+ transform: translateY(-2px);
284
+ box-shadow: 2px 2px 0 var(--secondary);
285
+ }
286
+
287
+ .chat-form {
288
+ display: flex;
289
+ gap: 12px;
290
+ }
291
+
292
+ #messageInput {
293
+ flex: 1;
294
+ font-family: "Patrick Hand", cursive;
295
+ font-size: 1.1rem;
296
+ padding: 15px 20px;
297
+ border: 3px solid var(--secondary);
298
+ border-radius: 25px;
299
+ background: white;
300
+ outline: none;
301
+ transition: all 0.2s ease;
302
+ }
303
+
304
+ #messageInput:focus {
305
+ box-shadow: 0 0 0 3px var(--primary);
306
+ }
307
+
308
+ #messageInput::placeholder {
309
+ color: var(--text-light);
310
+ }
311
+
312
+ #sendBtn {
313
+ width: 55px;
314
+ height: 55px;
315
+ border-radius: 50%;
316
+ border: 3px solid var(--secondary);
317
+ background: var(--primary);
318
+ cursor: pointer;
319
+ display: flex;
320
+ align-items: center;
321
+ justify-content: center;
322
+ transition: all 0.2s ease;
323
+ color: var(--secondary);
324
+ }
325
+
326
+ #sendBtn:hover {
327
+ transform: scale(1.1) rotate(10deg);
328
+ box-shadow: 3px 3px 0 var(--secondary);
329
+ }
330
+
331
+ #sendBtn:disabled {
332
+ opacity: 0.5;
333
+ cursor: not-allowed;
334
+ transform: none;
335
+ }
336
+
337
+ /* ===========================================
338
+ Footer - Sketchy style
339
+ =========================================== */
340
+ .footer {
341
+ text-align: center;
342
+ padding: 25px 0;
343
+ margin-top: 20px;
344
+ }
345
+
346
+ .social-links {
347
+ display: flex;
348
+ justify-content: center;
349
+ gap: 15px;
350
+ margin-bottom: 15px;
351
+ }
352
+
353
+ .social-links a {
354
+ width: 40px;
355
+ height: 40px;
356
+ display: flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ background: var(--primary);
360
+ border: 2px solid var(--secondary);
361
+ border-radius: 50%;
362
+ color: var(--secondary);
363
+ transition: all 0.2s ease;
364
+ }
365
+
366
+ .social-links a:hover {
367
+ transform: translateY(-3px) rotate(5deg);
368
+ box-shadow: 2px 2px 0 var(--secondary);
369
+ }
370
+
371
+ .copyright {
372
+ font-size: 0.9rem;
373
+ color: var(--text-light);
374
+ margin-bottom: 5px;
375
+ }
376
+
377
+ .contact {
378
+ font-size: 0.85rem;
379
+ }
380
+
381
+ .contact a {
382
+ color: var(--secondary);
383
+ text-decoration: none;
384
+ }
385
+
386
+ .contact a:hover {
387
+ text-decoration: underline;
388
+ }
389
+
390
+ /* ===========================================
391
+ Typing indicator
392
+ =========================================== */
393
+ .typing-indicator {
394
+ display: flex;
395
+ gap: 5px;
396
+ padding: 15px 20px;
397
+ }
398
+
399
+ .typing-indicator span {
400
+ width: 10px;
401
+ height: 10px;
402
+ background: var(--secondary);
403
+ border-radius: 50%;
404
+ animation: typing 1.4s infinite ease-in-out;
405
+ }
406
+
407
+ .typing-indicator span:nth-child(1) {
408
+ animation-delay: 0s;
409
+ }
410
+ .typing-indicator span:nth-child(2) {
411
+ animation-delay: 0.2s;
412
+ }
413
+ .typing-indicator span:nth-child(3) {
414
+ animation-delay: 0.4s;
415
+ }
416
+
417
+ @keyframes typing {
418
+ 0%,
419
+ 60%,
420
+ 100% {
421
+ transform: translateY(0);
422
+ opacity: 0.4;
423
+ }
424
+ 30% {
425
+ transform: translateY(-10px);
426
+ opacity: 1;
427
+ }
428
+ }
429
+
430
+ /* ===========================================
431
+ Mobile Responsive
432
+ =========================================== */
433
+ @media (max-width: 768px) {
434
+ .container {
435
+ padding: 10px;
436
+ }
437
+
438
+ .logo {
439
+ height: 40px;
440
+ }
441
+
442
+ .slogan {
443
+ font-size: 1.4rem;
444
+ }
445
+
446
+ .chat-container::after {
447
+ display: none;
448
+ }
449
+
450
+ .chat-header {
451
+ padding: 15px 20px;
452
+ }
453
+
454
+ .avatar {
455
+ width: 50px;
456
+ height: 50px;
457
+ }
458
+
459
+ .chat-header-info h2 {
460
+ font-size: 1.4rem;
461
+ }
462
+
463
+ .message {
464
+ max-width: 90%;
465
+ }
466
+
467
+ .suggestions {
468
+ gap: 8px;
469
+ }
470
+
471
+ .suggestion-btn {
472
+ font-size: 0.85rem;
473
+ padding: 6px 12px;
474
+ }
475
+
476
+ #messageInput {
477
+ padding: 12px 15px;
478
+ }
479
+
480
+ #sendBtn {
481
+ width: 48px;
482
+ height: 48px;
483
+ }
484
+ }
485
+
486
+ /* ===========================================
487
+ Scrollbar styling
488
+ =========================================== */
489
+ .chat-messages::-webkit-scrollbar {
490
+ width: 8px;
491
+ }
492
+
493
+ .chat-messages::-webkit-scrollbar-track {
494
+ background: transparent;
495
+ }
496
+
497
+ .chat-messages::-webkit-scrollbar-thumb {
498
+ background: var(--primary);
499
+ border-radius: 4px;
500
+ border: 1px solid var(--secondary);
501
+ }
502
+
503
+ .chat-messages::-webkit-scrollbar-thumb:hover {
504
+ background: #ffe94d;
505
+ }
static/js/chat.js ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * HERE AND NOW AI Chatbot - Chat functionality
3
+ */
4
+
5
+ // Generate a unique session ID
6
+ const sessionId = 'session_' + Math.random().toString(36).substr(2, 9);
7
+
8
+ // DOM elements
9
+ const chatMessages = document.getElementById('chatMessages');
10
+ const chatForm = document.getElementById('chatForm');
11
+ const messageInput = document.getElementById('messageInput');
12
+ const sendBtn = document.getElementById('sendBtn');
13
+
14
+ // User avatar (default)
15
+ const userAvatar = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="%23004040"/><circle cx="50" cy="40" r="18" fill="%23FFDF00"/><ellipse cx="50" cy="75" rx="28" ry="20" fill="%23FFDF00"/></svg>';
16
+
17
+ // Bot avatar
18
+ let botAvatar = '';
19
+
20
+ // Initialize
21
+ document.addEventListener('DOMContentLoaded', () => {
22
+ // Get bot avatar from the first message
23
+ const firstBotMessage = document.querySelector('.message.bot .message-avatar');
24
+ if (firstBotMessage) {
25
+ botAvatar = firstBotMessage.src;
26
+ }
27
+
28
+ // Focus input
29
+ messageInput.focus();
30
+ });
31
+
32
+ // Handle form submission
33
+ chatForm.addEventListener('submit', async (e) => {
34
+ e.preventDefault();
35
+
36
+ const message = messageInput.value.trim();
37
+ if (!message) return;
38
+
39
+ // Clear input
40
+ messageInput.value = '';
41
+
42
+ // Add user message
43
+ addMessage(message, 'user');
44
+
45
+ // Show typing indicator
46
+ showTyping();
47
+
48
+ // Send to API
49
+ try {
50
+ const response = await fetch('/api/chat', {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json'
54
+ },
55
+ body: JSON.stringify({
56
+ message: message,
57
+ session_id: sessionId
58
+ })
59
+ });
60
+
61
+ const data = await response.json();
62
+
63
+ // Remove typing indicator
64
+ hideTyping();
65
+
66
+ if (data.error) {
67
+ addMessage('Oops! Something went wrong. Please try again. 😅', 'bot');
68
+ } else {
69
+ addMessage(data.response, 'bot');
70
+ }
71
+ } catch (error) {
72
+ hideTyping();
73
+ addMessage('Connection error. Please check your internet and try again. 🔌', 'bot');
74
+ }
75
+ });
76
+
77
+ // Add message to chat
78
+ function addMessage(text, type) {
79
+ const messageDiv = document.createElement('div');
80
+ messageDiv.className = `message ${type}`;
81
+
82
+ const avatar = type === 'user' ? userAvatar : botAvatar;
83
+
84
+ messageDiv.innerHTML = `
85
+ <img src="${avatar}" alt="${type === 'user' ? 'You' : 'Caramel'}" class="message-avatar">
86
+ <div class="message-content">
87
+ <p>${formatMessage(text)}</p>
88
+ </div>
89
+ `;
90
+
91
+ chatMessages.appendChild(messageDiv);
92
+ scrollToBottom();
93
+ }
94
+
95
+ // Format message (handle line breaks and basic formatting)
96
+ function formatMessage(text) {
97
+ return text
98
+ .replace(/\n/g, '<br>')
99
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
100
+ .replace(/\*(.*?)\*/g, '<em>$1</em>');
101
+ }
102
+
103
+ // Show typing indicator
104
+ function showTyping() {
105
+ const typingDiv = document.createElement('div');
106
+ typingDiv.className = 'message bot typing-message';
107
+ typingDiv.innerHTML = `
108
+ <img src="${botAvatar}" alt="Caramel" class="message-avatar">
109
+ <div class="message-content">
110
+ <div class="typing-indicator">
111
+ <span></span>
112
+ <span></span>
113
+ <span></span>
114
+ </div>
115
+ </div>
116
+ `;
117
+ chatMessages.appendChild(typingDiv);
118
+ scrollToBottom();
119
+
120
+ // Disable send button
121
+ sendBtn.disabled = true;
122
+ }
123
+
124
+ // Hide typing indicator
125
+ function hideTyping() {
126
+ const typingMessage = document.querySelector('.typing-message');
127
+ if (typingMessage) {
128
+ typingMessage.remove();
129
+ }
130
+ sendBtn.disabled = false;
131
+ }
132
+
133
+ // Scroll to bottom
134
+ function scrollToBottom() {
135
+ chatMessages.scrollTop = chatMessages.scrollHeight;
136
+ }
137
+
138
+ // Send suggestion
139
+ function sendSuggestion(text) {
140
+ messageInput.value = text;
141
+ chatForm.dispatchEvent(new Event('submit'));
142
+ }
143
+
144
+ // Handle Enter key (Shift+Enter for new line)
145
+ messageInput.addEventListener('keydown', (e) => {
146
+ if (e.key === 'Enter' && !e.shiftKey) {
147
+ e.preventDefault();
148
+ chatForm.dispatchEvent(new Event('submit'));
149
+ }
150
+ });
templates/index.html ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>{{ branding.organizationName }} - AI Chatbot</title>
7
+ <link rel="icon" type="image/png" href="{{ branding.logo.favicon }}" />
8
+ <link rel="stylesheet" href="/static/css/style.css" />
9
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
+ <link
12
+ href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;600;700&family=Patrick+Hand&display=swap"
13
+ rel="stylesheet"
14
+ />
15
+ </head>
16
+ <body>
17
+ <div class="crayon-bg"></div>
18
+
19
+ <div class="container">
20
+ <!-- Header -->
21
+ <header class="header">
22
+ <div class="logo-container">
23
+ <img
24
+ src="{{ branding.logo.title }}"
25
+ alt="{{ branding.organizationName }}"
26
+ class="logo"
27
+ />
28
+ </div>
29
+ <p class="slogan">{{ branding.slogan }}</p>
30
+ </header>
31
+
32
+ <!-- Chat Container -->
33
+ <main class="chat-container">
34
+ <div class="chat-header">
35
+ <img src="{{ branding.chatbot.face }}" alt="Caramel" class="avatar" />
36
+ <div class="chat-header-info">
37
+ <h2>Chat with Caramel</h2>
38
+ <span class="status">🟢 Online</span>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="chat-messages" id="chatMessages">
43
+ <div class="message bot">
44
+ <img
45
+ src="{{ branding.chatbot.face }}"
46
+ alt="Caramel"
47
+ class="message-avatar"
48
+ />
49
+ <div class="message-content">
50
+ <p>
51
+ Hey there! 👋 I'm Caramel, your friendly AI assistant from HERE
52
+ AND NOW AI. How can I help you today?
53
+ </p>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="chat-input-container">
59
+ <div class="suggestions">
60
+ <button
61
+ class="suggestion-btn"
62
+ onclick="sendSuggestion('What does HERE AND NOW AI do?')"
63
+ >
64
+ What does HERE AND NOW AI do?
65
+ </button>
66
+ <button
67
+ class="suggestion-btn"
68
+ onclick="sendSuggestion('How can AI help my business?')"
69
+ >
70
+ How can AI help my business?
71
+ </button>
72
+ <button
73
+ class="suggestion-btn"
74
+ onclick="sendSuggestion('Tell me about your services')"
75
+ >
76
+ Tell me about your services
77
+ </button>
78
+ </div>
79
+ <form class="chat-form" id="chatForm">
80
+ <input
81
+ type="text"
82
+ id="messageInput"
83
+ placeholder="Type your message here..."
84
+ autocomplete="off"
85
+ />
86
+ <button type="submit" id="sendBtn">
87
+ <svg
88
+ width="24"
89
+ height="24"
90
+ viewBox="0 0 24 24"
91
+ fill="none"
92
+ stroke="currentColor"
93
+ stroke-width="2"
94
+ >
95
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" />
96
+ </svg>
97
+ </button>
98
+ </form>
99
+ </div>
100
+ </main>
101
+
102
+ <!-- Footer -->
103
+ <footer class="footer">
104
+ <div class="social-links">
105
+ <a
106
+ href="{{ branding.socialMedia.linkedin }}"
107
+ target="_blank"
108
+ title="LinkedIn"
109
+ >
110
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
111
+ <path
112
+ d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"
113
+ />
114
+ </svg>
115
+ </a>
116
+ <a
117
+ href="{{ branding.socialMedia.instagram }}"
118
+ target="_blank"
119
+ title="Instagram"
120
+ >
121
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
122
+ <path
123
+ d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"
124
+ />
125
+ </svg>
126
+ </a>
127
+ <a
128
+ href="{{ branding.socialMedia.youtube }}"
129
+ target="_blank"
130
+ title="YouTube"
131
+ >
132
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
133
+ <path
134
+ d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
135
+ />
136
+ </svg>
137
+ </a>
138
+ <a
139
+ href="{{ branding.socialMedia.github }}"
140
+ target="_blank"
141
+ title="GitHub"
142
+ >
143
+ <svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
144
+ <path
145
+ d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
146
+ />
147
+ </svg>
148
+ </a>
149
+ </div>
150
+ <p class="copyright">
151
+ © 2025 {{ branding.organizationName }}. All rights reserved.
152
+ </p>
153
+ <p class="contact">
154
+ <a href="mailto:{{ branding.email }}">{{ branding.email }}</a> |
155
+ <a href="tel:{{ branding.mobile }}">{{ branding.mobile }}</a>
156
+ </p>
157
+ </footer>
158
+ </div>
159
+
160
+ <script src="/static/js/chat.js"></script>
161
+ </body>
162
+ </html>