ayush-thakur02 commited on
Commit
07aa026
Β·
verified Β·
1 Parent(s): 767fee0

Upload 9 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git files
2
+ .git
3
+ .gitignore
4
+ .gitattributes
5
+
6
+ # Python cache
7
+ __pycache__/
8
+ *.pyc
9
+ *.pyo
10
+ *.pyd
11
+ .Python
12
+ *.so
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ ENV/
18
+ env/
19
+
20
+ # IDE files
21
+ .vscode/
22
+ .idea/
23
+ *.swp
24
+ *.swo
25
+
26
+ # OS generated files
27
+ .DS_Store
28
+ .DS_Store?
29
+ ._*
30
+ .Spotlight-V100
31
+ .Trashes
32
+ ehthumbs.db
33
+ Thumbs.db
34
+
35
+ # Documentation
36
+ README.md
37
+ *.md
38
+ !blogs/*.md
39
+
40
+ # Other
41
+ .dockerignore
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.11 slim image for smaller size
2
+ FROM python:3.11-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Set environment variables
8
+ ENV PYTHONUNBUFFERED=1
9
+ ENV PYTHONDONTWRITEBYTECODE=1
10
+
11
+ # Install system dependencies if needed
12
+ RUN apt-get update && apt-get install -y \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Copy all application files
16
+ COPY . .
17
+
18
+ # Create a non-root user for security
19
+ RUN useradd -m -u 1000 user && chown -R user:user /app
20
+ USER user
21
+
22
+ # Expose the port that Hugging Face Spaces expects
23
+ EXPOSE 7860
24
+
25
+ # Command to run the application on port 7860 (Hugging Face default)
26
+ CMD ["python", "server.py", "--port", "7860"]
blog-viewer.html ADDED
@@ -0,0 +1,884 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Documentation Viewer</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Lexend:[email protected]&display=swap" rel="stylesheet">
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: "Lexend", sans-serif;
17
+ }
18
+
19
+ body {
20
+ /* font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; */
21
+ line-height: 1.5;
22
+ color: #1d1d1f;
23
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
24
+ /* background: black; */
25
+ min-height: 100vh;
26
+ }
27
+
28
+ .header {
29
+ background: rgba(255, 255, 255, 0.6);
30
+ backdrop-filter: blur(1px);
31
+ -webkit-backdrop-filter: blur(1px);
32
+ color: #1d1d1f;
33
+ padding: 16px;
34
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
35
+ box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
36
+ position: fixed;
37
+ top: 0;
38
+ margin: 10px;
39
+ border-radius: 20px;
40
+ z-index: 100;
41
+ }
42
+
43
+ .header-content {
44
+ /* max-width: 1200px; */
45
+ margin: 0 auto;
46
+ display: flex;
47
+ justify-content: space-between;
48
+ align-items: center;
49
+ }
50
+
51
+ .header h1 {
52
+ font-size: 1.3rem;
53
+ font-weight: 600;
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ }
58
+
59
+ #documentTitle {
60
+ background: rgba(255, 255, 255);
61
+ border: 1px solid rgba(0, 0, 0, 0.2);
62
+ border-radius: 15px;
63
+ padding: 10px 15px;
64
+ margin-right: 10px;
65
+ }
66
+
67
+ .back-btn {
68
+ background: rgba(0, 122, 255, 0.3);
69
+ backdrop-filter: blur(200px);
70
+ -webkit-backdrop-filter: blur(200px);
71
+ /* background: rgba(255, 255, 255); */
72
+ color: #007aff;
73
+ border: none;
74
+ padding: 8px 16px;
75
+ border-radius: 20px;
76
+ cursor: pointer;
77
+ text-decoration: none;
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 6px;
81
+ font-size: 0.9rem;
82
+ font-weight: 500;
83
+ transition: all 0.3s ease;
84
+ }
85
+
86
+ .back-btn:hover {
87
+ background: rgba(0, 122, 255, 0.2);
88
+ transform: translateY(-1px);
89
+ }
90
+
91
+ .container {
92
+ max-width: 1200px;
93
+ margin: 0 auto;
94
+ padding: 24px 20px;
95
+ margin-top: 90px;
96
+ }
97
+
98
+ .document-meta {
99
+ background: rgba(255, 255, 255, 0.8);
100
+ backdrop-filter: blur(20px);
101
+ -webkit-backdrop-filter: blur(20px);
102
+ border: 1px solid rgba(255, 255, 255, 0.3);
103
+ border-radius: 12px;
104
+ padding: 16px;
105
+ margin-bottom: 20px;
106
+ border-left: 3px solid #007aff;
107
+ }
108
+
109
+ .meta-row {
110
+ display: flex;
111
+ justify-content: space-between;
112
+ align-items: center;
113
+ margin-bottom: 8px;
114
+ }
115
+
116
+ .meta-row:last-child {
117
+ margin-bottom: 0;
118
+ }
119
+
120
+ .meta-label {
121
+ font-weight: 600;
122
+ color: #007aff;
123
+ font-size: 0.9rem;
124
+ }
125
+
126
+ .meta-value {
127
+ color: #86868b;
128
+ font-size: 0.9rem;
129
+ }
130
+
131
+ .content {
132
+ background: rgba(255, 255, 255, 0.8);
133
+ backdrop-filter: blur(20px);
134
+ -webkit-backdrop-filter: blur(20px);
135
+ border: 1px solid rgba(255, 255, 255, 0.3);
136
+ border-radius: 12px;
137
+ padding: 32px;
138
+ min-height: 400px;
139
+ }
140
+
141
+ .loading {
142
+ text-align: center;
143
+ padding: 60px;
144
+ color: #007aff;
145
+ font-size: 1.1rem;
146
+ }
147
+
148
+ .error {
149
+ background: rgba(255, 59, 48, 0.1);
150
+ border: 1px solid rgba(255, 59, 48, 0.3);
151
+ color: #d70015;
152
+ padding: 20px;
153
+ border-radius: 12px;
154
+ text-align: center;
155
+ }
156
+
157
+ /* Markdown styling */
158
+ .markdown-content h1,
159
+ .markdown-content h2,
160
+ .markdown-content h3,
161
+ .markdown-content h4,
162
+ .markdown-content h5,
163
+ .markdown-content h6 {
164
+ margin-top: 2rem;
165
+ margin-bottom: 1rem;
166
+ font-weight: 600;
167
+ line-height: 1.25;
168
+ }
169
+
170
+ .markdown-content h1 {
171
+ font-size: 2rem;
172
+ border-bottom: 2px solid #eee;
173
+ padding-bottom: 0.5rem;
174
+ }
175
+
176
+ .markdown-content h2 {
177
+ font-size: 1.5rem;
178
+ border-bottom: 1px solid #eee;
179
+ padding-bottom: 0.3rem;
180
+ }
181
+
182
+ .markdown-content h3 {
183
+ font-size: 1.25rem;
184
+ }
185
+
186
+ .markdown-content p {
187
+ margin-bottom: 1rem;
188
+ }
189
+
190
+ .markdown-content ul,
191
+ .markdown-content ol {
192
+ margin-bottom: 1rem;
193
+ padding-left: 2rem;
194
+ }
195
+
196
+ .markdown-content li {
197
+ margin-bottom: 0.25rem;
198
+ }
199
+
200
+ .markdown-content blockquote {
201
+ border-left: 3px solid #007aff;
202
+ background: rgba(0, 122, 255, 0.05);
203
+ padding: 1rem;
204
+ margin: 1rem 0;
205
+ border-radius: 6px;
206
+ font-style: italic;
207
+ }
208
+
209
+ .markdown-content code {
210
+ background: rgba(0, 0, 0, 0.05);
211
+ padding: 0.2rem 0.4rem;
212
+ border-radius: 4px;
213
+ font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
214
+ font-size: 0.9rem;
215
+ }
216
+
217
+ .markdown-content pre {
218
+ background: rgba(0, 0, 0, 0.05);
219
+ border: 1px solid rgba(0, 0, 0, 0.1);
220
+ border-radius: 8px;
221
+ padding: 1rem;
222
+ overflow-x: auto;
223
+ margin: 1rem 0;
224
+ }
225
+
226
+ .markdown-content pre code {
227
+ background: none;
228
+ padding: 0;
229
+ }
230
+
231
+ .markdown-content table {
232
+ border-collapse: collapse;
233
+ width: 100%;
234
+ margin: 1rem 0;
235
+ }
236
+
237
+ .markdown-content th,
238
+ .markdown-content td {
239
+ border: 1px solid #ddd;
240
+ padding: 0.75rem;
241
+ text-align: left;
242
+ }
243
+
244
+ .markdown-content th {
245
+ background: #f8f9fa;
246
+ font-weight: 600;
247
+ }
248
+
249
+ .markdown-content a {
250
+ color: #007aff;
251
+ text-decoration: none;
252
+ }
253
+
254
+ .markdown-content a:hover {
255
+ text-decoration: underline;
256
+ }
257
+
258
+ .markdown-content img {
259
+ max-width: 100%;
260
+ height: auto;
261
+ border-radius: 8px;
262
+ margin: 1rem 0;
263
+ }
264
+
265
+ /* Custom scrollbar */
266
+ ::-webkit-scrollbar {
267
+ width: 8px;
268
+ }
269
+
270
+ ::-webkit-scrollbar-track {
271
+ background: rgba(255, 255, 255, 0.1);
272
+ }
273
+
274
+ ::-webkit-scrollbar-thumb {
275
+ background: rgba(0, 0, 0, 0.2);
276
+ border-radius: 4px;
277
+ }
278
+
279
+ ::-webkit-scrollbar-thumb:hover {
280
+ background: rgba(0, 0, 0, 0.3);
281
+ }
282
+
283
+ @media (max-width: 768px) {
284
+ .header-content {
285
+ flex-direction: column;
286
+ gap: 15px;
287
+ text-align: center;
288
+ }
289
+
290
+ .container {
291
+ padding: 20px 15px;
292
+ }
293
+
294
+ .content {
295
+ padding: 20px;
296
+ }
297
+
298
+ .meta-row {
299
+ flex-direction: column;
300
+ align-items: flex-start;
301
+ gap: 5px;
302
+ }
303
+ }
304
+ </style>
305
+ <!-- Include marked.js for markdown parsing -->
306
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
307
+ </head>
308
+
309
+ <body>
310
+ <div class="header">
311
+ <div class="header-content">
312
+ <h1>
313
+ <!-- <span>πŸ“„</span> -->
314
+ <span id="documentTitle">Loading Document...</span>
315
+ </h1>
316
+ <a href="javascript:void(0)" class="back-btn" onclick="window.close()">
317
+ <!-- <span>←</span> -->
318
+ <span>Close</span>
319
+ </a>
320
+ </div>
321
+ </div>
322
+
323
+ <div class="container">
324
+ <div class="document-meta" id="documentMeta" style="display: none;">
325
+ <div class="meta-row">
326
+ <span class="meta-label">πŸ“ File:</span>
327
+ <span class="meta-value" id="fileName"></span>
328
+ </div>
329
+ <div class="meta-row">
330
+ <span class="meta-label">πŸ“… Last Modified:</span>
331
+ <span class="meta-value" id="lastModified"></span>
332
+ </div>
333
+ <div class="meta-row">
334
+ <span class="meta-label">πŸ“Š Size:</span>
335
+ <span class="meta-value" id="fileSize"></span>
336
+ </div>
337
+ </div>
338
+
339
+ <div class="content">
340
+ <div class="loading" id="loadingState">
341
+ πŸ“– Loading document content...
342
+ </div>
343
+ <div id="markdownContent" class="markdown-content" style="display: none;"></div>
344
+ </div>
345
+ </div>
346
+
347
+ <script>
348
+ class BlogViewer {
349
+ constructor() {
350
+ this.filename = this.getQueryParameter('file');
351
+ this.documentTitle = document.getElementById('documentTitle');
352
+ this.documentMeta = document.getElementById('documentMeta');
353
+ this.fileName = document.getElementById('fileName');
354
+ this.lastModified = document.getElementById('lastModified');
355
+ this.fileSize = document.getElementById('fileSize');
356
+ this.loadingState = document.getElementById('loadingState');
357
+ this.markdownContent = document.getElementById('markdownContent');
358
+
359
+ this.init();
360
+ }
361
+
362
+ getQueryParameter(name) {
363
+ const urlParams = new URLSearchParams(window.location.search);
364
+ return urlParams.get(name);
365
+ }
366
+
367
+ async init() {
368
+ if (!this.filename) {
369
+ this.showError('No file specified');
370
+ return;
371
+ }
372
+
373
+ try {
374
+ await this.loadMarkdownFile();
375
+ } catch (error) {
376
+ this.showError('Failed to load the document');
377
+ }
378
+ }
379
+
380
+ async loadMarkdownFile() {
381
+ try {
382
+ // First, try to get file metadata from the API
383
+ let fileMetadata = null;
384
+ try {
385
+ const metaResponse = await fetch('./api/blogs');
386
+ if (metaResponse.ok) {
387
+ const blogs = await metaResponse.json();
388
+ fileMetadata = blogs.find(blog => blog.filename === this.filename);
389
+ }
390
+ } catch (error) {
391
+ console.log('Could not fetch metadata from API:', error);
392
+ }
393
+
394
+ // Try to fetch the actual markdown file
395
+ const response = await fetch(`./blogs/${this.filename}`);
396
+
397
+ if (!response.ok) {
398
+ // If file doesn't exist, show sample content
399
+ this.showSampleContent();
400
+ return;
401
+ }
402
+
403
+ const markdownText = await response.text();
404
+ this.renderMarkdown(markdownText);
405
+ this.updateMetadata(false, fileMetadata);
406
+
407
+ } catch (error) {
408
+ // Fallback to sample content
409
+ this.showSampleContent();
410
+ }
411
+ }
412
+
413
+ showSampleContent() {
414
+ const sampleContent = this.getSampleContent();
415
+ this.renderMarkdown(sampleContent);
416
+ this.updateMetadata(true, null);
417
+ }
418
+
419
+ getSampleContent() {
420
+ const samples = {
421
+ 'getting-started.md': `# Getting Started Guide
422
+
423
+ Welcome to our platform! This guide will help you get up and running quickly.
424
+
425
+ ## Prerequisites
426
+
427
+ Before you begin, make sure you have:
428
+
429
+ - A modern web browser
430
+ - Basic understanding of web technologies
431
+ - Access to our platform
432
+
433
+ ## Quick Start
434
+
435
+ 1. **Sign up for an account**
436
+ - Visit our registration page
437
+ - Fill in your details
438
+ - Verify your email address
439
+
440
+ 2. **Set up your profile**
441
+ - Add your personal information
442
+ - Configure your preferences
443
+ - Upload a profile picture
444
+
445
+ 3. **Create your first project**
446
+ - Click on "New Project"
447
+ - Choose a template or start from scratch
448
+ - Give your project a name
449
+
450
+ ## Next Steps
451
+
452
+ Once you've completed the initial setup:
453
+
454
+ - Explore our [API Documentation](./api-documentation.md)
455
+ - Check out the [Troubleshooting Guide](./troubleshooting.md)
456
+ - Join our community forums
457
+
458
+ > **Tip:** Don't forget to bookmark this documentation for easy access!
459
+
460
+ ## Need Help?
461
+
462
+ If you run into any issues, please:
463
+
464
+ 1. Check our FAQ section
465
+ 2. Search our knowledge base
466
+ 3. Contact our support team
467
+
468
+ ---
469
+
470
+ *Last updated: June 28, 2025*`,
471
+
472
+ 'api-documentation.md': `# API Documentation
473
+
474
+ Complete reference for all API endpoints, authentication, and usage examples.
475
+
476
+ ## Authentication
477
+
478
+ All API requests require authentication using API keys.
479
+
480
+ ### Getting Your API Key
481
+
482
+ 1. Log into your dashboard
483
+ 2. Navigate to Settings > API Keys
484
+ 3. Click "Generate New Key"
485
+ 4. Store it securely - it won't be shown again
486
+
487
+ ### Using Your API Key
488
+
489
+ Include your API key in the header of all requests:
490
+
491
+ \`\`\`
492
+ Authorization: Bearer YOUR_API_KEY
493
+ Content-Type: application/json
494
+ \`\`\`
495
+
496
+ ## Base URL
497
+
498
+ All API endpoints are relative to:
499
+ \`\`\`
500
+ https://api.yourcompany.com/v1/
501
+ \`\`\`
502
+
503
+ ## Endpoints
504
+
505
+ ### Users
506
+
507
+ #### Get User Profile
508
+ \`\`\`
509
+ GET /users/me
510
+ \`\`\`
511
+
512
+ **Response:**
513
+ \`\`\`json
514
+ {
515
+ "id": "12345",
516
+ "name": "John Doe",
517
+ "email": "[email protected]",
518
+ "created_at": "2025-01-01T00:00:00Z"
519
+ }
520
+ \`\`\`
521
+
522
+ #### Update User Profile
523
+ \`\`\`
524
+ PUT /users/me
525
+ \`\`\`
526
+
527
+ **Request Body:**
528
+ \`\`\`json
529
+ {
530
+ "name": "John Smith",
531
+ "email": "[email protected]"
532
+ }
533
+ \`\`\`
534
+
535
+ ### Projects
536
+
537
+ #### List Projects
538
+ \`\`\`
539
+ GET /projects
540
+ \`\`\`
541
+
542
+ **Query Parameters:**
543
+ - \`limit\` (optional): Number of results (default: 10)
544
+ - \`offset\` (optional): Pagination offset (default: 0)
545
+
546
+ #### Create Project
547
+ \`\`\`
548
+ POST /projects
549
+ \`\`\`
550
+
551
+ **Request Body:**
552
+ \`\`\`json
553
+ {
554
+ "name": "My Project",
555
+ "description": "Project description",
556
+ "settings": {
557
+ "public": false
558
+ }
559
+ }
560
+ \`\`\`
561
+
562
+ ## Error Handling
563
+
564
+ All errors follow this format:
565
+
566
+ \`\`\`json
567
+ {
568
+ "error": {
569
+ "code": "INVALID_REQUEST",
570
+ "message": "The request is invalid",
571
+ "details": "Additional error information"
572
+ }
573
+ }
574
+ \`\`\`
575
+
576
+ ### Common Error Codes
577
+
578
+ | Code | Description |
579
+ |------|-------------|
580
+ | \`UNAUTHORIZED\` | Invalid or missing API key |
581
+ | \`FORBIDDEN\` | Insufficient permissions |
582
+ | \`NOT_FOUND\` | Resource not found |
583
+ | \`RATE_LIMITED\` | Too many requests |
584
+
585
+ ## Rate Limits
586
+
587
+ - **Free Plan:** 100 requests per hour
588
+ - **Pro Plan:** 1,000 requests per hour
589
+ - **Enterprise:** Custom limits
590
+
591
+ ## SDKs and Libraries
592
+
593
+ We provide official SDKs for:
594
+
595
+ - **JavaScript/Node.js:** \`npm install @yourcompany/api-client\`
596
+ - **Python:** \`pip install yourcompany-api\`
597
+ - **PHP:** Available via Composer
598
+
599
+ ---
600
+
601
+ *For more examples and advanced usage, visit our [GitHub repository](https://github.com/yourcompany/api-examples).*`,
602
+
603
+ 'troubleshooting.md': `# Troubleshooting Guide
604
+
605
+ Common issues and their solutions to help you resolve problems quickly.
606
+
607
+ ## Authentication Issues
608
+
609
+ ### Problem: "Invalid API Key" Error
610
+
611
+ **Symptoms:**
612
+ - Receiving 401 Unauthorized responses
613
+ - Error message mentions invalid API key
614
+
615
+ **Solutions:**
616
+ 1. **Check your API key format**
617
+ - Ensure it starts with the correct prefix
618
+ - Verify there are no extra spaces or characters
619
+
620
+ 2. **Verify key permissions**
621
+ - Log into your dashboard
622
+ - Check that the key has the required permissions
623
+ - Regenerate the key if necessary
624
+
625
+ 3. **Check request headers**
626
+ \`\`\`
627
+ Authorization: Bearer YOUR_API_KEY
628
+ Content-Type: application/json
629
+ \`\`\`
630
+
631
+ ### Problem: Rate Limiting
632
+
633
+ **Symptoms:**
634
+ - Receiving 429 Too Many Requests
635
+ - Requests being rejected
636
+
637
+ **Solutions:**
638
+ 1. **Implement exponential backoff**
639
+ 2. **Upgrade your plan** for higher limits
640
+ 3. **Cache responses** to reduce API calls
641
+
642
+ ## Connection Issues
643
+
644
+ ### Problem: Timeout Errors
645
+
646
+ **Possible Causes:**
647
+ - Network connectivity issues
648
+ - Server overload
649
+ - Large request payloads
650
+
651
+ **Solutions:**
652
+ 1. **Increase timeout values**
653
+ \`\`\`javascript
654
+ // Example in JavaScript
655
+ fetch(url, {
656
+ timeout: 30000 // 30 seconds
657
+ })
658
+ \`\`\`
659
+
660
+ 2. **Implement retry logic**
661
+ \`\`\`python
662
+ # Example in Python
663
+ import time
664
+ import requests
665
+
666
+ def retry_request(url, max_retries=3):
667
+ for attempt in range(max_retries):
668
+ try:
669
+ response = requests.get(url, timeout=30)
670
+ return response
671
+ except requests.Timeout:
672
+ if attempt < max_retries - 1:
673
+ time.sleep(2 ** attempt) # Exponential backoff
674
+ else:
675
+ raise
676
+ \`\`\`
677
+
678
+ 3. **Break large requests into smaller chunks**
679
+
680
+ ## Data Issues
681
+
682
+ ### Problem: Unexpected Response Format
683
+
684
+ **Symptoms:**
685
+ - Missing expected fields
686
+ - Different data types than documented
687
+
688
+ **Solutions:**
689
+ 1. **Check API version**
690
+ - Ensure you're using the correct API version
691
+ - Update your client library if needed
692
+
693
+ 2. **Validate request parameters**
694
+ - Check parameter names and types
695
+ - Review the documentation for changes
696
+
697
+ 3. **Handle optional fields gracefully**
698
+ \`\`\`javascript
699
+ // Safe property access
700
+ const name = user.name || 'Unknown User';
701
+ const email = user.email || '';
702
+ \`\`\`
703
+
704
+ ## Performance Issues
705
+
706
+ ### Problem: Slow Response Times
707
+
708
+ **Common Causes:**
709
+ - Inefficient queries
710
+ - Large datasets
711
+ - Network latency
712
+
713
+ **Optimization Tips:**
714
+ 1. **Use pagination**
715
+ \`\`\`
716
+ GET /api/items?limit=10&offset=0
717
+ \`\`\`
718
+
719
+ 2. **Request only needed fields**
720
+ \`\`\`
721
+ GET /api/users?fields=id,name,email
722
+ \`\`\`
723
+
724
+ 3. **Implement caching**
725
+ - Use HTTP caching headers
726
+ - Implement client-side caching
727
+ - Consider CDN for static resources
728
+
729
+ ## Integration Issues
730
+
731
+ ### Problem: Webhook Delivery Failures
732
+
733
+ **Symptoms:**
734
+ - Webhooks not being received
735
+ - Delayed or duplicate deliveries
736
+
737
+ **Solutions:**
738
+ 1. **Verify endpoint availability**
739
+ - Ensure your endpoint is publicly accessible
740
+ - Check firewall and security settings
741
+
742
+ 2. **Return proper HTTP status codes**
743
+ - Return 200 for successful processing
744
+ - Return 4xx for client errors
745
+ - Return 5xx for server errors
746
+
747
+ 3. **Implement idempotency**
748
+ \`\`\`javascript
749
+ // Track processed webhook IDs
750
+ const processedWebhooks = new Set();
751
+
752
+ function handleWebhook(webhookId, data) {
753
+ if (processedWebhooks.has(webhookId)) {
754
+ return; // Already processed
755
+ }
756
+
757
+ // Process webhook
758
+ processWebhookData(data);
759
+ processedWebhooks.add(webhookId);
760
+ }
761
+ \`\`\`
762
+
763
+ ## Getting Additional Help
764
+
765
+ If you're still experiencing issues:
766
+
767
+ 1. **Check our status page** - [status.yourcompany.com](https://status.yourcompany.com)
768
+ 2. **Search our community forums** - Common issues are often discussed
769
+ 3. **Contact support** - Include:
770
+ - Error messages
771
+ - Request/response examples
772
+ - Timestamps
773
+ - Your account ID
774
+
775
+ ### Support Channels
776
+
777
+ - **Email:** [email protected]
778
+ - **Chat:** Available in your dashboard
779
+ - **Phone:** +1-555-0123 (Business hours)
780
+
781
+ ---
782
+
783
+ *This guide is updated regularly. Last revision: June 28, 2025*`
784
+ };
785
+
786
+ return samples[this.filename] || `# ${this.filename}
787
+
788
+ This is a sample document. To see actual content, add your markdown files to the \`blogs\` folder.
789
+
790
+ ## Getting Started
791
+
792
+ 1. Create your markdown files in the \`blogs\` directory
793
+ 2. Add your content using standard markdown syntax
794
+ 3. The documentation hub will automatically list and display them
795
+
796
+ ## Markdown Features Supported
797
+
798
+ - **Headers** (H1-H6)
799
+ - **Lists** (ordered and unordered)
800
+ - **Links** and images
801
+ - **Code blocks** and inline code
802
+ - **Tables**
803
+ - **Blockquotes**
804
+ - And much more!
805
+
806
+ ---
807
+
808
+ *Add your own content to replace this sample.*`;
809
+ }
810
+
811
+ renderMarkdown(markdownText) {
812
+ try {
813
+ // Configure marked options
814
+ marked.setOptions({
815
+ breaks: true,
816
+ gfm: true,
817
+ highlight: function (code, lang) {
818
+ // Simple syntax highlighting placeholder
819
+ return code;
820
+ }
821
+ });
822
+
823
+ const htmlContent = marked.parse(markdownText);
824
+
825
+ this.loadingState.style.display = 'none';
826
+ this.markdownContent.innerHTML = htmlContent;
827
+ this.markdownContent.style.display = 'block';
828
+
829
+ // Update page title
830
+ const firstHeading = this.markdownContent.querySelector('h1');
831
+ if (firstHeading) {
832
+ this.documentTitle.textContent = firstHeading.textContent;
833
+ document.title = firstHeading.textContent;
834
+ } else {
835
+ this.documentTitle.textContent = this.filename;
836
+ document.title = this.filename;
837
+ }
838
+ } catch (error) {
839
+ this.showError('Failed to render the document');
840
+ }
841
+ }
842
+
843
+ updateMetadata(isSample = false, fileMetadata = null) {
844
+ this.fileName.textContent = this.filename;
845
+
846
+ if (fileMetadata) {
847
+ // Use actual file metadata from server
848
+ this.lastModified.textContent = fileMetadata.lastModified;
849
+ this.fileSize.textContent = fileMetadata.size;
850
+ } else if (isSample) {
851
+ // Sample content
852
+ this.lastModified.textContent = new Date().toLocaleDateString();
853
+ this.fileSize.textContent = 'Sample Content';
854
+ } else {
855
+ // Fallback when no metadata available
856
+ this.lastModified.textContent = new Date().toLocaleDateString();
857
+ this.fileSize.textContent = 'File Size Unavailable';
858
+ }
859
+
860
+ this.documentMeta.style.display = 'block';
861
+ }
862
+
863
+ showError(message) {
864
+ this.loadingState.style.display = 'none';
865
+ this.markdownContent.innerHTML = `
866
+ <div class="error">
867
+ <h3>⚠️ Error</h3>
868
+ <p>${message}</p>
869
+ <p>Please check if the file exists in the blogs directory.</p>
870
+ </div>
871
+ `;
872
+ this.markdownContent.style.display = 'block';
873
+ this.documentTitle.textContent = 'Error Loading Document';
874
+ }
875
+ }
876
+
877
+ // Initialize the blog viewer
878
+ document.addEventListener('DOMContentLoaded', () => {
879
+ new BlogViewer();
880
+ });
881
+ </script>
882
+ </body>
883
+
884
+ </html>
blogs/azure-app-service.md ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Azure App Service Hosting Guide
2
+
3
+ In this guide, we will walk through the process of setting up Azure App Service hosting for your application. This includes creating a new App Service plan, configuring the App Service, and setting up networking and private endpoints. Let's dive right in!
4
+
5
+ ## Step 1: Create a New App Service Plan
6
+
7
+ ### 1.1 Navigate to the Azure Portal
8
+
9
+ * Go to your **Azure Portal**.
10
+ * Select **App Service Plans** under the **App Services** section.
11
+
12
+ ### 1.2 Create the Plan
13
+
14
+ * Click on **+ Add** to create a new App Service Plan.
15
+ * Under **Resource Group**, select the existing group `DBS_HDU_MAIN`.
16
+ * Enter a unique **name** for your plan.
17
+ * Choose the **Operating System** (Windows or Linux).
18
+ * Set the **Region** to **Central India**.
19
+ * Select a pricing plan that fits your requirements. Ensure the plan supports up to 16 GB of RAM (no GUP).
20
+
21
+ ### 1.3 Finalize and Create
22
+
23
+ * Once all fields are filled, click on **Create** to provision the plan.
24
+
25
+ ## Step 2: Configure the App Service
26
+
27
+ ### 2.1 Create the App Service
28
+
29
+ * Navigate to the **App Services** section in the Azure portal.
30
+ * Under **Resource Group**, select the same group `DBS_HDU_MAIN`.
31
+ * Enter an **App Name** for your application.
32
+ * Select **Code** as the hosting option.
33
+ * Choose the **Runtime Stack** (such as Node.js, Python, etc.).
34
+ * Select **Linux** for the operating system.
35
+ * In the **Pricing Plan** section, select the plan created in Step 1.
36
+
37
+ ### 2.2 Network Configuration
38
+
39
+ * **Enable Public Access** should be turned **off**.
40
+ * Turn on **Virtual Network Integration** and select **Default**.
41
+ * Under **Inbound Access**, turn on the switch.
42
+ * Enter a **Private Endpoint Name** under Inbound Access.
43
+ * Select an **Inbound Subnet** from the available options.
44
+ * Set **DNS** to **Manual**.
45
+ * Enable **Outbound Access** and select the available option.
46
+
47
+ ### 2.3 Review and Create
48
+
49
+ * Once you have reviewed the settings, click on **Review + Create**.
50
+ * If there are no issues, click on **Create** to deploy the app.
51
+
52
+ > **Note:** The process might fail at first, but don't worryβ€”your app will still be deployed. Just proceed to the next steps.
53
+
54
+ ## Step 3: Add Private Endpoint (If Required)
55
+
56
+ If the private endpoint is not showing up, you will need to manually configure it.
57
+
58
+ ### 3.1 Access Networking Settings
59
+
60
+ * Go to the **App Service** settings page.
61
+ * Under **Networking**, check if there is a private endpoint listed.
62
+
63
+ * If it shows **0**, click on **Add New (Advanced)** to configure the endpoint.
64
+
65
+ ### 3.2 Configure the Private Endpoint
66
+
67
+ * Under **Resource Group**, select `DBS_HDU_MAIN`.
68
+ * Fill in the necessary **Instance Details**.
69
+ * In the **Virtual Network** tab, select the appropriate subnet with an available IP.
70
+ * Go to **DNS**, and select **GTM-LZ-Central India** for the subscription.
71
+ * The **Resource Group** will be auto-selected.
72
+
73
+ ### 3.3 Review and Create
74
+
75
+ * After reviewing the settings, click **Create**.
76
+
77
+ ## Step 4: Clean Up Unused Private Endpoints
78
+
79
+ To ensure proper networking setup, make sure you have only one active private endpoint.
80
+
81
+ * If there are any unused private endpoints, remove them from the configuration.
82
+
83
+ ## Final Thoughts
84
+
85
+ After completing these steps, your app should be successfully hosted on Azure with private network access configured. Keep an eye on the settings, especially the private endpoints, to ensure smooth performance and secure networking for your application.
blogs/azure-vm-setup.md ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Azure Virtual Machine Setup and Troubleshooting Guide
2
+
3
+ ## Point of Contact
4
+
5
+ For any issues related to Azure instances or troubleshooting errors that persist even after referring to the Standard Operating Procedure (SOP) document, please reach out to the following contacts:
6
+
7
+ * **Git-Content Team**: [[email protected]](mailto:[email protected])
8
+ * **GIT Cloud-LZSupport-Azure**: [[email protected]](mailto:[email protected])
9
+
10
+
11
+
12
+ ## Introduction
13
+
14
+ Setting up and creating an Azure Virtual Machine (AVM) requires a systematic approach. In this guide, we will cover the key steps involved in the process and provide troubleshooting advice for common issues that may arise.
15
+
16
+
17
+
18
+ ## Steps for Creating and Configuring AVM
19
+
20
+ ### 1. Configure VM
21
+
22
+ * **URL**: [Azure Virtual Desktop](https://client.wvd.microsoft.com/arm/webclient/index.html)
23
+
24
+ #### Process:
25
+
26
+ 1. Get added to the **Desktop (DBS-HDU\_POC)** group. Once access is granted (usually within 24 hours), you will be able to log in to Azure Virtual Desktop.
27
+
28
+ 2. Double-click on the Desktop shortcut to access the Virtual Machine.
29
+
30
+ 3. **Common Issue**: If the connection fails on the first attempt, minimize the window and enter your HCL credentials when prompted. If the login window does not appear automatically, it will show once you minimize the screen.
31
+
32
+ 4. Once connected, visit the Azure portal by navigating to [Azure Portal](https://portal.azure.com/) and enter your credentials to access your environment.
33
+
34
+
35
+
36
+ ### 2. Switch to HCL Directory
37
+
38
+ If you are logged into a client directory, follow these steps to switch to the HCL directory:
39
+
40
+ 1. Click on your profile picture at the top right.
41
+ 2. Select **Switch Directory** and ensure the **HCL TECH** directory is active.
42
+
43
+
44
+
45
+ ### 3. Create and Use AVM
46
+
47
+ To create the AVM using an existing resource group (e.g., `RG-Prabhat`), follow these steps:
48
+
49
+ 1. In the Azure Portal, click **Virtual Machine** from the homepage.
50
+ 2. Select **Create** > **Azure Virtual Machine**.
51
+ 3. Choose the **Existing Resource Group** (e.g., `RG-Prabhat`).
52
+ 4. Under **Image**, select **Shared Images** and pick the appropriate image.
53
+ 5. **Networking**:
54
+
55
+ * Select the Virtual Network: `DBS-HDU-spoke-vnet`
56
+ * Subnet: `DBS-HDU-pep-subnet (10.131.108.48/28)`
57
+ * Public IP: **None**
58
+ 6. Click **Create** to finalize the VM creation.
59
+
60
+
61
+
62
+ ### 4. Configuring AVM
63
+
64
+ #### Windows Proxy Settings
65
+
66
+ To ensure smooth connectivity, you need to set up proxy configurations and environment variables.
67
+
68
+ 1. **Configure Proxy Settings**:
69
+
70
+ * Contact **[[email protected]](mailto:[email protected])** for assistance in enabling URL access or solving connectivity issues.
71
+
72
+ 2. **System Variables**:
73
+
74
+ * Edit the system variables to include `http_proxy` and `https_proxy` with the value: `http://199.19.250.205:80`.
75
+
76
+
77
+
78
+ #### PIP.ini File for Trusted Hosts
79
+
80
+ 1. Navigate to the **AVM** and open **This PC** > **Disk C** > **Temp**.
81
+
82
+ 2. Create a new text file, add the following content, and rename it to **pip.ini**:
83
+
84
+ ```ini
85
+ [global]
86
+ trusted-host = pypi.python.org
87
+ pypi.org
88
+ files.pythonhosted.org
89
+ huggingface.co
90
+ ```
91
+
92
+ 3. Save the file.
93
+
94
+
95
+
96
+ #### SSL Certificate Installation
97
+
98
+ 1. Install the SSL certificates by following the **Certificate Installation SOP** document.
99
+ 2. **ActiveX Control**: Ensure ActiveX control is enabled on the machine.
100
+
101
+
102
+
103
+ #### Proxy Configuration in VS Code
104
+
105
+ 1. Open **VS Code** and navigate to **Settings**.
106
+ 2. Search for **Proxy** and edit the proxy settings as instructed in the SOP.
107
+
108
+
109
+
110
+ ### 5. Common Errors and Solutions
111
+
112
+ #### Error: **Pip Installation SSL Certificate Issues**
113
+
114
+ If you encounter SSL certificate errors during `pip install`:
115
+
116
+ 1. Open the certificate file provided in the SOP as **Notepad\_1**.
117
+
118
+ 2. In **VS Code Terminal**, run the following command:
119
+
120
+ ```bash
121
+ pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org certify
122
+ ```
123
+
124
+ 3. Run the following Python code:
125
+
126
+ ```python
127
+ import certify
128
+ print(certify.where())
129
+ ```
130
+
131
+ 4. Copy the file path output and open the corresponding file in Notepad (**Notepad\_2**).
132
+
133
+ 5. Append the contents from **Notepad\_1** to the end of **Notepad\_2** and save the file.
134
+
135
+
136
+
137
+ #### Error: **Private Endpoint Error**
138
+
139
+ If you receive an error such as:
140
+
141
+ ```
142
+ openai.PermissionDeniedError: Error code: 403 - {'error': {'code': '403', 'message': 'Public access is disabled. Please configure private endpoint.'}}
143
+ ```
144
+
145
+ Solution: Use the following one-liner code to bypass this error:
146
+
147
+ ```python
148
+ os.environ["NO_PROXY"] = "https://prabhatopenaiservice.openai.azure.com"
149
+ ```
150
+
151
+
152
+
153
+ ### 6. Existing AVM from Resource Group
154
+
155
+ To access an existing AVM from a resource group:
156
+
157
+ 1. In the **Azure Portal**, click on the **Resource Group**.
158
+ 2. Select **Virtual Machine** and click **Connect**.
159
+ 3. Download the **RDP** file and open it.
160
+ 4. Enter your credentials to connect to the VM.
161
+
162
+
163
+
164
+ ### 7. Test Functionality
165
+
166
+ Once your AVM is configured, run the following tests to ensure everything is set up correctly:
167
+
168
+ #### Test 1: Pip Installation and API Access
169
+
170
+ ```python
171
+ import os
172
+ from dotenv import load_dotenv
173
+ from langchain_openai import AzureChatOpenAI
174
+ from langchain_core.messages import SystemMessage, HumanMessage
175
+
176
+ # Load environment variables
177
+ load_dotenv()
178
+ os.environ["NO_PROXY"] = "https://prabhatopenaiservice.openai.azure.com"
179
+
180
+ # Initialize OpenAI API client
181
+ AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
182
+ AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
183
+ AZURE_OPENAI_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
184
+ AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")
185
+ AZURE_OPENAI_TEMPERATURE = float(os.getenv("AZURE_OPENAI_TEMPERATURE"))
186
+
187
+ llm = AzureChatOpenAI(
188
+ azure_endpoint=AZURE_OPENAI_ENDPOINT,
189
+ azure_deployment=AZURE_OPENAI_DEPLOYMENT_NAME,
190
+ api_key=AZURE_OPENAI_API_KEY,
191
+ api_version=AZURE_OPENAI_API_VERSION,
192
+ temperature=AZURE_OPENAI_TEMPERATURE,
193
+ )
194
+
195
+ # Test querying a document
196
+ with open("demo.txt", "r", encoding="utf-8") as f:
197
+ document_content = f.read()
198
+
199
+ system_prompt = "You are a helpful assistant that answers questions based on the given document."
200
+ user_query = "Who is the highest run scorer in BGT history?"
201
+
202
+ messages = [
203
+ SystemMessage(content=system_prompt),
204
+ HumanMessage(content=f"Document:\n{document_content}\n\nQuestion: {user_query}")
205
+ ]
206
+
207
+ response = llm.invoke(messages)
208
+ print(response.content)
209
+ ```
210
+
211
+
212
+
213
+ #### Test 2: Hugging Face Model and Embeddings
214
+
215
+ ```python
216
+ import transformers
217
+
218
+ pipeline = transformers.pipeline(
219
+ "text-generation",
220
+ model="microsoft/phi-4",
221
+ model_kwargs={"torch_dtype": "auto"},
222
+ device_map="auto",
223
+ )
224
+
225
+ messages = [
226
+ {"role": "system", "content": "You are a medieval knight and must provide explanations to modern people."},
227
+ {"role": "user", "content": "How should I explain the Internet?"},
228
+ ]
229
+
230
+ outputs = pipeline(messages, max_new_tokens=128)
231
+ print(outputs[0]["generated_text"][-1])
232
+ ```
233
+
234
+
235
+
236
+ #### Test 3: FAISS Indexing and Search
237
+
238
+ ```python
239
+ import time
240
+ import faiss
241
+ import numpy as np
242
+
243
+ # Start test
244
+ start_time = time.time()
245
+
246
+ # Create dummy vectors
247
+ embedding_dim = 384
248
+ num_vectors = 100
249
+ db_vectors = np.random.random((num_vectors, embedding_dim)).astype('float32')
250
+ query_vector = np.random.random((1, embedding_dim)).astype('float32')
251
+
252
+ # Build FAISS index
253
+ index = faiss.IndexFlatL2(embedding_dim)
254
+ index.add(db_vectors)
255
+
256
+ # Perform search
257
+ k = 5
258
+ distances, indices = index.search(query_vector, k)
259
+ print(f"Search results: {distances}, {indices}")
260
+
261
+ # End test
262
+ end_time = time.time()
263
+ print(f"Test 3 Finished in {end_time - start_time:.2f} seconds")
264
+ ```
blogs/itimesheet.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How to Fill Timesheets
2
+
3
+ Filling out your timesheet in the **MyHCL** portal can be straightforward if you follow the right steps. Whether you're new to the portal or just need a refresher, here’s a quick guide to help you log your hours seamlessly.
4
+
5
+
6
+
7
+ ## πŸ” Step 1: Log in to MyHCL
8
+
9
+ Start by visiting [**myhcl.com**](https://www.myhcl.com) and log in using your HCL credentials.
10
+
11
+
12
+
13
+ ## πŸ” Step 2: Search for iTimesheet
14
+
15
+ Once logged in:
16
+
17
+ * Locate the **search bar** on the homepage.
18
+ * Type **iTimesheet** into the search field.
19
+ * Click on the **iTimesheet** link from the results.
20
+
21
+
22
+
23
+ ## βž• Step 3: Add/Edit Project, Phase & Activity
24
+
25
+ After opening the iTimesheet portal:
26
+
27
+ 1. Click on **Add/Edit Project/Phase/Activity**.
28
+
29
+ 2. A modal window will appear. In this window:
30
+
31
+ * Select **Project**: Choose `hclt`
32
+ * Select your assigned project: `DBS GENAI`
33
+ * Choose the appropriate **Phase**
34
+ * Choose the relevant `Activity`
35
+
36
+ 3. Click **Save and Proceed** to continue.
37
+
38
+
39
+
40
+ ## πŸ“ Step 4: Fill Your Timesheet
41
+
42
+ Now that your project details are set up:
43
+
44
+ * Navigate to the respective date slots.
45
+ * Enter your working hours or activities as required.
46
+ * Double-check for accuracy.
47
+
48
+ > ⚠️ **Important Note:**
49
+ > Make sure to fill your timesheet **only for the current week**.
50
+ > Filling out past or future weeks may result in being **marked absent**.
51
+
52
+
53
+
54
+ ## βœ… Step 5: Submit for Approval
55
+
56
+ Once you have filled in all your time entries:
57
+
58
+ * Click on **Submit for Approval**.
59
+
60
+ Your timesheet will now be forwarded to your reporting manager or supervisor for approval.
61
+
62
+
63
+
64
+ ## πŸŽ‰ That’s It!
65
+
66
+ By following these steps, you'll ensure your time is accurately logged and submitted on time. Make it a weekly habit to avoid last-minute rushes or missed deadlines.
67
+
68
+ Happy working! πŸ’Ό
index.html ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>AI HDU - Self Help Desk</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Lexend:[email protected]&display=swap" rel="stylesheet">
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ font-family: "Lexend", sans-serif;
17
+ }
18
+
19
+ body {
20
+ /* font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; */
21
+ line-height: 1.5;
22
+ color: #1d1d1f;
23
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
24
+ min-height: 100vh;
25
+ overflow-x: hidden;
26
+ }
27
+
28
+ /* Top Navigation Bar */
29
+ .top-nav {
30
+ position: fixed;
31
+ top: 0;
32
+ left: 0;
33
+ right: 0;
34
+ height: 60px;
35
+ background: rgba(255, 255, 255, 0.6);
36
+ backdrop-filter: blur(10px);
37
+ -webkit-backdrop-filter: blur(10px);
38
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
39
+ z-index: 1000;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ margin: 10px;
44
+ border-radius: 20px;
45
+ box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
46
+ }
47
+
48
+ .nav-content {
49
+ /* max-width: 1200px; */
50
+ width: 100%;
51
+ padding: 0 24px;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: space-between;
55
+ }
56
+
57
+ .nav-title {
58
+ font-size: 1.1rem;
59
+ font-weight: 600;
60
+ color: #1d1d1f;
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ }
65
+
66
+ .nav-stats {
67
+ font-size: 0.85rem;
68
+ color: #86868b;
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 16px;
72
+ }
73
+
74
+ /* Main Container */
75
+ .container {
76
+ max-width: 1400px;
77
+ margin: 0 auto;
78
+ padding: 80px 20px 120px;
79
+ }
80
+
81
+ /* Content Grid */
82
+ .blog-grid {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
85
+ gap: 16px;
86
+ margin-top: 20px;
87
+ }
88
+
89
+ .blog-card {
90
+ background: rgba(255, 255, 255, 0.8);
91
+ backdrop-filter: blur(20px);
92
+ -webkit-backdrop-filter: blur(20px);
93
+ border: 1px solid rgba(255, 255, 255, 0.3);
94
+ border-radius: 12px;
95
+ padding: 16px;
96
+ transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
97
+ cursor: pointer;
98
+ position: relative;
99
+ overflow: hidden;
100
+ }
101
+
102
+ .blog-card:hover {
103
+ transform: translateY(-2px);
104
+ background: rgba(255, 255, 255, 0.9);
105
+ border-color: rgba(0, 122, 255, 0.3);
106
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
107
+ }
108
+
109
+ .blog-title {
110
+ font-size: 1.1rem;
111
+ font-weight: 600;
112
+ color: #1d1d1f;
113
+ margin-bottom: 6px;
114
+ line-height: 1.3;
115
+ display: -webkit-box;
116
+ -webkit-line-clamp: 2;
117
+ line-clamp: 2;
118
+ -webkit-box-orient: vertical;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .blog-excerpt {
123
+ color: #86868b;
124
+ margin-bottom: 12px;
125
+ line-height: 1.4;
126
+ font-size: 0.9rem;
127
+ display: -webkit-box;
128
+ -webkit-line-clamp: 2;
129
+ line-clamp: 2;
130
+ -webkit-box-orient: vertical;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .blog-meta {
135
+ display: flex;
136
+ justify-content: space-between;
137
+ align-items: center;
138
+ font-size: 0.8rem;
139
+ color: #86868b;
140
+ }
141
+
142
+ .blog-date {
143
+ color: #007aff;
144
+ font-weight: 500;
145
+ }
146
+
147
+ .blog-size {
148
+ background: rgba(0, 122, 255, 0.1);
149
+ color: #007aff;
150
+ padding: 2px 6px;
151
+ border-radius: 6px;
152
+ font-weight: 500;
153
+ }
154
+
155
+ .match-indicators {
156
+ font-size: 0.75rem;
157
+ color: #007aff;
158
+ margin-bottom: 6px;
159
+ font-weight: 500;
160
+ }
161
+
162
+ /* Floating Search Bar */
163
+ .search-container {
164
+ position: fixed;
165
+ bottom: 24px;
166
+ left: 50%;
167
+ transform: translateX(-50%);
168
+ background: rgba(255, 255, 255, 0.1);
169
+ backdrop-filter: blur(1px);
170
+ -webkit-backdrop-filter: blur(1px);
171
+ border: 1px solid rgba(255, 255, 255, 0.3);
172
+ border-radius: 20px;
173
+ padding: 20px;
174
+ max-width: 500px;
175
+ width: calc(100% - 48px);
176
+ box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
177
+ z-index: 999;
178
+ }
179
+
180
+ .search-box {
181
+ position: relative;
182
+ }
183
+
184
+ .search-input {
185
+ width: 100%;
186
+ padding: 10px 15px;
187
+ font-size: 1rem;
188
+ border: none;
189
+ background: rgba(255, 255, 255, 0.9);
190
+ border-radius: 10px;
191
+ outline: none;
192
+ transition: all 0.3s ease;
193
+ color: #1d1d1f;
194
+ }
195
+
196
+ .search-input::placeholder {
197
+ color: #86868b;
198
+ }
199
+
200
+ .search-input:focus {
201
+ background: rgba(255, 255, 255, 0.9);
202
+ box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.3);
203
+ }
204
+
205
+ .search-icon {
206
+ position: absolute;
207
+ right: 12px;
208
+ top: 50%;
209
+ transform: translateY(-50%);
210
+ color: #86868b;
211
+ font-size: 1rem;
212
+ }
213
+
214
+ .search-highlight {
215
+ background: rgba(255, 214, 10, 0.7);
216
+ color: #1d1d1f;
217
+ padding: 1px 2px;
218
+ border-radius: 3px;
219
+ font-weight: 600;
220
+ }
221
+
222
+ .no-results {
223
+ text-align: center;
224
+ padding: 60px 20px;
225
+ color: #86868b;
226
+ background: rgba(255, 255, 255, 0.6);
227
+ backdrop-filter: blur(20px);
228
+ -webkit-backdrop-filter: blur(20px);
229
+ border-radius: 16px;
230
+ border: 1px solid rgba(255, 255, 255, 0.3);
231
+ }
232
+
233
+ .no-results h3 {
234
+ font-size: 1.3rem;
235
+ margin-bottom: 8px;
236
+ color: #1d1d1f;
237
+ font-weight: 600;
238
+ }
239
+
240
+ .loading {
241
+ text-align: center;
242
+ padding: 40px;
243
+ color: #007aff;
244
+ font-size: 1rem;
245
+ background: rgba(255, 255, 255, 0.6);
246
+ backdrop-filter: blur(20px);
247
+ -webkit-backdrop-filter: blur(20px);
248
+ border-radius: 16px;
249
+ border: 1px solid rgba(255, 255, 255, 0.3);
250
+ }
251
+
252
+ .error {
253
+ background: rgba(255, 59, 48, 0.1);
254
+ border: 1px solid rgba(255, 59, 48, 0.3);
255
+ color: #d70015;
256
+ padding: 16px;
257
+ border-radius: 12px;
258
+ margin: 20px 0;
259
+ backdrop-filter: blur(20px);
260
+ -webkit-backdrop-filter: blur(20px);
261
+ }
262
+
263
+ /* Smooth scrolling */
264
+ html {
265
+ scroll-behavior: smooth;
266
+ }
267
+
268
+ /* Custom scrollbar */
269
+ ::-webkit-scrollbar {
270
+ width: 8px;
271
+ }
272
+
273
+ ::-webkit-scrollbar-track {
274
+ background: rgba(255, 255, 255, 0.1);
275
+ }
276
+
277
+ ::-webkit-scrollbar-thumb {
278
+ background: rgba(0, 0, 0, 0.2);
279
+ border-radius: 4px;
280
+ }
281
+
282
+ ::-webkit-scrollbar-thumb:hover {
283
+ background: rgba(0, 0, 0, 0.3);
284
+ }
285
+
286
+ @media (max-width: 768px) {
287
+ .container {
288
+ padding: 80px 16px 120px;
289
+ }
290
+
291
+ .blog-grid {
292
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
293
+ gap: 12px;
294
+ }
295
+
296
+ .nav-content {
297
+ padding: 0 16px;
298
+ }
299
+
300
+ .nav-stats {
301
+ flex-direction: column;
302
+ gap: 4px;
303
+ align-items: flex-end;
304
+ font-size: 0.8rem;
305
+ }
306
+
307
+ .search-container {
308
+ bottom: 16px;
309
+ width: calc(100% - 32px);
310
+ }
311
+ }
312
+
313
+ @media (max-width: 480px) {
314
+ .blog-grid {
315
+ grid-template-columns: 1fr;
316
+ }
317
+
318
+ .nav-stats {
319
+ display: none;
320
+ }
321
+ }
322
+ </style>
323
+ </head>
324
+
325
+ <body>
326
+ <!-- Top Navigation -->
327
+ <div class="top-nav">
328
+ <div class="nav-content">
329
+ <div class="nav-title">
330
+ <span>πŸ€–</span>
331
+ <span>Artificial Intelligence Horizontal Division Unit - Self Help Desk</span>
332
+ </div>
333
+ <div class="nav-stats">
334
+ <span id="resultsCount">Loading...</span>
335
+ <span id="lastUpdated"></span>
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <!-- Main Container -->
341
+ <div class="container">
342
+ <div id="blogContainer">
343
+ <div class="loading">
344
+ πŸ“ Loading documentation files...
345
+ </div>
346
+ </div>
347
+ </div>
348
+
349
+ <!-- Floating Search Bar -->
350
+ <div class="search-container">
351
+ <div class="search-box">
352
+ <input type="text" class="search-input" placeholder="Search documentation..." id="searchInput">
353
+ <span class="search-icon">πŸ”</span>
354
+ </div>
355
+ </div>
356
+
357
+ <script>
358
+ class DocumentationHub {
359
+ constructor() {
360
+ this.blogs = [];
361
+ this.filteredBlogs = [];
362
+ this.allBlogsCache = []; // Cache for offline search
363
+ this.searchInput = document.getElementById('searchInput');
364
+ this.blogContainer = document.getElementById('blogContainer');
365
+ this.resultsCount = document.getElementById('resultsCount');
366
+ this.lastUpdated = document.getElementById('lastUpdated');
367
+ this.searchTimeout = null; // For debouncing
368
+
369
+ this.init();
370
+ }
371
+
372
+ async init() {
373
+ await this.loadBlogs();
374
+ this.setupEventListeners();
375
+ this.renderBlogs();
376
+ this.updateStats();
377
+ }
378
+
379
+ async loadBlogs() {
380
+ try {
381
+ // Try to fetch from the local API first
382
+ try {
383
+ const response = await fetch('/api/blogs');
384
+ if (response.ok) {
385
+ this.blogs = await response.json();
386
+ this.allBlogsCache = [...this.blogs]; // Cache for offline search
387
+ this.filteredBlogs = [...this.blogs];
388
+ return;
389
+ }
390
+ } catch (error) {
391
+ console.log('Local API not available, using sample data');
392
+ }
393
+
394
+ // Fallback to sample data
395
+ const sampleBlogs = [
396
+ {
397
+ filename: 'getting-started.md',
398
+ title: 'Getting Started Guide',
399
+ excerpt: 'Learn how to get started with our platform and set up your first project.',
400
+ size: '2.5 KB',
401
+ lastModified: '2025-06-20'
402
+ },
403
+ {
404
+ filename: 'api-documentation.md',
405
+ title: 'API Documentation',
406
+ excerpt: 'Complete reference for all API endpoints, authentication, and examples.',
407
+ size: '15.2 KB',
408
+ lastModified: '2025-06-25'
409
+ },
410
+ {
411
+ filename: 'troubleshooting.md',
412
+ title: 'Troubleshooting Guide',
413
+ excerpt: 'Common issues and their solutions to help you resolve problems quickly.',
414
+ size: '8.7 KB',
415
+ lastModified: '2025-06-22'
416
+ }
417
+ ];
418
+
419
+ this.blogs = sampleBlogs;
420
+ this.allBlogsCache = [...sampleBlogs];
421
+ this.filteredBlogs = [...this.blogs];
422
+ } catch (error) {
423
+ this.showError('Failed to load documentation files');
424
+ }
425
+ }
426
+
427
+ setupEventListeners() {
428
+ this.searchInput.addEventListener('input', (e) => {
429
+ // Debounce search for better performance
430
+ if (this.searchTimeout) {
431
+ clearTimeout(this.searchTimeout);
432
+ }
433
+ this.searchTimeout = setTimeout(() => {
434
+ this.searchBlogs(e.target.value);
435
+ }, 300);
436
+ });
437
+
438
+ this.searchInput.addEventListener('keypress', (e) => {
439
+ if (e.key === 'Enter') {
440
+ if (this.searchTimeout) {
441
+ clearTimeout(this.searchTimeout);
442
+ }
443
+ this.searchBlogs(e.target.value);
444
+ }
445
+ });
446
+ }
447
+
448
+ async searchBlogs(query) {
449
+ const searchTerm = query.toLowerCase().trim();
450
+
451
+ if (!searchTerm) {
452
+ this.filteredBlogs = [...this.allBlogsCache];
453
+ this.renderBlogs();
454
+ this.updateStats();
455
+ return;
456
+ }
457
+
458
+ // Show loading state for search
459
+ this.showSearchLoading();
460
+
461
+ try {
462
+ // Try to use server-side search API for full-text search
463
+ const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`);
464
+ if (response.ok) {
465
+ const searchData = await response.json();
466
+ this.filteredBlogs = searchData.results;
467
+ this.renderBlogs(searchTerm);
468
+ this.updateStats(searchData.query);
469
+ return;
470
+ }
471
+ } catch (error) {
472
+ console.log('Server search not available, using client-side search');
473
+ }
474
+
475
+ // Fallback to client-side search (title, excerpt, filename only)
476
+ this.filteredBlogs = this.allBlogsCache.filter(blog =>
477
+ blog.title.toLowerCase().includes(searchTerm) ||
478
+ blog.excerpt.toLowerCase().includes(searchTerm) ||
479
+ blog.filename.toLowerCase().includes(searchTerm)
480
+ );
481
+
482
+ this.renderBlogs(searchTerm);
483
+ this.updateStats(searchTerm);
484
+ }
485
+
486
+ showSearchLoading() {
487
+ this.blogContainer.innerHTML = `
488
+ <div class="loading">
489
+ πŸ” Searching through documentation...
490
+ </div>
491
+ `;
492
+ }
493
+
494
+ renderBlogs(searchTerm = '') {
495
+ if (this.filteredBlogs.length === 0) {
496
+ const noResultsHTML = searchTerm
497
+ ? `<div class="no-results">
498
+ <h3>πŸ” No documents found for "${searchTerm}"</h3>
499
+ <p>Try different keywords or browse all available documentation.</p>
500
+ </div>`
501
+ : `<div class="no-results">
502
+ <h3>πŸ“ No documents available</h3>
503
+ <p>Add markdown files to the blogs directory to get started.</p>
504
+ </div>`;
505
+
506
+ this.blogContainer.innerHTML = noResultsHTML;
507
+ return;
508
+ }
509
+
510
+ const blogHTML = this.filteredBlogs.map(blog => {
511
+ // Highlight search terms in title and excerpt
512
+ let highlightedTitle = blog.title;
513
+ let highlightedExcerpt = blog.excerpt;
514
+
515
+ if (searchTerm) {
516
+ highlightedTitle = this.highlightText(blog.title, searchTerm);
517
+ highlightedExcerpt = this.highlightText(blog.excerpt, searchTerm);
518
+ }
519
+
520
+ // Show match indicators if available
521
+ let matchIndicators = '';
522
+ if (blog.matches) {
523
+ const indicators = [];
524
+ if (blog.matches.filename) indicators.push('πŸ“ filename');
525
+ if (blog.matches.title) indicators.push('πŸ“ title');
526
+ if (blog.matches.content) indicators.push('πŸ“„ content');
527
+
528
+ if (indicators.length > 0) {
529
+ matchIndicators = `<div class="match-indicators">Found in: ${indicators.join(', ')}</div>`;
530
+ }
531
+ }
532
+
533
+ return `
534
+ <div class="blog-card" onclick="window.openBlog('${blog.filename}')">
535
+ <div class="blog-title">${highlightedTitle}</div>
536
+ <div class="blog-excerpt">${highlightedExcerpt}</div>
537
+ ${matchIndicators}
538
+ <div class="blog-meta">
539
+ <span class="blog-date">πŸ“… ${blog.lastModified}</span>
540
+ <span class="blog-size">πŸ“„ ${blog.size}</span>
541
+ </div>
542
+ </div>
543
+ `;
544
+ }).join('');
545
+
546
+ this.blogContainer.innerHTML = `<div class="blog-grid">${blogHTML}</div>`;
547
+ }
548
+
549
+ highlightText(text, searchTerm) {
550
+ if (!searchTerm) return text;
551
+
552
+ const regex = new RegExp(`(${this.escapeRegExp(searchTerm)})`, 'gi');
553
+ return text.replace(regex, '<mark class="search-highlight">$1</mark>');
554
+ }
555
+
556
+ escapeRegExp(string) {
557
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
558
+ }
559
+
560
+ updateStats(searchQuery = '') {
561
+ const total = this.allBlogsCache.length;
562
+ const shown = this.filteredBlogs.length;
563
+
564
+ if (searchQuery) {
565
+ this.resultsCount.textContent = `${shown} of ${total} documents found`;
566
+ } else if (this.searchInput.value.trim()) {
567
+ this.resultsCount.textContent = `${shown} of ${total} documents`;
568
+ } else {
569
+ this.resultsCount.textContent = `${total} documents`;
570
+ }
571
+
572
+ this.lastUpdated.textContent = `Updated ${new Date().toLocaleDateString()}`;
573
+ }
574
+
575
+ showError(message) {
576
+ this.blogContainer.innerHTML = `
577
+ <div class="error">
578
+ <strong>Error:</strong> ${message}
579
+ </div>
580
+ `;
581
+ }
582
+ }
583
+
584
+ // Global function to open blog files
585
+ window.openBlog = function (filename) {
586
+ // Open the blog viewer in a new window
587
+ const blogUrl = `./blog-viewer.html?file=${encodeURIComponent(filename)}`;
588
+ window.open(blogUrl, '_blank', 'width=1000,height=700,scrollbars=yes,resizable=yes');
589
+ };
590
+
591
+ // Initialize the documentation hub
592
+ document.addEventListener('DOMContentLoaded', () => {
593
+ new DocumentationHub();
594
+ });
595
+ </script>
596
+ </body>
597
+
598
+ </html>
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # No external dependencies required - using only Python standard library
2
+ # The server uses only built-in modules: http.server, socketserver, json, os, pathlib, etc.
server.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Simple HTTP server for serving the documentation hub locally.
4
+ This allows proper CORS handling and file serving for the markdown files.
5
+ """
6
+
7
+ import http.server
8
+ import socketserver
9
+ import json
10
+ import os
11
+ import mimetypes
12
+ from urllib.parse import urlparse, parse_qs
13
+ from pathlib import Path
14
+
15
+ class DocumentationHandler(http.server.SimpleHTTPRequestHandler):
16
+ def __init__(self, *args, **kwargs):
17
+ super().__init__(*args, directory=str(Path(__file__).parent), **kwargs)
18
+
19
+ def end_headers(self):
20
+ # Add CORS headers
21
+ self.send_header('Access-Control-Allow-Origin', '*')
22
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
23
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
24
+ super().end_headers()
25
+
26
+ def do_GET(self):
27
+ # Handle the blogs directory listing API
28
+ if self.path.startswith('/api/blogs'):
29
+ self.handle_blogs_api()
30
+ elif self.path.startswith('/api/search'):
31
+ self.handle_search_api()
32
+ else:
33
+ # Serve static files normally
34
+ super().do_GET()
35
+
36
+ def handle_blogs_api(self):
37
+ """API endpoint to list blog files and their metadata"""
38
+ try:
39
+ blogs_dir = Path(__file__).parent / 'blogs'
40
+ if not blogs_dir.exists():
41
+ self.send_error(404, "Blogs directory not found")
42
+ return
43
+
44
+ blogs = []
45
+ for md_file in blogs_dir.glob('*.md'):
46
+ # Get file stats
47
+ stat = md_file.stat()
48
+ size_kb = round(stat.st_size / 1024, 1)
49
+
50
+ # Read first few lines to extract title and excerpt
51
+ try:
52
+ with open(md_file, 'r', encoding='utf-8') as f:
53
+ content = f.read()
54
+ lines = content.split('\n')
55
+
56
+ # Extract title (first # header)
57
+ title = md_file.stem.replace('-', ' ').title()
58
+ for line in lines:
59
+ if line.startswith('# '):
60
+ title = line[2:].strip()
61
+ break
62
+
63
+ # Extract excerpt (first paragraph after title)
64
+ excerpt = "No description available."
65
+ for i, line in enumerate(lines):
66
+ if line.startswith('# '):
67
+ # Look for first non-empty paragraph after title
68
+ for j in range(i + 1, min(i + 10, len(lines))):
69
+ if lines[j].strip() and not lines[j].startswith('#'):
70
+ excerpt = lines[j].strip()[:150]
71
+ if len(lines[j].strip()) > 150:
72
+ excerpt += "..."
73
+ break
74
+ break
75
+
76
+ blogs.append({
77
+ 'filename': md_file.name,
78
+ 'title': title,
79
+ 'excerpt': excerpt,
80
+ 'size': f"{size_kb} KB",
81
+ 'lastModified': stat.st_mtime
82
+ })
83
+ except Exception as e:
84
+ print(f"Error reading {md_file}: {e}")
85
+ continue
86
+
87
+ # Sort by last modified (newest first)
88
+ blogs.sort(key=lambda x: x['lastModified'], reverse=True)
89
+
90
+ # Format dates
91
+ import datetime
92
+ for blog in blogs:
93
+ blog['lastModified'] = datetime.datetime.fromtimestamp(
94
+ blog['lastModified']
95
+ ).strftime('%Y-%m-%d')
96
+
97
+ # Send JSON response
98
+ response = json.dumps(blogs, indent=2)
99
+
100
+ self.send_response(200)
101
+ self.send_header('Content-Type', 'application/json')
102
+ self.end_headers()
103
+ self.wfile.write(response.encode('utf-8'))
104
+
105
+ except Exception as e:
106
+ print(f"Error in blogs API: {e}")
107
+ self.send_error(500, f"Internal server error: {e}")
108
+
109
+ def handle_search_api(self):
110
+ """API endpoint to search within blog content"""
111
+ try:
112
+ # Parse query parameters
113
+ parsed_url = urlparse(self.path)
114
+ query_params = parse_qs(parsed_url.query)
115
+ search_query = query_params.get('q', [''])[0].lower().strip()
116
+
117
+ if not search_query:
118
+ self.send_error(400, "Missing search query parameter 'q'")
119
+ return
120
+
121
+ blogs_dir = Path(__file__).parent / 'blogs'
122
+ if not blogs_dir.exists():
123
+ self.send_error(404, "Blogs directory not found")
124
+ return
125
+
126
+ search_results = []
127
+ for md_file in blogs_dir.glob('*.md'):
128
+ try:
129
+ with open(md_file, 'r', encoding='utf-8') as f:
130
+ content = f.read()
131
+ lines = content.split('\n')
132
+
133
+ # Extract title
134
+ title = md_file.stem.replace('-', ' ').title()
135
+ for line in lines:
136
+ if line.startswith('# '):
137
+ title = line[2:].strip()
138
+ break
139
+
140
+ # Check if search term is in title, filename, or content
141
+ filename_match = search_query in md_file.name.lower()
142
+ title_match = search_query in title.lower()
143
+ content_match = search_query in content.lower()
144
+
145
+ if filename_match or title_match or content_match:
146
+ # Get file stats
147
+ stat = md_file.stat()
148
+ size_kb = round(stat.st_size / 1024, 1)
149
+
150
+ # Extract excerpt (preferably containing search term)
151
+ excerpt = self.extract_search_excerpt(content, search_query)
152
+
153
+ # Calculate relevance score
154
+ relevance = 0
155
+ if filename_match:
156
+ relevance += 3
157
+ if title_match:
158
+ relevance += 2
159
+ if content_match:
160
+ relevance += 1
161
+
162
+ search_results.append({
163
+ 'filename': md_file.name,
164
+ 'title': title,
165
+ 'excerpt': excerpt,
166
+ 'size': f"{size_kb} KB",
167
+ 'lastModified': stat.st_mtime,
168
+ 'relevance': relevance,
169
+ 'matches': {
170
+ 'filename': filename_match,
171
+ 'title': title_match,
172
+ 'content': content_match
173
+ }
174
+ })
175
+ except Exception as e:
176
+ print(f"Error searching {md_file}: {e}")
177
+ continue
178
+
179
+ # Sort by relevance, then by last modified
180
+ search_results.sort(key=lambda x: (x['relevance'], x['lastModified']), reverse=True)
181
+
182
+ # Format dates
183
+ import datetime
184
+ for result in search_results:
185
+ result['lastModified'] = datetime.datetime.fromtimestamp(
186
+ result['lastModified']
187
+ ).strftime('%Y-%m-%d')
188
+ # Remove relevance from final response
189
+ del result['relevance']
190
+
191
+ # Send JSON response
192
+ response = json.dumps({
193
+ 'query': search_query,
194
+ 'results': search_results,
195
+ 'total': len(search_results)
196
+ }, indent=2)
197
+
198
+ self.send_response(200)
199
+ self.send_header('Content-Type', 'application/json')
200
+ self.end_headers()
201
+ self.wfile.write(response.encode('utf-8'))
202
+
203
+ except Exception as e:
204
+ print(f"Error in search API: {e}")
205
+ self.send_error(500, f"Internal server error: {e}")
206
+
207
+ def extract_search_excerpt(self, content, search_term, context_chars=150):
208
+ """Extract excerpt around search term or fallback to beginning"""
209
+ content_lower = content.lower()
210
+ search_pos = content_lower.find(search_term)
211
+
212
+ if search_pos == -1:
213
+ # If search term not found, return beginning of content
214
+ lines = content.split('\n')
215
+ for line in lines:
216
+ if line.strip() and not line.startswith('#'):
217
+ excerpt = line.strip()[:context_chars]
218
+ if len(line.strip()) > context_chars:
219
+ excerpt += "..."
220
+ return excerpt
221
+ return "No description available."
222
+
223
+ # Extract context around the search term
224
+ start = max(0, search_pos - context_chars // 2)
225
+ end = min(len(content), search_pos + len(search_term) + context_chars // 2)
226
+
227
+ excerpt = content[start:end].strip()
228
+
229
+ # Clean up excerpt (remove incomplete words at edges)
230
+ if start > 0:
231
+ space_pos = excerpt.find(' ')
232
+ if space_pos > 0:
233
+ excerpt = excerpt[space_pos:].strip()
234
+ excerpt = "..." + excerpt
235
+
236
+ if end < len(content):
237
+ last_space = excerpt.rfind(' ')
238
+ if last_space > 0:
239
+ excerpt = excerpt[:last_space].strip()
240
+ excerpt += "..."
241
+
242
+ return excerpt
243
+
244
+ def run_server(port=8000):
245
+ """Start the documentation server"""
246
+ try:
247
+ with socketserver.TCPServer(("", port), DocumentationHandler) as httpd:
248
+ print(f"🌐 Server running at: http://localhost:{port}")
249
+ print(f"πŸ“ Serving from: {Path(__file__).parent}")
250
+ print(f"πŸ” Open http://localhost:{port} in your browser")
251
+ print()
252
+ httpd.serve_forever()
253
+ except KeyboardInterrupt:
254
+ print("\nπŸ‘‹ Server stopped by user")
255
+ except OSError as e:
256
+ if e.errno == 10048: # Windows: Address already in use
257
+ print(f"❌ Port {port} is already in use. Try a different port:")
258
+ print(f" python server.py --port 8001")
259
+ else:
260
+ print(f"❌ Error starting server: {e}")
261
+
262
+ if __name__ == "__main__":
263
+ import sys
264
+ port = 8000
265
+
266
+ # Simple command line argument parsing
267
+ if len(sys.argv) > 1:
268
+ for i, arg in enumerate(sys.argv):
269
+ if arg in ['--port', '-p'] and i + 1 < len(sys.argv):
270
+ try:
271
+ port = int(sys.argv[i + 1])
272
+ except ValueError:
273
+ print("❌ Invalid port number")
274
+ sys.exit(1)
275
+ elif arg in ['--help', '-h']:
276
+ print("Usage: python server.py [--port PORT]")
277
+ print("Options:")
278
+ print(" --port, -p PORT Port to run server on (default: 8000)")
279
+ print(" --help, -h Show this help message")
280
+ sys.exit(0)
281
+
282
+ run_server(port)