Martechsol commited on
Commit
5879cf4
·
1 Parent(s): dae9ce0

chore: ignore and untrack backup folders

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +4 -0
  2. backup_2026-05-04/.env.example +0 -15
  3. backup_2026-05-04/.gitattributes +0 -2
  4. backup_2026-05-04/.gitignore +0 -9
  5. backup_2026-05-04/CONFIG_NOTES.md +0 -38
  6. backup_2026-05-04/CUSTOMIZATION_GUIDE.md +0 -55
  7. backup_2026-05-04/Dockerfile +0 -31
  8. backup_2026-05-04/PHPMailer/Exception.php +0 -40
  9. backup_2026-05-04/PHPMailer/PHPMailer.php +0 -0
  10. backup_2026-05-04/PHPMailer/SMTP.php +0 -1509
  11. backup_2026-05-04/PROJECT_OVERVIEW.md +0 -32
  12. backup_2026-05-04/README.md +0 -164
  13. backup_2026-05-04/app.py +0 -6
  14. backup_2026-05-04/app/__init__.py +0 -0
  15. backup_2026-05-04/app/admin/__init__.py +0 -1
  16. backup_2026-05-04/app/admin/router.py +0 -346
  17. backup_2026-05-04/app/admin/templates/admin.html +0 -1143
  18. backup_2026-05-04/app/api/__init__.py +0 -0
  19. backup_2026-05-04/app/api/schemas.py +0 -27
  20. backup_2026-05-04/app/core/__init__.py +0 -0
  21. backup_2026-05-04/app/core/config.py +0 -51
  22. backup_2026-05-04/app/main.py +0 -196
  23. backup_2026-05-04/app/services/__init__.py +0 -0
  24. backup_2026-05-04/app/services/chunker.py +0 -39
  25. backup_2026-05-04/app/services/document_loader.py +0 -37
  26. backup_2026-05-04/app/services/embeddings.py +0 -34
  27. backup_2026-05-04/app/services/llm.py +0 -264
  28. backup_2026-05-04/app/services/rag_pipeline.py +0 -98
  29. backup_2026-05-04/app/services/rate_limiter.py +0 -24
  30. backup_2026-05-04/app/services/reranker.py +0 -26
  31. backup_2026-05-04/app/services/session_store.py +0 -284
  32. backup_2026-05-04/app/services/vector_store.py +0 -167
  33. backup_2026-05-04/app/ui_gradio.py +0 -275
  34. backup_2026-05-04/docker-compose.yml +0 -14
  35. backup_2026-05-04/docs/MartechSol_Employee_HandBook (7) (1).pdf +0 -3
  36. backup_2026-05-04/docs/MartechSol_Employee_Handbook_Extracted.txt +0 -1091
  37. backup_2026-05-04/floaitng-icon/KhloeSAC.lottie +0 -3
  38. backup_2026-05-04/get_models.py +0 -25
  39. backup_2026-05-04/martech_sol_logo.jpg +0 -0
  40. backup_2026-05-04/requirements.txt +0 -14
  41. backup_2026-05-04/static/addon.html +0 -543
  42. backup_2026-05-04/static/bot-icon.lottie +0 -3
  43. backup_2026-05-04/static/chat-loader.js +0 -48
  44. backup_2026-05-04/test_groq.py +0 -26
  45. backup_2026-05-04/wordpress code/WP_INTEGRATION_SCRIPT.txt +0 -12
  46. backup_2026-05-04/wordpress code/addon.html +0 -477
  47. backup_2026-05-04/wordpress code/addon_backup.html +0 -175
  48. backup_2026-05-13_pre_intelligent_retrieval/.env.example +0 -19
  49. backup_2026-05-13_pre_intelligent_retrieval/.gitattributes +0 -2
  50. backup_2026-05-13_pre_intelligent_retrieval/.gitignore +0 -9
.gitignore CHANGED
@@ -7,3 +7,7 @@ venv/
7
  # Ignoring data directory to prevent pushing binary index files
8
  data/index/
9
  data/sessions/
 
 
 
 
 
7
  # Ignoring data directory to prevent pushing binary index files
8
  data/index/
9
  data/sessions/
10
+
11
+ # Ignore local backup directories
12
+ backup*/
13
+ *backup*/
backup_2026-05-04/.env.example DELETED
@@ -1,15 +0,0 @@
1
- LLM_PROVIDER=groq
2
- GROQ_API_KEY=your_groq_api_key
3
- GROQ_MODEL=llama-3.1-8b-instant
4
- HF_API_KEY=your_huggingface_api_key
5
- HF_MODEL=meta-llama/Llama-3.1-8B-Instruct
6
- EMBEDDING_MODEL=BAAI/bge-small-en-v1.5
7
- DOCS_DIR=docs
8
- INDEX_DIR=data/index
9
- SESSIONS_DIR=data/sessions
10
- TOP_K=4
11
- CORS_ALLOW_ORIGINS=*
12
- API_KEY=
13
- RATE_LIMIT_REQUESTS=60
14
- RATE_LIMIT_WINDOW_SECONDS=60
15
- RAG_API_URL=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- *.lottie filter=lfs diff=lfs merge=lfs -text
2
- *.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
backup_2026-05-04/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- .env
2
- __pycache__/
3
- .pytest_cache/
4
- .mypy_cache/
5
- .venv/
6
- venv/
7
- # Ignoring data directory to prevent pushing binary index files
8
- data/index/
9
- data/sessions/
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/CONFIG_NOTES.md DELETED
@@ -1,38 +0,0 @@
1
- # Configuration Backup - Martechsol RAG Chatbot
2
- **Date:** 2026-04-28
3
- **Source:** C:\Users\DELL\Desktop\RAG_backup2
4
- **Destination:** D:\RAG_Backup_2026_04_28
5
-
6
- ## Core Settings (from app/core/config.py)
7
- - **App Name:** Fast RAG Chatbot
8
- - **LLM Provider:** groq (default)
9
- - **Groq Model:** llama-3.1-8b-instant
10
- - **HF Model:** meta-llama/Llama-3.1-8B-Instruct
11
- - **Embedding Model:** BAAI/bge-small-en-v1.5
12
- - **Docs Directory:** docs/
13
- - **Index Directory:** data/index/
14
- - **Sessions Directory:** data/sessions/
15
- - **Chunk Size:** 420 tokens
16
- - **Overlap:** 80 tokens
17
- - **Top K:** 4
18
-
19
- ## Admin Credentials
20
- - **Username:** martech_admin
21
- - **Password:** martech_admin_303
22
- - **OTP Expiry:** 300 seconds (5 minutes)
23
-
24
- ## Security
25
- - **Auth Method:** HTTP Basic Auth + OTP (email-based)
26
- - **Email for OTP:** randomjoedown@gmail.com
27
- - **SMTP Server:** smtp.gmail.com (Port 465)
28
-
29
- ## API / Integration
30
- - **Endpoint:** /api/chat
31
- - **Widget Endpoint:** /widget
32
- - **Admin Endpoint:** /admin
33
- - **CORS:** Allowed for all (*) for testing
34
-
35
- ## Infrastructure
36
- - **Hugging Face Path:** /data (for persistent storage)
37
- - **Local Path:** data/ (for local storage)
38
- - **Dockerfile:** Based on python:3.10-slim, serves on port 7860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/CUSTOMIZATION_GUIDE.md DELETED
@@ -1,55 +0,0 @@
1
- # Martechsol Assistant Customization Guide
2
-
3
- This guide explains how to customize the visual appearance and behavior of your Gradio-based chat interface.
4
-
5
- ## 1. Modifying the UI Layout (app/ui_gradio.py)
6
-
7
- The interface is built using `gradio.Blocks`. You can modify the structure in the `with gr.Blocks(...) as demo:` section.
8
-
9
- ### Common Components:
10
- - **`chatbot`**: The main chat window. We use `elem_id="chatbot-window"` for CSS targeting.
11
- - **`msg`**: The text input box.
12
- - **`send`**: The primary action button.
13
-
14
- ## 2. Custom CSS (app/ui_gradio.py)
15
-
16
- At the top of `app/ui_gradio.py`, there is a `custom_css` string. This is the most powerful way to change the look of your app.
17
-
18
- ### How to target elements:
19
- We use `elem_id` in Python to give elements stable names.
20
-
21
- Example:
22
- ```python
23
- chatbot = gr.Chatbot(elem_id="my-custom-chat")
24
- ```
25
- Then in CSS:
26
- ```css
27
- #my-custom-chat {
28
- background-color: #f0f0f0;
29
- }
30
- ```
31
-
32
- ### Important Selectors:
33
- - **`footer`**: Targets the Gradio branding at the bottom.
34
- - **`.gradio-container`**: The main outer wrapper of your app.
35
- - **`[data-testid="user"]`**: Targets user messages.
36
- - **`[data-testid="bot"]`**: Targets assistant messages.
37
-
38
- ## 3. Persistent Data on Hugging Face
39
-
40
- Your data is stored in the `/data` volume.
41
- - **Sessions**: `/data/sessions/`
42
- - **FAISS Index**: `/data/index/`
43
-
44
- To manage these, you can use the **Admin Panel** at `/admin`.
45
-
46
- ## 4. Tips for Widget Embedding
47
-
48
- If you are embedding this as an iframe (like in `addon.html`):
49
- 1. **Remove redundant headers**: We hide the Markdown titles in the CSS (`#title-area { display: none; }`) so the iframe looks like a part of your page.
50
- 2. **Handle height**: We set `height: auto !important` and `min-height: 40px !important` on the chatbot so it doesn't take up 500px by default.
51
- 3. **Transparent Background**: You can add `body { background-color: transparent !important; }` to the `custom_css` if your theme supports it.
52
-
53
- ## 5. Deployment
54
-
55
- Whenever you save a file, the changes are automatically pushed to your Hugging Face Space. The Space will rebuild automatically (takes about 1-2 minutes).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/Dockerfile DELETED
@@ -1,31 +0,0 @@
1
- FROM python:3.11-slim
2
-
3
- # Set environment variables
4
- ENV PYTHONDONTWRITEBYTECODE=1
5
- ENV PYTHONUNBUFFERED=1
6
- ENV HOME=/home/user
7
-
8
- # Create a non-root user for Hugging Face
9
- RUN useradd -m -u 1000 user
10
- WORKDIR $HOME/app
11
-
12
- # Pre-create data directories with correct ownership
13
- RUN mkdir -p $HOME/app/data/index $HOME/app/data/sessions && \
14
- chown -R user:user $HOME/app
15
-
16
- # Switch to non-root user
17
- USER user
18
- ENV PATH=$HOME/.local/bin:$PATH
19
-
20
- # Install dependencies
21
- COPY --chown=user requirements.txt .
22
- RUN pip install --no-cache-dir --user -r requirements.txt
23
-
24
- # Copy application code
25
- COPY --chown=user . .
26
-
27
- # Expose port
28
- EXPOSE 7860
29
-
30
- # Start application
31
- CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/PHPMailer/Exception.php DELETED
@@ -1,40 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * PHPMailer Exception class.
5
- * PHP Version 5.5.
6
- *
7
- * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
8
- *
9
- * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
10
- * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
11
- * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
12
- * @author Brent R. Matzelle (original founder)
13
- * @copyright 2012 - 2020 Marcus Bointon
14
- * @copyright 2010 - 2012 Jim Jagielski
15
- * @copyright 2004 - 2009 Andy Prevost
16
- * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
17
- * @note This program is distributed in the hope that it will be useful - WITHOUT
18
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
- * FITNESS FOR A PARTICULAR PURPOSE.
20
- */
21
-
22
- namespace PHPMailer\PHPMailer;
23
-
24
- /**
25
- * PHPMailer exception handler.
26
- *
27
- * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
28
- */
29
- class Exception extends \Exception
30
- {
31
- /**
32
- * Prettify error message output.
33
- *
34
- * @return string
35
- */
36
- public function errorMessage()
37
- {
38
- return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
39
- }
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/PHPMailer/PHPMailer.php DELETED
The diff for this file is too large to render. See raw diff
 
backup_2026-05-04/PHPMailer/SMTP.php DELETED
@@ -1,1509 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * PHPMailer RFC821 SMTP email transport class.
5
- * PHP Version 5.5.
6
- *
7
- * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
8
- *
9
- * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
10
- * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
11
- * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
12
- * @author Brent R. Matzelle (original founder)
13
- * @copyright 2012 - 2020 Marcus Bointon
14
- * @copyright 2010 - 2012 Jim Jagielski
15
- * @copyright 2004 - 2009 Andy Prevost
16
- * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
17
- * @note This program is distributed in the hope that it will be useful - WITHOUT
18
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
- * FITNESS FOR A PARTICULAR PURPOSE.
20
- */
21
-
22
- namespace PHPMailer\PHPMailer;
23
-
24
- /**
25
- * PHPMailer RFC821 SMTP email transport class.
26
- * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
27
- *
28
- * @author Chris Ryan
29
- * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
30
- */
31
- class SMTP
32
- {
33
- /**
34
- * The PHPMailer SMTP version number.
35
- *
36
- * @var string
37
- */
38
- const VERSION = '6.9.3';
39
-
40
- /**
41
- * SMTP line break constant.
42
- *
43
- * @var string
44
- */
45
- const LE = "\r\n";
46
-
47
- /**
48
- * The SMTP port to use if one is not specified.
49
- *
50
- * @var int
51
- */
52
- const DEFAULT_PORT = 25;
53
-
54
- /**
55
- * The SMTPs port to use if one is not specified.
56
- *
57
- * @var int
58
- */
59
- const DEFAULT_SECURE_PORT = 465;
60
-
61
- /**
62
- * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
63
- * *excluding* a trailing CRLF break.
64
- *
65
- * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.6
66
- *
67
- * @var int
68
- */
69
- const MAX_LINE_LENGTH = 998;
70
-
71
- /**
72
- * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
73
- * *including* a trailing CRLF line break.
74
- *
75
- * @see https://www.rfc-editor.org/rfc/rfc5321#section-4.5.3.1.5
76
- *
77
- * @var int
78
- */
79
- const MAX_REPLY_LENGTH = 512;
80
-
81
- /**
82
- * Debug level for no output.
83
- *
84
- * @var int
85
- */
86
- const DEBUG_OFF = 0;
87
-
88
- /**
89
- * Debug level to show client -> server messages.
90
- *
91
- * @var int
92
- */
93
- const DEBUG_CLIENT = 1;
94
-
95
- /**
96
- * Debug level to show client -> server and server -> client messages.
97
- *
98
- * @var int
99
- */
100
- const DEBUG_SERVER = 2;
101
-
102
- /**
103
- * Debug level to show connection status, client -> server and server -> client messages.
104
- *
105
- * @var int
106
- */
107
- const DEBUG_CONNECTION = 3;
108
-
109
- /**
110
- * Debug level to show all messages.
111
- *
112
- * @var int
113
- */
114
- const DEBUG_LOWLEVEL = 4;
115
-
116
- /**
117
- * Debug output level.
118
- * Options:
119
- * * self::DEBUG_OFF (`0`) No debug output, default
120
- * * self::DEBUG_CLIENT (`1`) Client commands
121
- * * self::DEBUG_SERVER (`2`) Client commands and server responses
122
- * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
123
- * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
124
- *
125
- * @var int
126
- */
127
- public $do_debug = self::DEBUG_OFF;
128
-
129
- /**
130
- * How to handle debug output.
131
- * Options:
132
- * * `echo` Output plain-text as-is, appropriate for CLI
133
- * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
134
- * * `error_log` Output to error log as configured in php.ini
135
- * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
136
- *
137
- * ```php
138
- * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
139
- * ```
140
- *
141
- * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
142
- * level output is used:
143
- *
144
- * ```php
145
- * $mail->Debugoutput = new myPsr3Logger;
146
- * ```
147
- *
148
- * @var string|callable|\Psr\Log\LoggerInterface
149
- */
150
- public $Debugoutput = 'echo';
151
-
152
- /**
153
- * Whether to use VERP.
154
- *
155
- * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
156
- * @see https://www.postfix.org/VERP_README.html Info on VERP
157
- *
158
- * @var bool
159
- */
160
- public $do_verp = false;
161
-
162
- /**
163
- * The timeout value for connection, in seconds.
164
- * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
165
- * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
166
- *
167
- * @see https://www.rfc-editor.org/rfc/rfc2821#section-4.5.3.2
168
- *
169
- * @var int
170
- */
171
- public $Timeout = 300;
172
-
173
- /**
174
- * How long to wait for commands to complete, in seconds.
175
- * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
176
- *
177
- * @var int
178
- */
179
- public $Timelimit = 300;
180
-
181
- /**
182
- * Patterns to extract an SMTP transaction id from reply to a DATA command.
183
- * The first capture group in each regex will be used as the ID.
184
- * MS ESMTP returns the message ID, which may not be correct for internal tracking.
185
- *
186
- * @var string[]
187
- */
188
- protected $smtp_transaction_id_patterns = [
189
- 'exim' => '/[\d]{3} OK id=(.*)/',
190
- 'sendmail' => '/[\d]{3} 2\.0\.0 (.*) Message/',
191
- 'postfix' => '/[\d]{3} 2\.0\.0 Ok: queued as (.*)/',
192
- 'Microsoft_ESMTP' => '/[0-9]{3} 2\.[\d]\.0 (.*)@(?:.*) Queued mail for delivery/',
193
- 'Amazon_SES' => '/[\d]{3} Ok (.*)/',
194
- 'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
195
- 'CampaignMonitor' => '/[\d]{3} 2\.0\.0 OK:([a-zA-Z\d]{48})/',
196
- 'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
197
- 'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
198
- 'Mailjet' => '/[\d]{3} OK queued as (.*)/',
199
- ];
200
-
201
- /**
202
- * Allowed SMTP XCLIENT attributes.
203
- * Must be allowed by the SMTP server. EHLO response is not checked.
204
- *
205
- * @see https://www.postfix.org/XCLIENT_README.html
206
- *
207
- * @var array
208
- */
209
- public static $xclient_allowed_attributes = [
210
- 'NAME', 'ADDR', 'PORT', 'PROTO', 'HELO', 'LOGIN', 'DESTADDR', 'DESTPORT'
211
- ];
212
-
213
- /**
214
- * The last transaction ID issued in response to a DATA command,
215
- * if one was detected.
216
- *
217
- * @var string|bool|null
218
- */
219
- protected $last_smtp_transaction_id;
220
-
221
- /**
222
- * The socket for the server connection.
223
- *
224
- * @var ?resource
225
- */
226
- protected $smtp_conn;
227
-
228
- /**
229
- * Error information, if any, for the last SMTP command.
230
- *
231
- * @var array
232
- */
233
- protected $error = [
234
- 'error' => '',
235
- 'detail' => '',
236
- 'smtp_code' => '',
237
- 'smtp_code_ex' => '',
238
- ];
239
-
240
- /**
241
- * The reply the server sent to us for HELO.
242
- * If null, no HELO string has yet been received.
243
- *
244
- * @var string|null
245
- */
246
- protected $helo_rply;
247
-
248
- /**
249
- * The set of SMTP extensions sent in reply to EHLO command.
250
- * Indexes of the array are extension names.
251
- * Value at index 'HELO' or 'EHLO' (according to command that was sent)
252
- * represents the server name. In case of HELO it is the only element of the array.
253
- * Other values can be boolean TRUE or an array containing extension options.
254
- * If null, no HELO/EHLO string has yet been received.
255
- *
256
- * @var array|null
257
- */
258
- protected $server_caps;
259
-
260
- /**
261
- * The most recent reply received from the server.
262
- *
263
- * @var string
264
- */
265
- protected $last_reply = '';
266
-
267
- /**
268
- * Output debugging info via a user-selected method.
269
- *
270
- * @param string $str Debug string to output
271
- * @param int $level The debug level of this message; see DEBUG_* constants
272
- *
273
- * @see SMTP::$Debugoutput
274
- * @see SMTP::$do_debug
275
- */
276
- protected function edebug($str, $level = 0)
277
- {
278
- if ($level > $this->do_debug) {
279
- return;
280
- }
281
- //Is this a PSR-3 logger?
282
- if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
283
- //Remove trailing line breaks potentially added by calls to SMTP::client_send()
284
- $this->Debugoutput->debug(rtrim($str, "\r\n"));
285
-
286
- return;
287
- }
288
- //Avoid clash with built-in function names
289
- if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
290
- call_user_func($this->Debugoutput, $str, $level);
291
-
292
- return;
293
- }
294
- switch ($this->Debugoutput) {
295
- case 'error_log':
296
- //Don't output, just log
297
- /** @noinspection ForgottenDebugOutputInspection */
298
- error_log($str);
299
- break;
300
- case 'html':
301
- //Cleans up output a bit for a better looking, HTML-safe output
302
- echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
303
- preg_replace('/[\r\n]+/', '', $str),
304
- ENT_QUOTES,
305
- 'UTF-8'
306
- ), "<br>\n";
307
- break;
308
- case 'echo':
309
- default:
310
- //Normalize line breaks
311
- $str = preg_replace('/\r\n|\r/m', "\n", $str);
312
- echo gmdate('Y-m-d H:i:s'),
313
- "\t",
314
- //Trim trailing space
315
- trim(
316
- //Indent for readability, except for trailing break
317
- str_replace(
318
- "\n",
319
- "\n \t ",
320
- trim($str)
321
- )
322
- ),
323
- "\n";
324
- }
325
- }
326
-
327
- /**
328
- * Connect to an SMTP server.
329
- *
330
- * @param string $host SMTP server IP or host name
331
- * @param int $port The port number to connect to
332
- * @param int $timeout How long to wait for the connection to open
333
- * @param array $options An array of options for stream_context_create()
334
- *
335
- * @return bool
336
- */
337
- public function connect($host, $port = null, $timeout = 30, $options = [])
338
- {
339
- //Clear errors to avoid confusion
340
- $this->setError('');
341
- //Make sure we are __not__ connected
342
- if ($this->connected()) {
343
- //Already connected, generate error
344
- $this->setError('Already connected to a server');
345
-
346
- return false;
347
- }
348
- if (empty($port)) {
349
- $port = self::DEFAULT_PORT;
350
- }
351
- //Connect to the SMTP server
352
- $this->edebug(
353
- "Connection: opening to $host:$port, timeout=$timeout, options=" .
354
- (count($options) > 0 ? var_export($options, true) : 'array()'),
355
- self::DEBUG_CONNECTION
356
- );
357
-
358
- $this->smtp_conn = $this->getSMTPConnection($host, $port, $timeout, $options);
359
-
360
- if ($this->smtp_conn === false) {
361
- //Error info already set inside `getSMTPConnection()`
362
- return false;
363
- }
364
-
365
- $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
366
-
367
- //Get any announcement
368
- $this->last_reply = $this->get_lines();
369
- $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
370
- $responseCode = (int)substr($this->last_reply, 0, 3);
371
- if ($responseCode === 220) {
372
- return true;
373
- }
374
- //Anything other than a 220 response means something went wrong
375
- //RFC 5321 says the server will wait for us to send a QUIT in response to a 554 error
376
- //https://www.rfc-editor.org/rfc/rfc5321#section-3.1
377
- if ($responseCode === 554) {
378
- $this->quit();
379
- }
380
- //This will handle 421 responses which may not wait for a QUIT (e.g. if the server is being shut down)
381
- $this->edebug('Connection: closing due to error', self::DEBUG_CONNECTION);
382
- $this->close();
383
- return false;
384
- }
385
-
386
- /**
387
- * Create connection to the SMTP server.
388
- *
389
- * @param string $host SMTP server IP or host name
390
- * @param int $port The port number to connect to
391
- * @param int $timeout How long to wait for the connection to open
392
- * @param array $options An array of options for stream_context_create()
393
- *
394
- * @return false|resource
395
- */
396
- protected function getSMTPConnection($host, $port = null, $timeout = 30, $options = [])
397
- {
398
- static $streamok;
399
- //This is enabled by default since 5.0.0 but some providers disable it
400
- //Check this once and cache the result
401
- if (null === $streamok) {
402
- $streamok = function_exists('stream_socket_client');
403
- }
404
-
405
- $errno = 0;
406
- $errstr = '';
407
- if ($streamok) {
408
- $socket_context = stream_context_create($options);
409
- set_error_handler(function () {
410
- call_user_func_array([$this, 'errorHandler'], func_get_args());
411
- });
412
- $connection = stream_socket_client(
413
- $host . ':' . $port,
414
- $errno,
415
- $errstr,
416
- $timeout,
417
- STREAM_CLIENT_CONNECT,
418
- $socket_context
419
- );
420
- } else {
421
- //Fall back to fsockopen which should work in more places, but is missing some features
422
- $this->edebug(
423
- 'Connection: stream_socket_client not available, falling back to fsockopen',
424
- self::DEBUG_CONNECTION
425
- );
426
- set_error_handler(function () {
427
- call_user_func_array([$this, 'errorHandler'], func_get_args());
428
- });
429
- $connection = fsockopen(
430
- $host,
431
- $port,
432
- $errno,
433
- $errstr,
434
- $timeout
435
- );
436
- }
437
- restore_error_handler();
438
-
439
- //Verify we connected properly
440
- if (!is_resource($connection)) {
441
- $this->setError(
442
- 'Failed to connect to server',
443
- '',
444
- (string) $errno,
445
- $errstr
446
- );
447
- $this->edebug(
448
- 'SMTP ERROR: ' . $this->error['error']
449
- . ": $errstr ($errno)",
450
- self::DEBUG_CLIENT
451
- );
452
-
453
- return false;
454
- }
455
-
456
- //SMTP server can take longer to respond, give longer timeout for first read
457
- //Windows does not have support for this timeout function
458
- if (strpos(PHP_OS, 'WIN') !== 0) {
459
- $max = (int)ini_get('max_execution_time');
460
- //Don't bother if unlimited, or if set_time_limit is disabled
461
- if (0 !== $max && $timeout > $max && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
462
- @set_time_limit($timeout);
463
- }
464
- stream_set_timeout($connection, $timeout, 0);
465
- }
466
-
467
- return $connection;
468
- }
469
-
470
- /**
471
- * Initiate a TLS (encrypted) session.
472
- *
473
- * @return bool
474
- */
475
- public function startTLS()
476
- {
477
- if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
478
- return false;
479
- }
480
-
481
- //Allow the best TLS version(s) we can
482
- $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
483
-
484
- //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
485
- //so add them back in manually if we can
486
- if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
487
- $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
488
- $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
489
- }
490
-
491
- //Begin encrypted connection
492
- set_error_handler(function () {
493
- call_user_func_array([$this, 'errorHandler'], func_get_args());
494
- });
495
- $crypto_ok = stream_socket_enable_crypto(
496
- $this->smtp_conn,
497
- true,
498
- $crypto_method
499
- );
500
- restore_error_handler();
501
-
502
- return (bool) $crypto_ok;
503
- }
504
-
505
- /**
506
- * Perform SMTP authentication.
507
- * Must be run after hello().
508
- *
509
- * @see hello()
510
- *
511
- * @param string $username The user name
512
- * @param string $password The password
513
- * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
514
- * @param OAuthTokenProvider $OAuth An optional OAuthTokenProvider instance for XOAUTH2 authentication
515
- *
516
- * @return bool True if successfully authenticated
517
- */
518
- public function authenticate(
519
- $username,
520
- $password,
521
- $authtype = null,
522
- $OAuth = null
523
- ) {
524
- if (!$this->server_caps) {
525
- $this->setError('Authentication is not allowed before HELO/EHLO');
526
-
527
- return false;
528
- }
529
-
530
- if (array_key_exists('EHLO', $this->server_caps)) {
531
- //SMTP extensions are available; try to find a proper authentication method
532
- if (!array_key_exists('AUTH', $this->server_caps)) {
533
- $this->setError('Authentication is not allowed at this stage');
534
- //'at this stage' means that auth may be allowed after the stage changes
535
- //e.g. after STARTTLS
536
-
537
- return false;
538
- }
539
-
540
- $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
541
- $this->edebug(
542
- 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
543
- self::DEBUG_LOWLEVEL
544
- );
545
-
546
- //If we have requested a specific auth type, check the server supports it before trying others
547
- if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
548
- $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
549
- $authtype = null;
550
- }
551
-
552
- if (empty($authtype)) {
553
- //If no auth mechanism is specified, attempt to use these, in this order
554
- //Try CRAM-MD5 first as it's more secure than the others
555
- foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
556
- if (in_array($method, $this->server_caps['AUTH'], true)) {
557
- $authtype = $method;
558
- break;
559
- }
560
- }
561
- if (empty($authtype)) {
562
- $this->setError('No supported authentication methods found');
563
-
564
- return false;
565
- }
566
- $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
567
- }
568
-
569
- if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
570
- $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
571
-
572
- return false;
573
- }
574
- } elseif (empty($authtype)) {
575
- $authtype = 'LOGIN';
576
- }
577
- switch ($authtype) {
578
- case 'PLAIN':
579
- //Start authentication
580
- if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
581
- return false;
582
- }
583
- //Send encoded username and password
584
- if (
585
- //Format from https://www.rfc-editor.org/rfc/rfc4616#section-2
586
- //We skip the first field (it's forgery), so the string starts with a null byte
587
- !$this->sendCommand(
588
- 'User & Password',
589
- base64_encode("\0" . $username . "\0" . $password),
590
- 235
591
- )
592
- ) {
593
- return false;
594
- }
595
- break;
596
- case 'LOGIN':
597
- //Start authentication
598
- if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
599
- return false;
600
- }
601
- if (!$this->sendCommand('Username', base64_encode($username), 334)) {
602
- return false;
603
- }
604
- if (!$this->sendCommand('Password', base64_encode($password), 235)) {
605
- return false;
606
- }
607
- break;
608
- case 'CRAM-MD5':
609
- //Start authentication
610
- if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
611
- return false;
612
- }
613
- //Get the challenge
614
- $challenge = base64_decode(substr($this->last_reply, 4));
615
-
616
- //Build the response
617
- $response = $username . ' ' . $this->hmac($challenge, $password);
618
-
619
- //send encoded credentials
620
- return $this->sendCommand('Username', base64_encode($response), 235);
621
- case 'XOAUTH2':
622
- //The OAuth instance must be set up prior to requesting auth.
623
- if (null === $OAuth) {
624
- return false;
625
- }
626
- $oauth = $OAuth->getOauth64();
627
-
628
- //Start authentication
629
- if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
630
- return false;
631
- }
632
- break;
633
- default:
634
- $this->setError("Authentication method \"$authtype\" is not supported");
635
-
636
- return false;
637
- }
638
-
639
- return true;
640
- }
641
-
642
- /**
643
- * Calculate an MD5 HMAC hash.
644
- * Works like hash_hmac('md5', $data, $key)
645
- * in case that function is not available.
646
- *
647
- * @param string $data The data to hash
648
- * @param string $key The key to hash with
649
- *
650
- * @return string
651
- */
652
- protected function hmac($data, $key)
653
- {
654
- if (function_exists('hash_hmac')) {
655
- return hash_hmac('md5', $data, $key);
656
- }
657
-
658
- //The following borrowed from
659
- //https://www.php.net/manual/en/function.mhash.php#27225
660
-
661
- //RFC 2104 HMAC implementation for php.
662
- //Creates an md5 HMAC.
663
- //Eliminates the need to install mhash to compute a HMAC
664
- //by Lance Rushing
665
-
666
- $bytelen = 64; //byte length for md5
667
- if (strlen($key) > $bytelen) {
668
- $key = pack('H*', md5($key));
669
- }
670
- $key = str_pad($key, $bytelen, chr(0x00));
671
- $ipad = str_pad('', $bytelen, chr(0x36));
672
- $opad = str_pad('', $bytelen, chr(0x5c));
673
- $k_ipad = $key ^ $ipad;
674
- $k_opad = $key ^ $opad;
675
-
676
- return md5($k_opad . pack('H*', md5($k_ipad . $data)));
677
- }
678
-
679
- /**
680
- * Check connection state.
681
- *
682
- * @return bool True if connected
683
- */
684
- public function connected()
685
- {
686
- if (is_resource($this->smtp_conn)) {
687
- $sock_status = stream_get_meta_data($this->smtp_conn);
688
- if ($sock_status['eof']) {
689
- //The socket is valid but we are not connected
690
- $this->edebug(
691
- 'SMTP NOTICE: EOF caught while checking if connected',
692
- self::DEBUG_CLIENT
693
- );
694
- $this->close();
695
-
696
- return false;
697
- }
698
-
699
- return true; //everything looks good
700
- }
701
-
702
- return false;
703
- }
704
-
705
- /**
706
- * Close the socket and clean up the state of the class.
707
- * Don't use this function without first trying to use QUIT.
708
- *
709
- * @see quit()
710
- */
711
- public function close()
712
- {
713
- $this->server_caps = null;
714
- $this->helo_rply = null;
715
- if (is_resource($this->smtp_conn)) {
716
- //Close the connection and cleanup
717
- fclose($this->smtp_conn);
718
- $this->smtp_conn = null; //Makes for cleaner serialization
719
- $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
720
- }
721
- }
722
-
723
- /**
724
- * Send an SMTP DATA command.
725
- * Issues a data command and sends the msg_data to the server,
726
- * finalizing the mail transaction. $msg_data is the message
727
- * that is to be sent with the headers. Each header needs to be
728
- * on a single line followed by a <CRLF> with the message headers
729
- * and the message body being separated by an additional <CRLF>.
730
- * Implements RFC 821: DATA <CRLF>.
731
- *
732
- * @param string $msg_data Message data to send
733
- *
734
- * @return bool
735
- */
736
- public function data($msg_data)
737
- {
738
- //This will use the standard timelimit
739
- if (!$this->sendCommand('DATA', 'DATA', 354)) {
740
- return false;
741
- }
742
-
743
- /* The server is ready to accept data!
744
- * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
745
- * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
746
- * smaller lines to fit within the limit.
747
- * We will also look for lines that start with a '.' and prepend an additional '.'.
748
- * NOTE: this does not count towards line-length limit.
749
- */
750
-
751
- //Normalize line breaks before exploding
752
- $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
753
-
754
- /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
755
- * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will
756
- * process all lines before a blank line as headers.
757
- */
758
-
759
- $field = substr($lines[0], 0, strpos($lines[0], ':'));
760
- $in_headers = false;
761
- if (!empty($field) && strpos($field, ' ') === false) {
762
- $in_headers = true;
763
- }
764
-
765
- foreach ($lines as $line) {
766
- $lines_out = [];
767
- if ($in_headers && $line === '') {
768
- $in_headers = false;
769
- }
770
- //Break this line up into several smaller lines if it's too long
771
- //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
772
- while (isset($line[self::MAX_LINE_LENGTH])) {
773
- //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
774
- //so as to avoid breaking in the middle of a word
775
- $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
776
- //Deliberately matches both false and 0
777
- if (!$pos) {
778
- //No nice break found, add a hard break
779
- $pos = self::MAX_LINE_LENGTH - 1;
780
- $lines_out[] = substr($line, 0, $pos);
781
- $line = substr($line, $pos);
782
- } else {
783
- //Break at the found point
784
- $lines_out[] = substr($line, 0, $pos);
785
- //Move along by the amount we dealt with
786
- $line = substr($line, $pos + 1);
787
- }
788
- //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
789
- if ($in_headers) {
790
- $line = "\t" . $line;
791
- }
792
- }
793
- $lines_out[] = $line;
794
-
795
- //Send the lines to the server
796
- foreach ($lines_out as $line_out) {
797
- //Dot-stuffing as per RFC5321 section 4.5.2
798
- //https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
799
- if (!empty($line_out) && $line_out[0] === '.') {
800
- $line_out = '.' . $line_out;
801
- }
802
- $this->client_send($line_out . static::LE, 'DATA');
803
- }
804
- }
805
-
806
- //Message data has been sent, complete the command
807
- //Increase timelimit for end of DATA command
808
- $savetimelimit = $this->Timelimit;
809
- $this->Timelimit *= 2;
810
- $result = $this->sendCommand('DATA END', '.', 250);
811
- $this->recordLastTransactionID();
812
- //Restore timelimit
813
- $this->Timelimit = $savetimelimit;
814
-
815
- return $result;
816
- }
817
-
818
- /**
819
- * Send an SMTP HELO or EHLO command.
820
- * Used to identify the sending server to the receiving server.
821
- * This makes sure that client and server are in a known state.
822
- * Implements RFC 821: HELO <SP> <domain> <CRLF>
823
- * and RFC 2821 EHLO.
824
- *
825
- * @param string $host The host name or IP to connect to
826
- *
827
- * @return bool
828
- */
829
- public function hello($host = '')
830
- {
831
- //Try extended hello first (RFC 2821)
832
- if ($this->sendHello('EHLO', $host)) {
833
- return true;
834
- }
835
-
836
- //Some servers shut down the SMTP service here (RFC 5321)
837
- if (substr($this->helo_rply, 0, 3) == '421') {
838
- return false;
839
- }
840
-
841
- return $this->sendHello('HELO', $host);
842
- }
843
-
844
- /**
845
- * Send an SMTP HELO or EHLO command.
846
- * Low-level implementation used by hello().
847
- *
848
- * @param string $hello The HELO string
849
- * @param string $host The hostname to say we are
850
- *
851
- * @return bool
852
- *
853
- * @see hello()
854
- */
855
- protected function sendHello($hello, $host)
856
- {
857
- $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
858
- $this->helo_rply = $this->last_reply;
859
- if ($noerror) {
860
- $this->parseHelloFields($hello);
861
- } else {
862
- $this->server_caps = null;
863
- }
864
-
865
- return $noerror;
866
- }
867
-
868
- /**
869
- * Parse a reply to HELO/EHLO command to discover server extensions.
870
- * In case of HELO, the only parameter that can be discovered is a server name.
871
- *
872
- * @param string $type `HELO` or `EHLO`
873
- */
874
- protected function parseHelloFields($type)
875
- {
876
- $this->server_caps = [];
877
- $lines = explode("\n", $this->helo_rply);
878
-
879
- foreach ($lines as $n => $s) {
880
- //First 4 chars contain response code followed by - or space
881
- $s = trim(substr($s, 4));
882
- if (empty($s)) {
883
- continue;
884
- }
885
- $fields = explode(' ', $s);
886
- if (!empty($fields)) {
887
- if (!$n) {
888
- $name = $type;
889
- $fields = $fields[0];
890
- } else {
891
- $name = array_shift($fields);
892
- switch ($name) {
893
- case 'SIZE':
894
- $fields = ($fields ? $fields[0] : 0);
895
- break;
896
- case 'AUTH':
897
- if (!is_array($fields)) {
898
- $fields = [];
899
- }
900
- break;
901
- default:
902
- $fields = true;
903
- }
904
- }
905
- $this->server_caps[$name] = $fields;
906
- }
907
- }
908
- }
909
-
910
- /**
911
- * Send an SMTP MAIL command.
912
- * Starts a mail transaction from the email address specified in
913
- * $from. Returns true if successful or false otherwise. If True
914
- * the mail transaction is started and then one or more recipient
915
- * commands may be called followed by a data command.
916
- * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
917
- *
918
- * @param string $from Source address of this message
919
- *
920
- * @return bool
921
- */
922
- public function mail($from)
923
- {
924
- $useVerp = ($this->do_verp ? ' XVERP' : '');
925
-
926
- return $this->sendCommand(
927
- 'MAIL FROM',
928
- 'MAIL FROM:<' . $from . '>' . $useVerp,
929
- 250
930
- );
931
- }
932
-
933
- /**
934
- * Send an SMTP QUIT command.
935
- * Closes the socket if there is no error or the $close_on_error argument is true.
936
- * Implements from RFC 821: QUIT <CRLF>.
937
- *
938
- * @param bool $close_on_error Should the connection close if an error occurs?
939
- *
940
- * @return bool
941
- */
942
- public function quit($close_on_error = true)
943
- {
944
- $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
945
- $err = $this->error; //Save any error
946
- if ($noerror || $close_on_error) {
947
- $this->close();
948
- $this->error = $err; //Restore any error from the quit command
949
- }
950
-
951
- return $noerror;
952
- }
953
-
954
- /**
955
- * Send an SMTP RCPT command.
956
- * Sets the TO argument to $toaddr.
957
- * Returns true if the recipient was accepted false if it was rejected.
958
- * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
959
- *
960
- * @param string $address The address the message is being sent to
961
- * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
962
- * or DELAY. If you specify NEVER all other notifications are ignored.
963
- *
964
- * @return bool
965
- */
966
- public function recipient($address, $dsn = '')
967
- {
968
- if (empty($dsn)) {
969
- $rcpt = 'RCPT TO:<' . $address . '>';
970
- } else {
971
- $dsn = strtoupper($dsn);
972
- $notify = [];
973
-
974
- if (strpos($dsn, 'NEVER') !== false) {
975
- $notify[] = 'NEVER';
976
- } else {
977
- foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
978
- if (strpos($dsn, $value) !== false) {
979
- $notify[] = $value;
980
- }
981
- }
982
- }
983
-
984
- $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
985
- }
986
-
987
- return $this->sendCommand(
988
- 'RCPT TO',
989
- $rcpt,
990
- [250, 251]
991
- );
992
- }
993
-
994
- /**
995
- * Send SMTP XCLIENT command to server and check its return code.
996
- *
997
- * @return bool True on success
998
- */
999
- public function xclient(array $vars)
1000
- {
1001
- $xclient_options = "";
1002
- foreach ($vars as $key => $value) {
1003
- if (in_array($key, SMTP::$xclient_allowed_attributes)) {
1004
- $xclient_options .= " {$key}={$value}";
1005
- }
1006
- }
1007
- if (!$xclient_options) {
1008
- return true;
1009
- }
1010
- return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250);
1011
- }
1012
-
1013
- /**
1014
- * Send an SMTP RSET command.
1015
- * Abort any transaction that is currently in progress.
1016
- * Implements RFC 821: RSET <CRLF>.
1017
- *
1018
- * @return bool True on success
1019
- */
1020
- public function reset()
1021
- {
1022
- return $this->sendCommand('RSET', 'RSET', 250);
1023
- }
1024
-
1025
- /**
1026
- * Send a command to an SMTP server and check its return code.
1027
- *
1028
- * @param string $command The command name - not sent to the server
1029
- * @param string $commandstring The actual command to send
1030
- * @param int|array $expect One or more expected integer success codes
1031
- *
1032
- * @return bool True on success
1033
- */
1034
- protected function sendCommand($command, $commandstring, $expect)
1035
- {
1036
- if (!$this->connected()) {
1037
- $this->setError("Called $command without being connected");
1038
-
1039
- return false;
1040
- }
1041
- //Reject line breaks in all commands
1042
- if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
1043
- $this->setError("Command '$command' contained line breaks");
1044
-
1045
- return false;
1046
- }
1047
- $this->client_send($commandstring . static::LE, $command);
1048
-
1049
- $this->last_reply = $this->get_lines();
1050
- //Fetch SMTP code and possible error code explanation
1051
- $matches = [];
1052
- if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
1053
- $code = (int) $matches[1];
1054
- $code_ex = (count($matches) > 2 ? $matches[2] : null);
1055
- //Cut off error code from each response line
1056
- $detail = preg_replace(
1057
- "/{$code}[ -]" .
1058
- ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
1059
- '',
1060
- $this->last_reply
1061
- );
1062
- } else {
1063
- //Fall back to simple parsing if regex fails
1064
- $code = (int) substr($this->last_reply, 0, 3);
1065
- $code_ex = null;
1066
- $detail = substr($this->last_reply, 4);
1067
- }
1068
-
1069
- $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
1070
-
1071
- if (!in_array($code, (array) $expect, true)) {
1072
- $this->setError(
1073
- "$command command failed",
1074
- $detail,
1075
- $code,
1076
- $code_ex
1077
- );
1078
- $this->edebug(
1079
- 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
1080
- self::DEBUG_CLIENT
1081
- );
1082
-
1083
- return false;
1084
- }
1085
-
1086
- //Don't clear the error store when using keepalive
1087
- if ($command !== 'RSET') {
1088
- $this->setError('');
1089
- }
1090
-
1091
- return true;
1092
- }
1093
-
1094
- /**
1095
- * Send an SMTP SAML command.
1096
- * Starts a mail transaction from the email address specified in $from.
1097
- * Returns true if successful or false otherwise. If True
1098
- * the mail transaction is started and then one or more recipient
1099
- * commands may be called followed by a data command. This command
1100
- * will send the message to the users terminal if they are logged
1101
- * in and send them an email.
1102
- * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
1103
- *
1104
- * @param string $from The address the message is from
1105
- *
1106
- * @return bool
1107
- */
1108
- public function sendAndMail($from)
1109
- {
1110
- return $this->sendCommand('SAML', "SAML FROM:$from", 250);
1111
- }
1112
-
1113
- /**
1114
- * Send an SMTP VRFY command.
1115
- *
1116
- * @param string $name The name to verify
1117
- *
1118
- * @return bool
1119
- */
1120
- public function verify($name)
1121
- {
1122
- return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
1123
- }
1124
-
1125
- /**
1126
- * Send an SMTP NOOP command.
1127
- * Used to keep keep-alives alive, doesn't actually do anything.
1128
- *
1129
- * @return bool
1130
- */
1131
- public function noop()
1132
- {
1133
- return $this->sendCommand('NOOP', 'NOOP', 250);
1134
- }
1135
-
1136
- /**
1137
- * Send an SMTP TURN command.
1138
- * This is an optional command for SMTP that this class does not support.
1139
- * This method is here to make the RFC821 Definition complete for this class
1140
- * and _may_ be implemented in future.
1141
- * Implements from RFC 821: TURN <CRLF>.
1142
- *
1143
- * @return bool
1144
- */
1145
- public function turn()
1146
- {
1147
- $this->setError('The SMTP TURN command is not implemented');
1148
- $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
1149
-
1150
- return false;
1151
- }
1152
-
1153
- /**
1154
- * Send raw data to the server.
1155
- *
1156
- * @param string $data The data to send
1157
- * @param string $command Optionally, the command this is part of, used only for controlling debug output
1158
- *
1159
- * @return int|bool The number of bytes sent to the server or false on error
1160
- */
1161
- public function client_send($data, $command = '')
1162
- {
1163
- //If SMTP transcripts are left enabled, or debug output is posted online
1164
- //it can leak credentials, so hide credentials in all but lowest level
1165
- if (
1166
- self::DEBUG_LOWLEVEL > $this->do_debug &&
1167
- in_array($command, ['User & Password', 'Username', 'Password'], true)
1168
- ) {
1169
- $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
1170
- } else {
1171
- $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
1172
- }
1173
- set_error_handler(function () {
1174
- call_user_func_array([$this, 'errorHandler'], func_get_args());
1175
- });
1176
- $result = fwrite($this->smtp_conn, $data);
1177
- restore_error_handler();
1178
-
1179
- return $result;
1180
- }
1181
-
1182
- /**
1183
- * Get the latest error.
1184
- *
1185
- * @return array
1186
- */
1187
- public function getError()
1188
- {
1189
- return $this->error;
1190
- }
1191
-
1192
- /**
1193
- * Get SMTP extensions available on the server.
1194
- *
1195
- * @return array|null
1196
- */
1197
- public function getServerExtList()
1198
- {
1199
- return $this->server_caps;
1200
- }
1201
-
1202
- /**
1203
- * Get metadata about the SMTP server from its HELO/EHLO response.
1204
- * The method works in three ways, dependent on argument value and current state:
1205
- * 1. HELO/EHLO has not been sent - returns null and populates $this->error.
1206
- * 2. HELO has been sent -
1207
- * $name == 'HELO': returns server name
1208
- * $name == 'EHLO': returns boolean false
1209
- * $name == any other string: returns null and populates $this->error
1210
- * 3. EHLO has been sent -
1211
- * $name == 'HELO'|'EHLO': returns the server name
1212
- * $name == any other string: if extension $name exists, returns True
1213
- * or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
1214
- *
1215
- * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1216
- *
1217
- * @return string|bool|null
1218
- */
1219
- public function getServerExt($name)
1220
- {
1221
- if (!$this->server_caps) {
1222
- $this->setError('No HELO/EHLO was sent');
1223
-
1224
- return null;
1225
- }
1226
-
1227
- if (!array_key_exists($name, $this->server_caps)) {
1228
- if ('HELO' === $name) {
1229
- return $this->server_caps['EHLO'];
1230
- }
1231
- if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
1232
- return false;
1233
- }
1234
- $this->setError('HELO handshake was used; No information about server extensions available');
1235
-
1236
- return null;
1237
- }
1238
-
1239
- return $this->server_caps[$name];
1240
- }
1241
-
1242
- /**
1243
- * Get the last reply from the server.
1244
- *
1245
- * @return string
1246
- */
1247
- public function getLastReply()
1248
- {
1249
- return $this->last_reply;
1250
- }
1251
-
1252
- /**
1253
- * Read the SMTP server's response.
1254
- * Either before eof or socket timeout occurs on the operation.
1255
- * With SMTP we can tell if we have more lines to read if the
1256
- * 4th character is '-' symbol. If it is a space then we don't
1257
- * need to read anything else.
1258
- *
1259
- * @return string
1260
- */
1261
- protected function get_lines()
1262
- {
1263
- //If the connection is bad, give up straight away
1264
- if (!is_resource($this->smtp_conn)) {
1265
- return '';
1266
- }
1267
- $data = '';
1268
- $endtime = 0;
1269
- stream_set_timeout($this->smtp_conn, $this->Timeout);
1270
- if ($this->Timelimit > 0) {
1271
- $endtime = time() + $this->Timelimit;
1272
- }
1273
- $selR = [$this->smtp_conn];
1274
- $selW = null;
1275
- while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1276
- //Must pass vars in here as params are by reference
1277
- //solution for signals inspired by https://github.com/symfony/symfony/pull/6540
1278
- set_error_handler(function () {
1279
- call_user_func_array([$this, 'errorHandler'], func_get_args());
1280
- });
1281
- $n = stream_select($selR, $selW, $selW, $this->Timelimit);
1282
- restore_error_handler();
1283
-
1284
- if ($n === false) {
1285
- $message = $this->getError()['detail'];
1286
-
1287
- $this->edebug(
1288
- 'SMTP -> get_lines(): select failed (' . $message . ')',
1289
- self::DEBUG_LOWLEVEL
1290
- );
1291
-
1292
- //stream_select returns false when the `select` system call is interrupted
1293
- //by an incoming signal, try the select again
1294
- if (stripos($message, 'interrupted system call') !== false) {
1295
- $this->edebug(
1296
- 'SMTP -> get_lines(): retrying stream_select',
1297
- self::DEBUG_LOWLEVEL
1298
- );
1299
- $this->setError('');
1300
- continue;
1301
- }
1302
-
1303
- break;
1304
- }
1305
-
1306
- if (!$n) {
1307
- $this->edebug(
1308
- 'SMTP -> get_lines(): select timed-out in (' . $this->Timelimit . ' sec)',
1309
- self::DEBUG_LOWLEVEL
1310
- );
1311
- break;
1312
- }
1313
-
1314
- //Deliberate noise suppression - errors are handled afterwards
1315
- $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
1316
- $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
1317
- $data .= $str;
1318
- //If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
1319
- //or 4th character is a space or a line break char, we are done reading, break the loop.
1320
- //String array access is a significant micro-optimisation over strlen
1321
- if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
1322
- break;
1323
- }
1324
- //Timed-out? Log and break
1325
- $info = stream_get_meta_data($this->smtp_conn);
1326
- if ($info['timed_out']) {
1327
- $this->edebug(
1328
- 'SMTP -> get_lines(): stream timed-out (' . $this->Timeout . ' sec)',
1329
- self::DEBUG_LOWLEVEL
1330
- );
1331
- break;
1332
- }
1333
- //Now check if reads took too long
1334
- if ($endtime && time() > $endtime) {
1335
- $this->edebug(
1336
- 'SMTP -> get_lines(): timelimit reached (' .
1337
- $this->Timelimit . ' sec)',
1338
- self::DEBUG_LOWLEVEL
1339
- );
1340
- break;
1341
- }
1342
- }
1343
-
1344
- return $data;
1345
- }
1346
-
1347
- /**
1348
- * Enable or disable VERP address generation.
1349
- *
1350
- * @param bool $enabled
1351
- */
1352
- public function setVerp($enabled = false)
1353
- {
1354
- $this->do_verp = $enabled;
1355
- }
1356
-
1357
- /**
1358
- * Get VERP address generation mode.
1359
- *
1360
- * @return bool
1361
- */
1362
- public function getVerp()
1363
- {
1364
- return $this->do_verp;
1365
- }
1366
-
1367
- /**
1368
- * Set error messages and codes.
1369
- *
1370
- * @param string $message The error message
1371
- * @param string $detail Further detail on the error
1372
- * @param string $smtp_code An associated SMTP error code
1373
- * @param string $smtp_code_ex Extended SMTP code
1374
- */
1375
- protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1376
- {
1377
- $this->error = [
1378
- 'error' => $message,
1379
- 'detail' => $detail,
1380
- 'smtp_code' => $smtp_code,
1381
- 'smtp_code_ex' => $smtp_code_ex,
1382
- ];
1383
- }
1384
-
1385
- /**
1386
- * Set debug output method.
1387
- *
1388
- * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
1389
- */
1390
- public function setDebugOutput($method = 'echo')
1391
- {
1392
- $this->Debugoutput = $method;
1393
- }
1394
-
1395
- /**
1396
- * Get debug output method.
1397
- *
1398
- * @return string
1399
- */
1400
- public function getDebugOutput()
1401
- {
1402
- return $this->Debugoutput;
1403
- }
1404
-
1405
- /**
1406
- * Set debug output level.
1407
- *
1408
- * @param int $level
1409
- */
1410
- public function setDebugLevel($level = 0)
1411
- {
1412
- $this->do_debug = $level;
1413
- }
1414
-
1415
- /**
1416
- * Get debug output level.
1417
- *
1418
- * @return int
1419
- */
1420
- public function getDebugLevel()
1421
- {
1422
- return $this->do_debug;
1423
- }
1424
-
1425
- /**
1426
- * Set SMTP timeout.
1427
- *
1428
- * @param int $timeout The timeout duration in seconds
1429
- */
1430
- public function setTimeout($timeout = 0)
1431
- {
1432
- $this->Timeout = $timeout;
1433
- }
1434
-
1435
- /**
1436
- * Get SMTP timeout.
1437
- *
1438
- * @return int
1439
- */
1440
- public function getTimeout()
1441
- {
1442
- return $this->Timeout;
1443
- }
1444
-
1445
- /**
1446
- * Reports an error number and string.
1447
- *
1448
- * @param int $errno The error number returned by PHP
1449
- * @param string $errmsg The error message returned by PHP
1450
- * @param string $errfile The file the error occurred in
1451
- * @param int $errline The line number the error occurred on
1452
- */
1453
- protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
1454
- {
1455
- $notice = 'Connection failed.';
1456
- $this->setError(
1457
- $notice,
1458
- $errmsg,
1459
- (string) $errno
1460
- );
1461
- $this->edebug(
1462
- "$notice Error #$errno: $errmsg [$errfile line $errline]",
1463
- self::DEBUG_CONNECTION
1464
- );
1465
- }
1466
-
1467
- /**
1468
- * Extract and return the ID of the last SMTP transaction based on
1469
- * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
1470
- * Relies on the host providing the ID in response to a DATA command.
1471
- * If no reply has been received yet, it will return null.
1472
- * If no pattern was matched, it will return false.
1473
- *
1474
- * @return bool|string|null
1475
- */
1476
- protected function recordLastTransactionID()
1477
- {
1478
- $reply = $this->getLastReply();
1479
-
1480
- if (empty($reply)) {
1481
- $this->last_smtp_transaction_id = null;
1482
- } else {
1483
- $this->last_smtp_transaction_id = false;
1484
- foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1485
- $matches = [];
1486
- if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1487
- $this->last_smtp_transaction_id = trim($matches[1]);
1488
- break;
1489
- }
1490
- }
1491
- }
1492
-
1493
- return $this->last_smtp_transaction_id;
1494
- }
1495
-
1496
- /**
1497
- * Get the queue/transaction ID of the last SMTP transaction
1498
- * If no reply has been received yet, it will return null.
1499
- * If no pattern was matched, it will return false.
1500
- *
1501
- * @return bool|string|null
1502
- *
1503
- * @see recordLastTransactionID()
1504
- */
1505
- public function getLastTransactionID()
1506
- {
1507
- return $this->last_smtp_transaction_id;
1508
- }
1509
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/PROJECT_OVERVIEW.md DELETED
@@ -1,32 +0,0 @@
1
- # Martechsol Chat Assistant - Project Overview
2
-
3
- This document explains how your RAG (Retrieval-Augmented Generation) chatbot works in simple terms.
4
-
5
- ## 1. The "Brain" (The AI Model)
6
- The assistant uses a hybrid LLM approach:
7
- - **Qwen 2.5 32B** (specifically `qwen/qwen3-32b`) as the main reasoning and generation engine.
8
- - **Llama 3.1 8B** (specifically `llama-3.1-8b-instant`) as a lightning-fast model for query rewriting and expansion.
9
- - **Provider**: Both are powered by **Groq**, enabling near-instantaneous responses.
10
-
11
- ## 2. How it Works (The "RAG" Process)
12
- Instead of just relying on general knowledge, this bot "reads" your documents to give specific answers.
13
- 1. **Reading**: It looks at your files in the `docs/` folder (PDFs and Text files).
14
- 2. **Memorizing**: It breaks the text into small chunks and converts them into mathematical "vectors" (using the `bge-small-en-v1.5` model).
15
- 3. **Searching**: When you ask a question, it expands the query using Llama 3.1 8B, then performs a **Hybrid Search** combining Dense vectors (**FAISS**) and Keyword search (**BM25**).
16
- 4. **Reranking**: It deeply evaluates the top retrieved chunks using `bge-reranker-base` to ensure maximum relevance.
17
- 5. **Answering**: It sends your question along with the most relevant document parts to the Qwen 2.5 AI, which then writes a highly precise, formatted reply.
18
-
19
- ## 3. The Architecture
20
- ### Frontend (The Face)
21
- - **Gradio**: This is the clean, chat-like interface you see. It is hosted on **Hugging Face Spaces**.
22
- - **WordPress Addon**: A custom HTML/CSS/JS wrapper that lets you embed the chat as a beautiful floating button on your website.
23
-
24
- ### Backend (The Engine)
25
- - **FastAPI**: A high-performance Python framework that connects everything. It manages the messages, handles the document search, and talks to the AI provider.
26
- - **Uvicorn**: The lightning-fast server that runs the FastAPI code.
27
-
28
- ## 4. Key Features
29
- - **Humanistic Replies**: The bot is programmed to be polite, conversational, and professional.
30
- - **Context-Aware**: It remembers the last few messages in the conversation so you can ask follow-up questions.
31
- - **Greeting Support**: It can handle "Hi" and "Hello" naturally before getting down to business.
32
- - **Safe & Grounded**: It is instructed to only answer based on your documents to prevent "hallucinations" (making things up).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/README.md DELETED
@@ -1,164 +0,0 @@
1
- ---
2
- title: Fast RAG Chatbot
3
- emoji: 🤖
4
- colorFrom: blue
5
- colorTo: indigo
6
- sdk: docker
7
- pinned: false
8
- storage: true
9
- ---
10
-
11
- # High-Performance RAG Chatbot (FastAPI + FAISS)
12
-
13
- Production-style document QA chatbot using:
14
- - FastAPI API service
15
- - FAISS vector search
16
- - SentenceTransformer embeddings (`BAAI/bge-small-en-v1.5` by default)
17
- - Groq (preferred) or Hugging Face LLM APIs
18
- - Optional Gradio chat UI
19
-
20
- ## Features
21
-
22
- - Loads `.pdf` and `.txt` files from `docs/`
23
- - Cleans extracted text and chunks into semantic windows
24
- - Chunk size: 420 tokens (word-level approximation)
25
- - Overlap: 80 tokens
26
- - Builds FAISS index and saves it locally
27
- - Re-indexes only when docs change (fingerprint-based cache)
28
- - Retrieves top-k relevant chunks only (default k=4)
29
- - Strict anti-hallucination prompt
30
- - Health endpoint with docs/index status
31
- - Retrieval logging (source + similarity score)
32
- - CORS controls for website integration
33
- - Optional API key auth for `/chat`
34
- - In-memory rate limiting per client IP
35
- - Query embedding cache for repeated questions
36
- - Docker + docker-compose deployment
37
-
38
- ## Project Structure
39
-
40
- `app/main.py` - FastAPI app and endpoints
41
- `app/services/document_loader.py` - PDF/TXT ingestion and cleaning
42
- `app/services/chunker.py` - token-window chunking
43
- `app/services/embeddings.py` - embedding model wrapper
44
- `app/services/vector_store.py` - FAISS index and retrieval
45
- `app/services/llm.py` - Groq/HF LLM clients and prompt
46
- `app/services/rag_pipeline.py` - end-to-end chat flow
47
- `app/ui_gradio.py` - optional web chat UI
48
-
49
- ## Setup
50
-
51
- 1. Install dependencies:
52
-
53
- ```bash
54
- pip install -r requirements.txt
55
- ```
56
-
57
- 2. Configure environment:
58
-
59
- ```bash
60
- copy .env.example .env
61
- ```
62
-
63
- Then set your keys in `.env`:
64
- - `GROQ_API_KEY` (if using Groq)
65
- - `HF_API_KEY` (if using Hugging Face)
66
- - Optional:
67
- - `API_KEY` for request auth (send as `x-api-key`)
68
- - `CORS_ALLOW_ORIGINS` as comma-separated origins
69
- - `RATE_LIMIT_REQUESTS` and `RATE_LIMIT_WINDOW_SECONDS`
70
-
71
- 3. Add documents:
72
- - Put your `.pdf` and `.txt` files in `docs/`
73
-
74
- ## Run API
75
-
76
- ```bash
77
- uvicorn app.main:app --host 0.0.0.0 --port 8000
78
- ```
79
-
80
- ## Endpoints
81
-
82
- ### `GET /health`
83
- Returns status and index readiness.
84
-
85
- ### `POST /chat`
86
-
87
- Request:
88
- ```json
89
- {
90
- "message": "What are the key points?",
91
- "history": []
92
- }
93
- ```
94
-
95
- Response:
96
- ```json
97
- {
98
- "reply": "Answer based on retrieved context.",
99
- "retrieved_chunks": [
100
- {
101
- "id": "...",
102
- "source": "...",
103
- "text": "...",
104
- "score": 0.83
105
- }
106
- ]
107
- }
108
- ```
109
-
110
- ## Optional UI
111
-
112
- Start API first, then:
113
-
114
- ```bash
115
- python -m app.ui_gradio
116
- ```
117
-
118
- By default, Gradio now runs in direct RAG mode (no localhost API dependency).
119
- If you set `RAG_API_URL`, it will call that external FastAPI endpoint instead.
120
-
121
- ## Deployment Notes
122
-
123
- - Works as backend for websites (REST API is frontend-agnostic)
124
- - Persist `data/index/` volume in production
125
- - Prefer Groq provider for low latency
126
- - Keep `top_k` small (3-5) for speed and lower prompt tokens
127
- - Protect `/chat` with `API_KEY` in production
128
- - Set strict `CORS_ALLOW_ORIGINS` instead of `*`
129
-
130
- ## Docker Deployment
131
-
132
- Build and run:
133
-
134
- ```bash
135
- docker compose up --build -d
136
- ```
137
-
138
- Health check:
139
-
140
- ```bash
141
- curl http://localhost:8000/health
142
- ```
143
-
144
- Chat call with API key:
145
-
146
- ```bash
147
- curl -X POST http://localhost:8000/chat ^
148
- -H "Content-Type: application/json" ^
149
- -H "x-api-key: YOUR_API_KEY" ^
150
- -d "{\"message\":\"What does the handbook say about leave policy?\",\"history\":[]}"
151
- ```
152
-
153
- ## Hugging Face Spaces (Recommended: Gradio Space)
154
-
155
- Use these settings in your Space:
156
- - **SDK**: Gradio
157
- - **App file**: `app.py`
158
- - **Python version**: 3.10+ (3.11 recommended)
159
-
160
- Add Space Secrets:
161
- - `GROQ_API_KEY` (or `HF_API_KEY`)
162
- - Optional: `LLM_PROVIDER`, `GROQ_MODEL`, `HF_MODEL`, `TOP_K`
163
-
164
- Upload project files (excluding `.env`) and include your knowledge files inside `docs/`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app.py DELETED
@@ -1,6 +0,0 @@
1
- import uvicorn
2
- from app.main import app
3
-
4
- # Hugging Face Spaces (sdk: gradio) looks for `app` variable
5
- if __name__ == "__main__":
6
- uvicorn.run("app.main:app", host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
backup_2026-05-04/app/__init__.py DELETED
File without changes
backup_2026-05-04/app/admin/__init__.py DELETED
@@ -1 +0,0 @@
1
- # app/admin/__init__.py
 
 
backup_2026-05-04/app/admin/router.py DELETED
@@ -1,346 +0,0 @@
1
- """
2
- app/admin/router.py
3
- ───────────────────
4
- Secure FastAPI router for the Admin Panel.
5
-
6
- Security design:
7
- • All /admin/* routes require HTTP Basic Auth.
8
- • Credentials are compared using secrets.compare_digest() to prevent
9
- timing-based attacks — NOT a simple == comparison.
10
- • Wrong credentials always return 401 with WWW-Authenticate header.
11
- • Admin routes are completely isolated from the main app and Gradio UI.
12
-
13
- Endpoints:
14
- GET /admin → Serves the premium admin.html SPA
15
- GET /api/admin/sessions → Returns all session summaries (JSON)
16
- GET /api/admin/sessions/{session_id} → Returns full session transcript
17
- DELETE /api/admin/sessions/{session_id} → Deletes a session
18
- """
19
-
20
- import secrets
21
- import logging
22
- import asyncio
23
- import time
24
- import random
25
- import smtplib
26
- from email.mime.text import MIMEText
27
- from email.mime.multipart import MIMEMultipart
28
- from pathlib import Path
29
- from typing import Dict
30
-
31
- from fastapi import APIRouter, Depends, HTTPException, status
32
- from fastapi.responses import HTMLResponse, JSONResponse
33
- from fastapi.security import HTTPBasic, HTTPBasicCredentials
34
-
35
- from app.services.session_store import get_all_sessions, get_session, delete_session, _list_json_files, _read_json
36
- from app.core.config import get_settings
37
- import pandas as pd
38
- import io
39
- from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
40
-
41
- logger = logging.getLogger(__name__)
42
- settings = get_settings()
43
-
44
- # ── Credentials (stored as constants — in production, move to env vars) ───────
45
- ADMIN_USERNAME = "martech_admin"
46
- ADMIN_PASSWORD = "martech_admin_303"
47
-
48
- # ── Security scheme ───────────────────────────────────────────────────────────
49
- security = HTTPBasic()
50
-
51
- # ── Router ────────────────────────────────────────────────────────────────────
52
- admin_router = APIRouter(tags=["admin"])
53
-
54
- # ── OTP Store (In-memory for simplicity) ──────────────────────────────────────
55
- # Format: {username: {"code": "123456", "expires": timestamp}}
56
- _otp_store: Dict[str, dict] = {}
57
- OTP_EXPIRY_SECONDS = 300 # 5 minutes
58
-
59
- # ── Path to the admin HTML template ──────────────────────────────────────────
60
- _ADMIN_HTML_PATH = Path(__file__).parent / "templates" / "admin.html"
61
-
62
-
63
- # ── Auth dependency ───────────────────────────────────────────────────────────
64
- def verify_admin(credentials: HTTPBasicCredentials = Depends(security)) -> str:
65
- """
66
- Verifies HTTP Basic credentials using constant-time comparison.
67
- Raises 401 for any mismatch. Returns the username on success.
68
- """
69
- correct_username = secrets.compare_digest(
70
- credentials.username.encode("utf-8"),
71
- ADMIN_USERNAME.encode("utf-8"),
72
- )
73
- correct_password = secrets.compare_digest(
74
- credentials.password.encode("utf-8"),
75
- ADMIN_PASSWORD.encode("utf-8"),
76
- )
77
- if not (correct_username and correct_password):
78
- logger.warning(
79
- "Admin auth failed for user: '%s'", credentials.username
80
- )
81
- raise HTTPException(
82
- status_code=status.HTTP_401_UNAUTHORIZED,
83
- detail="Invalid admin credentials.",
84
- )
85
- return credentials.username
86
-
87
-
88
- # ── OTP Endpoints ─────────────────────────────────────────────────────────────
89
-
90
- from pydantic import BaseModel
91
-
92
- class LoginRequest(BaseModel):
93
- username: str
94
- password: str
95
-
96
- class OTPVerifyRequest(BaseModel):
97
- username: str
98
- password: str
99
- code: str
100
-
101
- @admin_router.post("/api/admin/request-otp", include_in_schema=False)
102
- async def request_otp(req: LoginRequest):
103
- """
104
- Verifies password and generates a 6-digit OTP.
105
- Sends the OTP to the configured admin email.
106
- """
107
- if not (secrets.compare_digest(req.username, ADMIN_USERNAME) and
108
- secrets.compare_digest(req.password, ADMIN_PASSWORD)):
109
- raise HTTPException(status_code=401, detail="Invalid credentials.")
110
-
111
- # Generate 6-digit code
112
- code = "".join([str(random.randint(0, 9)) for _ in range(6)])
113
- _otp_store[req.username] = {
114
- "code": code,
115
- "expires": time.time() + OTP_EXPIRY_SECONDS
116
- }
117
-
118
- # Attempt to send email
119
- email_sent = False
120
- if settings.smtp_user and settings.smtp_pass:
121
- try:
122
- # Use to_thread to avoid blocking the event loop during SMTP network I/O
123
- await asyncio.to_thread(send_otp_email, settings.admin_email, code)
124
- email_sent = True
125
- logger.info("OTP email sent to %s", settings.admin_email)
126
- except Exception as e:
127
- logger.error("Failed to send OTP email: %s", e)
128
- else:
129
- logger.warning("SMTP credentials not configured. OTP will only be in logs.")
130
-
131
- # FALLBACK/LOG: Always log to console so the user is never locked out
132
- print("\n" + "="*50)
133
- print(f" ADMIN OTP FOR {req.username.upper()}: {code}")
134
- print(f" SENDING TO: {settings.admin_email} ({'SUCCESS' if email_sent else 'FAILED/SKIPPED'})")
135
- print(" EXPIRES IN 5 MINUTES")
136
- print("="*50 + "\n")
137
-
138
- return {
139
- "message": "OTP sent successfully.",
140
- "email_status": "sent" if email_sent else "logged_to_console_only"
141
- }
142
-
143
-
144
- import socket
145
-
146
- def send_otp_email(to_email: str, otp_code: str):
147
- """Sends a professional OTP email via SMTP. Run in thread."""
148
- msg = MIMEMultipart()
149
- msg['From'] = settings.smtp_user
150
- msg['To'] = to_email
151
- msg['Subject'] = f"Your Admin Access Code: {otp_code}"
152
-
153
- body = f"""
154
- <html>
155
- <body style="font-family: sans-serif; padding: 20px; color: #333;">
156
- <h2 style="color: #3b82f6;">Martechsol Admin Panel</h2>
157
- <p>Your one-time security code is:</p>
158
- <div style="font-size: 32px; font-weight: bold; letter-spacing: 5px; padding: 15px; background: #f3f4f6; border-radius: 8px; display: inline-block; margin: 10px 0;">
159
- {otp_code}
160
- </div>
161
- <p style="color: #666; font-size: 14px;">This code will expire in 5 minutes.</p>
162
- <hr style="border: 0; border-top: 1px solid #eee; margin-top: 20px;">
163
- <p style="font-size: 12px; color: #999;">If you did not request this code, please secure your account.</p>
164
- </body>
165
- </html>
166
- """
167
- msg.attach(MIMEText(body, 'html'))
168
-
169
- # Force IPv4 resolution to avoid "Network unreachable" IPv6 issues
170
- try:
171
- ais = socket.getaddrinfo(settings.smtp_server, settings.smtp_port, socket.AF_INET)
172
- target_ip = ais[0][4][0]
173
- logger.info("Resolved %s to IPv4: %s", settings.smtp_server, target_ip)
174
- except Exception as e:
175
- logger.error("DNS Resolution failed: %s", e)
176
- target_ip = settings.smtp_server
177
-
178
- logger.info("Attempting SMTP_SSL connection to %s:%d (via %s)",
179
- settings.smtp_server, settings.smtp_port, target_ip)
180
-
181
- # Using the IP directly can sometimes bypass DNS/routing quirks
182
- with smtplib.SMTP_SSL(target_ip, settings.smtp_port, timeout=15) as server:
183
- server.login(settings.smtp_user, settings.smtp_pass)
184
- server.send_message(msg)
185
-
186
-
187
- @admin_router.post("/api/admin/verify-otp", include_in_schema=False)
188
- async def verify_otp(req: OTPVerifyRequest):
189
- """Verifies both the password and the OTP code."""
190
- # 1. Verify password first
191
- if not (secrets.compare_digest(req.username, ADMIN_USERNAME) and
192
- secrets.compare_digest(req.password, ADMIN_PASSWORD)):
193
- raise HTTPException(status_code=401, detail="Invalid credentials.")
194
-
195
- # 2. Verify OTP
196
- stored = _otp_store.get(req.username)
197
- if not stored:
198
- raise HTTPException(status_code=400, detail="No OTP requested for this user.")
199
-
200
- if time.time() > stored["expires"]:
201
- del _otp_store[req.username]
202
- raise HTTPException(status_code=400, detail="OTP has expired. Please request a new one.")
203
-
204
- if not secrets.compare_digest(req.code, stored["code"]):
205
- raise HTTPException(status_code=401, detail="Invalid OTP code.")
206
-
207
- # Success — clear OTP and return
208
- del _otp_store[req.username]
209
- return {"message": "OTP verified."}
210
-
211
-
212
- # ── Routes ────────────────────────────────────────────────────────────────────
213
-
214
- @admin_router.get("/admin", response_class=HTMLResponse, include_in_schema=False)
215
- async def admin_panel():
216
- """Serves the admin panel SPA."""
217
- try:
218
- html_content = _ADMIN_HTML_PATH.read_text(encoding="utf-8")
219
- return HTMLResponse(content=html_content, status_code=200)
220
- except FileNotFoundError:
221
- logger.error("admin.html template not found at %s", _ADMIN_HTML_PATH)
222
- raise HTTPException(
223
- status_code=500,
224
- detail="Admin panel template is missing. Please redeploy.",
225
- )
226
-
227
-
228
- @admin_router.get("/api/admin/sessions", include_in_schema=False)
229
- async def list_sessions(username: str = Depends(verify_admin)):
230
- """Returns all session summaries sorted by most recent activity."""
231
- sessions = await get_all_sessions()
232
- return JSONResponse(content={"sessions": sessions, "total": len(sessions)})
233
-
234
-
235
- @admin_router.get("/api/admin/sessions/{session_id}", include_in_schema=False)
236
- async def get_session_detail(
237
- session_id: str, username: str = Depends(verify_admin)
238
- ):
239
- """Returns the full Q&A transcript for a specific session."""
240
- # Basic input sanitisation — session_id should only be a UUID-like string
241
- if not session_id.replace("-", "").replace("_", "").isalnum():
242
- raise HTTPException(status_code=400, detail="Invalid session ID format.")
243
-
244
- data = await get_session(session_id)
245
- if not data:
246
- raise HTTPException(
247
- status_code=404,
248
- detail=f"Session '{session_id}' not found.",
249
- )
250
- return JSONResponse(content=data)
251
-
252
-
253
- @admin_router.delete("/api/admin/sessions/{session_id}", include_in_schema=False)
254
- async def remove_session(
255
- session_id: str, username: str = Depends(verify_admin)
256
- ):
257
- """Deletes a session file permanently."""
258
- if not session_id.replace("-", "").replace("_", "").isalnum():
259
- raise HTTPException(status_code=400, detail="Invalid session ID format.")
260
-
261
- success = await delete_session(session_id)
262
- if not success:
263
- raise HTTPException(status_code=500, detail="Failed to delete session.")
264
- return JSONResponse(content={"deleted": session_id})
265
-
266
-
267
- # ── Export Endpoints ─────────────────────────────────────────────────────────
268
-
269
- @admin_router.get("/api/admin/export/session/{session_id}", include_in_schema=False)
270
- async def export_session(
271
- session_id: str, format: str = "excel", username: str = Depends(verify_admin)
272
- ):
273
- """Exports a single session's transcript in Excel or JSON format."""
274
- data = await get_session(session_id)
275
- if not data:
276
- raise HTTPException(status_code=404, detail="Session not found.")
277
-
278
- messages = data.get("messages", [])
279
- export_data = []
280
- for msg in messages:
281
- export_data.append({
282
- "Timestamp": msg.get("timestamp", ""),
283
- "Question": msg.get("question", ""),
284
- "Answer": msg.get("answer", "")
285
- })
286
-
287
- if format.lower() == "json":
288
- return JSONResponse(
289
- content=export_data,
290
- headers={"Content-Disposition": f"attachment; filename=session_{session_id}.json"}
291
- )
292
-
293
- # Excel Export
294
- df = pd.DataFrame(export_data)
295
- output = io.BytesIO()
296
- with pd.ExcelWriter(output, engine='openpyxl') as writer:
297
- df.to_excel(writer, index=False, sheet_name='Transcript')
298
-
299
- output.seek(0)
300
- return StreamingResponse(
301
- output,
302
- media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
303
- headers={"Content-Disposition": f"attachment; filename=session_{session_id}.xlsx"}
304
- )
305
-
306
-
307
- @admin_router.get("/api/admin/export/all", include_in_schema=False)
308
- async def export_all_sessions(
309
- format: str = "excel", username: str = Depends(verify_admin)
310
- ):
311
- """Exports all sessions' transcripts in a single Excel or JSON file."""
312
- files = await asyncio.to_thread(_list_json_files)
313
- all_messages = []
314
-
315
- for fpath in files:
316
- try:
317
- data = await asyncio.to_thread(_read_json, fpath)
318
- session_id = data.get("session_id", fpath.stem)
319
- for msg in data.get("messages", []):
320
- all_messages.append({
321
- "Session ID": session_id,
322
- "Timestamp": msg.get("timestamp", ""),
323
- "Question": msg.get("question", ""),
324
- "Answer": msg.get("answer", "")
325
- })
326
- except Exception as e:
327
- logger.warning("Failed to read %s for export: %s", fpath, e)
328
-
329
- if format.lower() == "json":
330
- return JSONResponse(
331
- content=all_messages,
332
- headers={"Content-Disposition": "attachment; filename=all_chats_export.json"}
333
- )
334
-
335
- # Excel Export
336
- df = pd.DataFrame(all_messages)
337
- output = io.BytesIO()
338
- with pd.ExcelWriter(output, engine='openpyxl') as writer:
339
- df.to_excel(writer, index=False, sheet_name='All Chats')
340
-
341
- output.seek(0)
342
- return StreamingResponse(
343
- output,
344
- media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
345
- headers={"Content-Disposition": "attachment; filename=all_chats_export.xlsx"}
346
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/admin/templates/admin.html DELETED
@@ -1,1143 +0,0 @@
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>Martechsol — Admin Panel</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com" />
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
10
- <style>
11
- *,
12
- *::before,
13
- *::after {
14
- box-sizing: border-box;
15
- margin: 0;
16
- padding: 0
17
- }
18
-
19
- :root {
20
- --bg: #0a0d14;
21
- --surface: #111827;
22
- --surface2: #1a2235;
23
- --surface3: #1f2a3d;
24
- --border: #1e2d45;
25
- --accent: #3b82f6;
26
- --accent2: #6366f1;
27
- --green: #10b981;
28
- --red: #ef4444;
29
- --yellow: #f59e0b;
30
- --text: #f1f5f9;
31
- --text2: #94a3b8;
32
- --text3: #64748b;
33
- --radius: 12px;
34
- --shadow: 0 4px 24px rgba(0, 0, 0, .4);
35
- }
36
-
37
- html,
38
- body {
39
- height: 100%;
40
- font-family: 'Inter', sans-serif;
41
- background: var(--bg);
42
- color: var(--text);
43
- overflow: hidden
44
- }
45
-
46
- /* ── Login Screen ── */
47
- #login-screen {
48
- position: fixed;
49
- inset: 0;
50
- display: flex;
51
- align-items: center;
52
- justify-content: center;
53
- background: radial-gradient(ellipse at 50% 0%, #1a2a4a 0%, var(--bg) 70%);
54
- z-index: 9999;
55
- }
56
-
57
- .login-card {
58
- width: 380px;
59
- background: var(--surface);
60
- border: 1px solid var(--border);
61
- border-radius: 20px;
62
- padding: 44px 40px;
63
- box-shadow: var(--shadow);
64
- animation: fadeUp .5s ease;
65
- }
66
-
67
- .login-logo {
68
- text-align: center;
69
- margin-bottom: 28px
70
- }
71
-
72
- .login-logo svg {
73
- width: 52px;
74
- height: 52px
75
- }
76
-
77
- .login-logo h1 {
78
- font-size: 22px;
79
- font-weight: 700;
80
- margin-top: 12px;
81
- background: linear-gradient(135deg, var(--accent), var(--accent2));
82
- -webkit-background-clip: text;
83
- -webkit-text-fill-color: transparent
84
- }
85
-
86
- .login-logo p {
87
- font-size: 13px;
88
- color: var(--text3);
89
- margin-top: 4px
90
- }
91
-
92
- .form-group {
93
- margin-bottom: 16px
94
- }
95
-
96
- .form-group label {
97
- display: block;
98
- font-size: 12px;
99
- font-weight: 600;
100
- color: var(--text2);
101
- text-transform: uppercase;
102
- letter-spacing: .06em;
103
- margin-bottom: 6px
104
- }
105
-
106
- .form-group input {
107
- width: 100%;
108
- padding: 11px 14px;
109
- background: var(--surface2);
110
- border: 1px solid var(--border);
111
- border-radius: 8px;
112
- color: var(--text);
113
- font-size: 14px;
114
- font-family: inherit;
115
- outline: none;
116
- transition: border-color .2s;
117
- }
118
-
119
- .form-group input:focus {
120
- border-color: var(--accent)
121
- }
122
-
123
- .btn-login {
124
- width: 100%;
125
- padding: 12px;
126
- border: none;
127
- border-radius: 8px;
128
- cursor: pointer;
129
- font-size: 14px;
130
- font-weight: 600;
131
- font-family: inherit;
132
- background: linear-gradient(135deg, var(--accent), var(--accent2));
133
- color: #fff;
134
- transition: opacity .2s, transform .15s;
135
- margin-top: 4px;
136
- }
137
-
138
- .btn-login:hover {
139
- opacity: .9;
140
- transform: translateY(-1px)
141
- }
142
-
143
- .login-error {
144
- display: none;
145
- background: rgba(239, 68, 68, .12);
146
- border: 1px solid rgba(239, 68, 68, .3);
147
- border-radius: 8px;
148
- padding: 10px 14px;
149
- font-size: 13px;
150
- color: #fca5a5;
151
- margin-top: 14px;
152
- }
153
-
154
- /* ── App Shell ── */
155
- #app {
156
- display: none;
157
- height: 100vh;
158
- display: grid;
159
- grid-template-columns: 280px 1fr;
160
- grid-template-rows: 60px 1fr
161
- }
162
-
163
- .topbar {
164
- grid-column: 1/-1;
165
- display: flex;
166
- align-items: center;
167
- justify-content: space-between;
168
- padding: 0 24px;
169
- background: var(--surface);
170
- border-bottom: 1px solid var(--border);
171
- }
172
-
173
- .topbar-brand {
174
- display: flex;
175
- align-items: center;
176
- gap: 10px;
177
- font-weight: 700;
178
- font-size: 16px
179
- }
180
-
181
- .topbar-brand .dot {
182
- width: 8px;
183
- height: 8px;
184
- background: var(--green);
185
- border-radius: 50%;
186
- box-shadow: 0 0 8px var(--green);
187
- animation: pulse 2s infinite
188
- }
189
-
190
- .topbar-right {
191
- display: flex;
192
- align-items: center;
193
- gap: 14px
194
- }
195
-
196
- .badge {
197
- font-size: 11px;
198
- padding: 3px 10px;
199
- border-radius: 20px;
200
- background: rgba(59, 130, 246, .15);
201
- color: var(--accent);
202
- border: 1px solid rgba(59, 130, 246, .25)
203
- }
204
-
205
- .btn-logout {
206
- padding: 7px 16px;
207
- border: 1px solid var(--border);
208
- border-radius: 8px;
209
- background: transparent;
210
- color: var(--text2);
211
- font-size: 13px;
212
- font-family: inherit;
213
- cursor: pointer;
214
- transition: all .2s;
215
- }
216
-
217
- .btn-logout:hover {
218
- border-color: var(--red);
219
- color: var(--red)
220
- }
221
-
222
- /* ── Sidebar ── */
223
- .sidebar {
224
- background: var(--surface);
225
- border-right: 1px solid var(--border);
226
- overflow: hidden;
227
- display: flex;
228
- flex-direction: column;
229
- }
230
-
231
- .sidebar-head {
232
- padding: 16px 20px;
233
- border-bottom: 1px solid var(--border);
234
- }
235
-
236
- .sidebar-head h2 {
237
- font-size: 12px;
238
- font-weight: 600;
239
- text-transform: uppercase;
240
- letter-spacing: .08em;
241
- color: var(--text3)
242
- }
243
-
244
- .sidebar-stats {
245
- display: flex;
246
- gap: 16px;
247
- margin-top: 10px
248
- }
249
-
250
- .stat {
251
- text-align: center;
252
- flex: 1;
253
- background: var(--surface2);
254
- border-radius: 8px;
255
- padding: 8px 4px
256
- }
257
-
258
- .stat-val {
259
- font-size: 20px;
260
- font-weight: 700;
261
- color: var(--accent)
262
- }
263
-
264
- .stat-lbl {
265
- font-size: 10px;
266
- color: var(--text3);
267
- margin-top: 1px
268
- }
269
-
270
- .sidebar-actions {
271
- padding: 12px 16px;
272
- display: flex;
273
- gap: 8px;
274
- border-bottom: 1px solid var(--border)
275
- }
276
-
277
- .btn-export {
278
- flex: 1;
279
- padding: 8px;
280
- border: 1px solid var(--border);
281
- border-radius: 8px;
282
- background: var(--surface2);
283
- color: var(--text2);
284
- font-size: 12px;
285
- font-weight: 600;
286
- cursor: pointer;
287
- display: flex;
288
- align-items: center;
289
- justify-content: center;
290
- gap: 6px;
291
- transition: all .2s;
292
- }
293
-
294
- .btn-export:hover {
295
- border-color: var(--accent);
296
- color: var(--accent);
297
- background: rgba(59, 130, 246, 0.05)
298
- }
299
-
300
- .export-dropdown {
301
- position: relative;
302
- display: inline-block;
303
- flex: 1
304
- }
305
-
306
- .export-menu {
307
- position: absolute;
308
- top: 100%;
309
- left: 0;
310
- right: 0;
311
- background: var(--surface3);
312
- border: 1px solid var(--border);
313
- border-radius: 8px;
314
- margin-top: 4px;
315
- display: none;
316
- z-index: 100;
317
- box-shadow: var(--shadow);
318
- overflow: hidden;
319
- }
320
-
321
- .export-menu button {
322
- width: 100%;
323
- padding: 10px;
324
- border: none;
325
- background: none;
326
- color: var(--text2);
327
- font-size: 12px;
328
- text-align: left;
329
- cursor: pointer;
330
- transition: all .2s;
331
- }
332
-
333
- .export-menu button:hover {
334
- background: var(--surface2);
335
- color: var(--text)
336
- }
337
-
338
- .export-dropdown:hover .export-menu {
339
- display: block
340
- }
341
-
342
- .search-wrap {
343
- padding: 12px 16px;
344
- border-bottom: 1px solid var(--border)
345
- }
346
-
347
- .search-wrap input {
348
- width: 100%;
349
- padding: 8px 12px;
350
- background: var(--surface2);
351
- border: 1px solid var(--border);
352
- border-radius: 8px;
353
- color: var(--text);
354
- font-size: 13px;
355
- font-family: inherit;
356
- outline: none;
357
- }
358
-
359
- .search-wrap input:focus {
360
- border-color: var(--accent)
361
- }
362
-
363
- .session-list {
364
- flex: 1;
365
- overflow-y: auto;
366
- padding: 8px
367
- }
368
-
369
- .session-list::-webkit-scrollbar {
370
- width: 4px
371
- }
372
-
373
- .session-list::-webkit-scrollbar-track {
374
- background: transparent
375
- }
376
-
377
- .session-list::-webkit-scrollbar-thumb {
378
- background: var(--border);
379
- border-radius: 4px
380
- }
381
-
382
- .session-item {
383
- padding: 12px 14px;
384
- border-radius: 10px;
385
- cursor: pointer;
386
- margin-bottom: 4px;
387
- border: 1px solid transparent;
388
- transition: all .2s;
389
- }
390
-
391
- .session-item:hover {
392
- background: var(--surface2);
393
- border-color: var(--border)
394
- }
395
-
396
- .session-item.active {
397
- background: rgba(59, 130, 246, .1);
398
- border-color: rgba(59, 130, 246, .35)
399
- }
400
-
401
- .session-item-id {
402
- font-size: 12px;
403
- font-weight: 600;
404
- color: var(--text);
405
- white-space: nowrap;
406
- overflow: hidden;
407
- text-overflow: ellipsis
408
- }
409
-
410
- .session-item-meta {
411
- display: flex;
412
- justify-content: space-between;
413
- margin-top: 4px
414
- }
415
-
416
- .session-item-time {
417
- font-size: 11px;
418
- color: var(--text3)
419
- }
420
-
421
- .session-item-count {
422
- font-size: 11px;
423
- background: var(--surface3);
424
- padding: 1px 7px;
425
- border-radius: 20px;
426
- color: var(--text2)
427
- }
428
-
429
- .no-sessions {
430
- text-align: center;
431
- padding: 40px 20px;
432
- color: var(--text3);
433
- font-size: 13px
434
- }
435
-
436
- /* ── Main Content ── */
437
- .main {
438
- overflow: hidden;
439
- display: flex;
440
- flex-direction: column;
441
- background: var(--bg)
442
- }
443
-
444
- .main-placeholder {
445
- flex: 1;
446
- display: flex;
447
- flex-direction: column;
448
- align-items: center;
449
- justify-content: center;
450
- color: var(--text3);
451
- gap: 14px;
452
- }
453
-
454
- .main-placeholder svg {
455
- width: 56px;
456
- height: 56px;
457
- opacity: .3
458
- }
459
-
460
- .main-placeholder p {
461
- font-size: 14px
462
- }
463
-
464
- .chat-view {
465
- flex: 1;
466
- display: flex;
467
- flex-direction: column;
468
- overflow: hidden
469
- }
470
-
471
- .chat-header {
472
- padding: 16px 24px;
473
- background: var(--surface);
474
- border-bottom: 1px solid var(--border);
475
- display: flex;
476
- align-items: center;
477
- justify-content: space-between;
478
- }
479
-
480
- .chat-header-info h3 {
481
- font-size: 14px;
482
- font-weight: 600
483
- }
484
-
485
- .chat-header-info p {
486
- font-size: 12px;
487
- color: var(--text3);
488
- margin-top: 2px
489
- }
490
-
491
- .chat-header-actions {
492
- display: flex;
493
- gap: 8px
494
- }
495
-
496
- .btn-del {
497
- padding: 6px 14px;
498
- border: 1px solid rgba(239, 68, 68, .3);
499
- border-radius: 8px;
500
- background: rgba(239, 68, 68, .08);
501
- color: #fca5a5;
502
- font-size: 12px;
503
- font-family: inherit;
504
- cursor: pointer;
505
- transition: all .2s;
506
- }
507
-
508
- .btn-del:hover {
509
- background: rgba(239, 68, 68, .2)
510
- }
511
-
512
- .messages-area {
513
- flex: 1;
514
- overflow-y: auto;
515
- padding: 24px;
516
- display: flex;
517
- flex-direction: column;
518
- gap: 16px
519
- }
520
-
521
- .messages-area::-webkit-scrollbar {
522
- width: 4px
523
- }
524
-
525
- .messages-area::-webkit-scrollbar-thumb {
526
- background: var(--border);
527
- border-radius: 4px
528
- }
529
-
530
- .msg-block {
531
- display: flex;
532
- flex-direction: column;
533
- gap: 8px
534
- }
535
-
536
- .msg-label {
537
- font-size: 10px;
538
- font-weight: 700;
539
- text-transform: uppercase;
540
- letter-spacing: .08em;
541
- display: flex;
542
- align-items: center;
543
- gap: 6px
544
- }
545
-
546
- .msg-label.q {
547
- color: var(--accent)
548
- }
549
-
550
- .msg-label.a {
551
- color: var(--green)
552
- }
553
-
554
- .msg-bubble {
555
- padding: 13px 16px;
556
- border-radius: 10px;
557
- font-size: 14px;
558
- line-height: 1.65;
559
- white-space: pre-wrap;
560
- word-break: break-word;
561
- max-width: 100%;
562
- }
563
-
564
- .msg-bubble.q {
565
- background: rgba(59, 130, 246, .1);
566
- border: 1px solid rgba(59, 130, 246, .2);
567
- color: var(--text)
568
- }
569
-
570
- .msg-bubble.a {
571
- background: rgba(16, 185, 129, .08);
572
- border: 1px solid rgba(16, 185, 129, .2);
573
- color: var(--text)
574
- }
575
-
576
- .msg-ts {
577
- font-size: 11px;
578
- color: var(--text3);
579
- margin-top: 2px
580
- }
581
-
582
- .msg-num {
583
- font-size: 11px;
584
- background: var(--surface2);
585
- padding: 1px 8px;
586
- border-radius: 20px;
587
- color: var(--text3)
588
- }
589
-
590
- .spinner {
591
- width: 32px;
592
- height: 32px;
593
- border: 3px solid var(--border);
594
- border-top-color: var(--accent);
595
- border-radius: 50%;
596
- animation: spin .7s linear infinite;
597
- margin: auto;
598
- }
599
-
600
- .toast {
601
- position: fixed;
602
- bottom: 24px;
603
- right: 24px;
604
- padding: 12px 20px;
605
- background: var(--surface2);
606
- border: 1px solid var(--border);
607
- border-radius: 10px;
608
- font-size: 13px;
609
- color: var(--text);
610
- box-shadow: var(--shadow);
611
- transform: translateY(20px);
612
- opacity: 0;
613
- transition: all .3s;
614
- pointer-events: none;
615
- z-index: 9998;
616
- }
617
-
618
- .toast.show {
619
- transform: translateY(0);
620
- opacity: 1
621
- }
622
-
623
- .toast.success {
624
- border-color: rgba(16, 185, 129, .4);
625
- color: #6ee7b7
626
- }
627
-
628
- .toast.error {
629
- border-color: rgba(239, 68, 68, .4);
630
- color: #fca5a5
631
- }
632
-
633
- /* ── Animations ── */
634
- @keyframes fadeUp {
635
- from {
636
- opacity: 0;
637
- transform: translateY(18px)
638
- }
639
-
640
- to {
641
- opacity: 1;
642
- transform: translateY(0)
643
- }
644
- }
645
-
646
- @keyframes pulse {
647
-
648
- 0%,
649
- 100% {
650
- opacity: 1
651
- }
652
-
653
- 50% {
654
- opacity: .4
655
- }
656
- }
657
-
658
- @keyframes spin {
659
- to {
660
- transform: rotate(360deg)
661
- }
662
- }
663
- </style>
664
- </head>
665
-
666
- <body>
667
-
668
- <!-- LOGIN SCREEN -->
669
- <div id="login-screen">
670
- <div class="login-card">
671
- <div class="login-logo">
672
- <svg viewBox="0 0 52 52" fill="none">
673
- <rect width="52" height="52" rx="14" fill="url(#lg)" />
674
- <path d="M16 26h20M26 16v20" stroke="#fff" stroke-width="2.5" stroke-linecap="round" />
675
- <defs>
676
- <linearGradient id="lg" x1="0" y1="0" x2="52" y2="52">
677
- <stop stop-color="#3b82f6" />
678
- <stop offset="1" stop-color="#6366f1" />
679
- </linearGradient>
680
- </defs>
681
- </svg>
682
- <h1>Martechsol Admin</h1>
683
- <p>Secure Chat Analytics Dashboard</p>
684
- </div>
685
- <!-- Stage 1: Username & Password -->
686
- <div id="login-stage-1">
687
- <div class="form-group">
688
- <label>Username</label>
689
- <input type="text" id="inp-user" placeholder="Enter username" autocomplete="username" />
690
- </div>
691
- <div class="form-group">
692
- <label>Password</label>
693
- <input type="password" id="inp-pass" placeholder="Enter password" autocomplete="current-password" />
694
- </div>
695
- <button class="btn-login" id="btn-login">Sign In</button>
696
- </div>
697
-
698
- <!-- Stage 2: OTP (Hidden by default) -->
699
- <div id="login-stage-2" style="display:none">
700
- <div class="form-group">
701
- <label>One-Time Password (OTP)</label>
702
- <input type="text" id="inp-otp" placeholder="6-digit code" maxlength="6"
703
- style="text-align:center;letter-spacing:0.5em;font-size:18px;font-weight:700" />
704
- <p id="otp-hint" style="font-size:11px;color:var(--text3);margin-top:8px;text-align:center">Code sent to your
705
- email</p>
706
- </div>
707
- <button class="btn-login" id="btn-verify">Verify & Enter</button>
708
- <button class="btn-logout" id="btn-back" style="width:100%;margin-top:10px;border:none">Back to Login</button>
709
- </div>
710
-
711
- <div class="login-error" id="login-error">Invalid credentials. Please try again.</div>
712
- </div>
713
- </div>
714
-
715
- <!-- APP SHELL (hidden until login) -->
716
- <div id="app" style="display:none">
717
- <!-- Topbar -->
718
- <header class="topbar">
719
- <div class="topbar-brand">
720
- <div class="dot"></div>
721
- Martechsol Admin Panel
722
- </div>
723
- <div class="topbar-right">
724
- <span class="badge" id="total-badge">Loading…</span>
725
- <button class="btn-logout" id="btn-logout">Sign Out</button>
726
- </div>
727
- </header>
728
-
729
- <!-- Sidebar -->
730
- <aside class="sidebar">
731
- <div class="sidebar-head">
732
- <h2>Sessions</h2>
733
- <div class="sidebar-stats">
734
- <div class="stat">
735
- <div class="stat-val" id="stat-total">—</div>
736
- <div class="stat-lbl">Total</div>
737
- </div>
738
- <div class="stat">
739
- <div class="stat-val" id="stat-msgs">—</div>
740
- <div class="stat-lbl">Messages</div>
741
- </div>
742
- <div class="stat">
743
- <div class="stat-val" id="stat-today">—</div>
744
- <div class="stat-lbl">Today</div>
745
- </div>
746
- </div>
747
- </div>
748
- <div class="sidebar-actions">
749
- <div class="export-dropdown">
750
- <button class="btn-export">
751
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
752
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" />
753
- </svg>
754
- Export All
755
- </button>
756
- <div class="export-menu">
757
- <button id="btn-export-all-excel">Excel (.xlsx)</button>
758
- <button id="btn-export-all-json">JSON (.json)</button>
759
- </div>
760
- </div>
761
- </div>
762
- <div class="search-wrap">
763
- <input type="text" id="search-inp" placeholder="Search sessions…" />
764
- </div>
765
- <div class="session-list" id="session-list">
766
- <div class="no-sessions">Loading sessions…</div>
767
- </div>
768
- </aside>
769
-
770
- <!-- Main -->
771
- <main class="main" id="main-area">
772
- <div class="main-placeholder" id="placeholder">
773
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
774
- <path
775
- d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
776
- stroke-linecap="round" stroke-linejoin="round" />
777
- </svg>
778
- <p>Select a session from the sidebar to view the transcript</p>
779
- </div>
780
- <div class="chat-view" id="chat-view" style="display:none">
781
- <div class="chat-header">
782
- <div class="chat-header-info">
783
- <h3 id="view-session-id">Session ID</h3>
784
- <p id="view-session-user" style="font-weight:600;color:var(--accent);margin-top:2px"></p>
785
- <p id="view-session-meta">Loading…</p>
786
- </div>
787
- <div class="chat-header-actions">
788
- <div class="export-dropdown">
789
- <button class="btn-export" style="padding: 6px 12px;">
790
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
791
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" />
792
- </svg>
793
- Export Session
794
- </button>
795
- <div class="export-menu" style="right:0;left:auto;min-width:120px">
796
- <button id="btn-export-session-excel">Excel (.xlsx)</button>
797
- <button id="btn-export-session-json">JSON (.json)</button>
798
- </div>
799
- </div>
800
- <button class="btn-del" id="btn-delete-session">Delete Session</button>
801
- </div>
802
- </div>
803
- <div class="messages-area" id="messages-area"></div>
804
- </div>
805
- </main>
806
- </div>
807
-
808
- <div class="toast" id="toast"></div>
809
-
810
- <script>
811
- (function () {
812
- const CREDS = { user: 'martech_admin', pass: 'martech_admin_303' };
813
- let _creds = null;
814
- let _allSessions = [];
815
- let _activeId = null;
816
-
817
- const STORAGE_KEY = 'martech_admin_creds';
818
-
819
- // ── Helpers ──────────────────────────────────────────────
820
- function $(id) { return document.getElementById(id) }
821
- function authHeader() { return 'Basic ' + btoa(_creds.user + ':' + _creds.pass) }
822
-
823
- function showToast(msg, type = '') {
824
- const t = $('toast');
825
- t.textContent = msg; t.className = 'toast ' + (type || '');
826
- t.classList.add('show');
827
- setTimeout(() => t.classList.remove('show'), 3000);
828
- }
829
-
830
- function fmtDate(iso) {
831
- if (!iso) return '—';
832
- const d = new Date(iso);
833
- return d.toLocaleDateString() + ' ' + d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
834
- }
835
-
836
- function shortId(id) {
837
- if (!id) return '—';
838
- return id.length > 16 ? id.slice(0, 8) + '…' + id.slice(-6) : id;
839
- }
840
-
841
- function todayCount(sessions) {
842
- const today = new Date().toDateString();
843
- return sessions.filter(s => {
844
- try { return new Date(s.created_at).toDateString() === today } catch { return false }
845
- }).length;
846
- }
847
-
848
- // ── Login ─────────────────────────────────────────────────
849
- async function doLogin() {
850
- const u = $('inp-user').value.trim();
851
- const p = $('inp-pass').value;
852
- if (!u || !p) return;
853
-
854
- $('btn-login').textContent = 'Authenticating…';
855
- $('btn-login').disabled = true;
856
- $('login-error').style.display = 'none';
857
-
858
- try {
859
- const r = await fetch('/api/admin/request-otp', {
860
- method: 'POST',
861
- headers: { 'Content-Type': 'application/json' },
862
- body: JSON.stringify({ username: u, password: p })
863
- });
864
-
865
- if (r.ok) {
866
- const data = await r.json();
867
- // Switch to OTP stage
868
- $('login-stage-1').style.display = 'none';
869
- $('login-stage-2').style.display = 'block';
870
- if (data.email_status === 'logged_to_console_only') {
871
- $('otp-hint').textContent = 'SMTP not configured. Check server logs.';
872
- $('otp-hint').style.color = 'var(--yellow)';
873
- } else {
874
- $('otp-hint').textContent = 'Code sent to your registered email.';
875
- $('otp-hint').style.color = 'var(--text3)';
876
- }
877
- $('inp-otp').focus();
878
- } else {
879
- const err = await r.json();
880
- $('login-error').textContent = err.detail || 'Login failed.';
881
- $('login-error').style.display = 'block';
882
- }
883
- } catch (e) {
884
- $('login-error').textContent = 'Network error: ' + e.message;
885
- $('login-error').style.display = 'block';
886
- } finally {
887
- $('btn-login').textContent = 'Sign In';
888
- $('btn-login').disabled = false;
889
- }
890
- }
891
-
892
- async function doVerifyOtp() {
893
- const u = $('inp-user').value.trim();
894
- const p = $('inp-pass').value;
895
- const otp = $('inp-otp').value.trim();
896
- if (!otp) return;
897
-
898
- $('btn-verify').textContent = 'Verifying…';
899
- $('btn-verify').disabled = true;
900
- $('login-error').style.display = 'none';
901
-
902
- try {
903
- const r = await fetch('/api/admin/verify-otp', {
904
- method: 'POST',
905
- headers: { 'Content-Type': 'application/json' },
906
- body: JSON.stringify({ username: u, password: p, code: otp })
907
- });
908
-
909
- if (r.ok) {
910
- _creds = { user: u, pass: p, otp_verified: true };
911
- localStorage.setItem(STORAGE_KEY, JSON.stringify(_creds));
912
- $('login-screen').style.display = 'none';
913
- $('app').style.display = 'grid';
914
- loadSessions();
915
- showToast('Login successful', 'success');
916
- } else {
917
- const err = await r.json();
918
- $('login-error').textContent = err.detail || 'Invalid OTP.';
919
- $('login-error').style.display = 'block';
920
- $('inp-otp').value = '';
921
- }
922
- } catch (e) {
923
- $('login-error').textContent = 'Network error: ' + e.message;
924
- $('login-error').style.display = 'block';
925
- } finally {
926
- $('btn-verify').textContent = 'Verify & Enter';
927
- $('btn-verify').disabled = false;
928
- }
929
- }
930
-
931
- $('btn-login').addEventListener('click', doLogin);
932
- $('btn-verify').addEventListener('click', doVerifyOtp);
933
- $('btn-back').addEventListener('click', () => {
934
- $('login-stage-2').style.display = 'none';
935
- $('login-stage-1').style.display = 'block';
936
- $('login-error').style.display = 'none';
937
- $('inp-otp').value = '';
938
- });
939
-
940
- [$('inp-user'), $('inp-pass')].forEach(el => el.addEventListener('keydown', e => { if (e.key === 'Enter') doLogin() }));
941
- $('inp-otp').addEventListener('keydown', e => { if (e.key === 'Enter') doVerifyOtp() });
942
-
943
- $('btn-logout').addEventListener('click', () => {
944
- _creds = null; _allSessions = []; _activeId = null;
945
- localStorage.removeItem(STORAGE_KEY);
946
- $('app').style.display = 'none';
947
- $('login-screen').style.display = 'flex';
948
- $('login-stage-2').style.display = 'none';
949
- $('login-stage-1').style.display = 'block';
950
- $('inp-pass').value = '';
951
- $('inp-otp').value = '';
952
- $('login-error').style.display = 'none';
953
- });
954
-
955
- // ── Auto-Login ────────────────────────────────────────────
956
- (function init() {
957
- const stored = localStorage.getItem(STORAGE_KEY);
958
- if (stored) {
959
- try {
960
- const parsed = JSON.parse(stored);
961
- if (parsed.user === CREDS.user && parsed.pass === CREDS.pass && parsed.otp_verified) {
962
- _creds = parsed;
963
- $('login-screen').style.display = 'none';
964
- $('app').style.display = 'grid';
965
- loadSessions();
966
- } else {
967
- // Force re-login if OTP was not verified
968
- localStorage.removeItem(STORAGE_KEY);
969
- }
970
- } catch (e) {
971
- localStorage.removeItem(STORAGE_KEY);
972
- }
973
- }
974
- })();
975
-
976
- // ── Sessions list ─────────────────────────────────────────
977
- async function loadSessions() {
978
- $('session-list').innerHTML = '<div class="no-sessions"><div class="spinner"></div></div>';
979
- try {
980
- const r = await fetch('/api/admin/sessions', { headers: { Authorization: authHeader() } });
981
- if (!r.ok) throw new Error('HTTP ' + r.status);
982
- const data = await r.json();
983
- _allSessions = data.sessions || [];
984
- renderSidebar(_allSessions);
985
- updateStats(_allSessions);
986
- } catch (e) {
987
- $('session-list').innerHTML = '<div class="no-sessions">Failed to load sessions.</div>';
988
- showToast('Could not fetch sessions: ' + e.message, 'error');
989
- }
990
- }
991
-
992
- function updateStats(sessions) {
993
- const totalMsgs = sessions.reduce((a, s) => a + (s.message_count || 0), 0);
994
- $('stat-total').textContent = sessions.length;
995
- $('stat-msgs').textContent = totalMsgs;
996
- $('stat-today').textContent = todayCount(sessions);
997
- $('total-badge').textContent = sessions.length + ' Sessions';
998
- }
999
-
1000
- function renderSidebar(sessions) {
1001
- if (!sessions.length) {
1002
- $('session-list').innerHTML = '<div class="no-sessions">No sessions yet.</div>';
1003
- return;
1004
- }
1005
- $('session-list').innerHTML = sessions.map(s => `
1006
- <div class="session-item${s.session_id === _activeId ? ' active' : ''}"
1007
- data-id="${s.session_id}">
1008
- <div class="session-item-id" title="${s.user_name || s.session_id}">${s.user_name || shortId(s.session_id)}</div>
1009
- <div class="session-item-meta">
1010
- <span class="session-item-time">${fmtDate(s.last_active)}</span>
1011
- <span class="session-item-count">${s.message_count || 0} msg${s.message_count !== 1 ? 's' : ''}</span>
1012
- </div>
1013
- </div>
1014
- `).join('');
1015
- document.querySelectorAll('.session-item').forEach(el => {
1016
- el.addEventListener('click', () => openSession(el.dataset.id));
1017
- });
1018
- }
1019
-
1020
- $('search-inp').addEventListener('input', e => {
1021
- const q = e.target.value.toLowerCase();
1022
- const filtered = _allSessions.filter(s => s.session_id.toLowerCase().includes(q));
1023
- renderSidebar(filtered);
1024
- });
1025
-
1026
- // ── Session detail ────────────────────────────────────────
1027
- async function openSession(id) {
1028
- _activeId = id;
1029
- document.querySelectorAll('.session-item').forEach(el => {
1030
- el.classList.toggle('active', el.dataset.id === id);
1031
- });
1032
- $('placeholder').style.display = 'none';
1033
- $('chat-view').style.display = 'flex';
1034
- $('view-session-id').textContent = shortId(id);
1035
- $('view-session-user').textContent = '';
1036
- $('view-session-meta').textContent = 'Loading…';
1037
- $('messages-area').innerHTML = '<div class="spinner" style="margin-top:60px"></div>';
1038
-
1039
- try {
1040
- const r = await fetch('/api/admin/sessions/' + encodeURIComponent(id),
1041
- { headers: { Authorization: authHeader() } });
1042
- if (!r.ok) throw new Error('HTTP ' + r.status);
1043
- const data = await r.json();
1044
- renderSession(data);
1045
- } catch (e) {
1046
- $('messages-area').innerHTML = '<div style="color:var(--red);padding:20px">Failed to load session: ' + e.message + '</div>';
1047
- }
1048
- }
1049
-
1050
- function renderSession(data) {
1051
- $('view-session-user').textContent = data.user_name || 'Guest User';
1052
- $('view-session-meta').textContent =
1053
- 'Created: ' + fmtDate(data.created_at) + ' | Last active: ' + fmtDate(data.last_active) +
1054
- ' | ' + (data.message_count || 0) + ' messages';
1055
-
1056
- const msgs = data.messages || [];
1057
- if (!msgs.length) {
1058
- $('messages-area').innerHTML = '<div style="color:var(--text3);text-align:center;margin-top:40px">No messages in this session.</div>';
1059
- return;
1060
- }
1061
- $('messages-area').innerHTML = msgs.map(m => `
1062
- <div class="msg-block">
1063
- <div style="display:flex;align-items:center;gap:8px">
1064
- <span class="msg-label q">
1065
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/></svg>
1066
- User Question
1067
- </span>
1068
- <span class="msg-num">#${m.id || '—'}</span>
1069
- <span class="msg-ts">${fmtDate(m.timestamp)}</span>
1070
- </div>
1071
- <div class="msg-bubble q">${escHtml(m.question)}</div>
1072
- <div class="msg-label a" style="margin-top:6px">
1073
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>
1074
- Assistant Answer
1075
- </div>
1076
- <div class="msg-bubble a">${escHtml(m.answer)}</div>
1077
- </div>
1078
- `).join('');
1079
- }
1080
-
1081
- function escHtml(str) {
1082
- return String(str || '')
1083
- .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
1084
- .replace(/"/g, '&quot;');
1085
- }
1086
-
1087
- // ── Delete session ────────────────────────────────────────
1088
- $('btn-delete-session').addEventListener('click', async () => {
1089
- if (!_activeId) return;
1090
- if (!confirm('Delete session ' + shortId(_activeId) + '? This cannot be undone.')) return;
1091
- try {
1092
- const r = await fetch('/api/admin/sessions/' + encodeURIComponent(_activeId),
1093
- { method: 'DELETE', headers: { Authorization: authHeader() } });
1094
- if (!r.ok) throw new Error('HTTP ' + r.status);
1095
- showToast('Session deleted.', 'success');
1096
- _allSessions = _allSessions.filter(s => s.session_id !== _activeId);
1097
- _activeId = null;
1098
- $('chat-view').style.display = 'none';
1099
- $('placeholder').style.display = 'flex';
1100
- renderSidebar(_allSessions);
1101
- updateStats(_allSessions);
1102
- } catch (e) { showToast('Delete failed: ' + e.message, 'error') }
1103
- });
1104
-
1105
- // ── Exports ───────────────────────────────────────────────
1106
- async function downloadExport(url, filename) {
1107
- try {
1108
- const r = await fetch(url, { headers: { Authorization: authHeader() } });
1109
- if (!r.ok) throw new Error('Export failed');
1110
- const blob = await r.blob();
1111
- const link = document.createElement('a');
1112
- link.href = window.URL.createObjectURL(blob);
1113
- link.download = filename;
1114
- link.click();
1115
- showToast('Export successful', 'success');
1116
- } catch (e) {
1117
- showToast('Export failed: ' + e.message, 'error');
1118
- }
1119
- }
1120
-
1121
- $('btn-export-all-excel').addEventListener('click', () => {
1122
- downloadExport('/api/admin/export/all?format=excel', 'all_chats_export.xlsx');
1123
- });
1124
-
1125
- $('btn-export-all-json').addEventListener('click', () => {
1126
- downloadExport('/api/admin/export/all?format=json', 'all_chats_export.json');
1127
- });
1128
-
1129
- $('btn-export-session-excel').addEventListener('click', () => {
1130
- if (!_activeId) return;
1131
- downloadExport(`/api/admin/export/session/${encodeURIComponent(_activeId)}?format=excel`, `session_${_activeId}.xlsx`);
1132
- });
1133
-
1134
- $('btn-export-session-json').addEventListener('click', () => {
1135
- if (!_activeId) return;
1136
- downloadExport(`/api/admin/export/session/${encodeURIComponent(_activeId)}?format=json`, `session_${_activeId}.json`);
1137
- });
1138
-
1139
- })();
1140
- </script>
1141
- </body>
1142
-
1143
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/api/__init__.py DELETED
File without changes
backup_2026-05-04/app/api/schemas.py DELETED
@@ -1,27 +0,0 @@
1
- from typing import List, Dict, Any
2
-
3
- from pydantic import BaseModel, Field
4
-
5
-
6
- class ChatRequest(BaseModel):
7
- message: str = Field(..., min_length=1)
8
- history: List[Dict[str, str]] = Field(default_factory=list)
9
- session_id: str | None = Field(default=None, description="Unique session identifier for chat persistence")
10
-
11
-
12
- class ChatResponse(BaseModel):
13
- reply: str
14
- retrieved_chunks: List[Dict[str, Any]] = Field(default_factory=list)
15
-
16
-
17
- class HealthResponse(BaseModel):
18
- status: str
19
- docs_loaded: bool
20
- index_ready: bool
21
- chunk_count: int
22
- index_path: str
23
-
24
-
25
- class SessionUpdateNameRequest(BaseModel):
26
- session_id: str
27
- user_name: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/core/__init__.py DELETED
File without changes
backup_2026-05-04/app/core/config.py DELETED
@@ -1,51 +0,0 @@
1
- from functools import lru_cache
2
- from pathlib import Path
3
- from pydantic import Field, model_validator
4
- from pydantic_settings import BaseSettings, SettingsConfigDict
5
-
6
-
7
- class Settings(BaseSettings):
8
- model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
9
-
10
- app_name: str = "Fast RAG Chatbot"
11
- llm_provider: str = Field(default="groq", alias="LLM_PROVIDER")
12
- groq_api_key: str = Field(default="", alias="GROQ_API_KEY")
13
- groq_model: str = Field(default="qwen/qwen3-32b", alias="GROQ_MODEL")
14
- groq_rewrite_model: str = Field(default="llama-3.1-8b-instant", alias="GROQ_REWRITE_MODEL")
15
- hf_api_key: str = Field(default="", alias="HF_API_KEY")
16
- hf_model: str = Field(default="meta-llama/Llama-3.1-8B-Instruct", alias="HF_MODEL")
17
- embedding_model: str = Field(default="BAAI/bge-small-en-v1.5", alias="EMBEDDING_MODEL")
18
- reranker_model: str = Field(default="BAAI/bge-reranker-base", alias="RERANKER_MODEL")
19
- docs_dir: Path = Field(default=Path("docs"), alias="DOCS_DIR")
20
- @model_validator(mode='after')
21
- def set_persistent_paths(self) -> 'Settings':
22
- # If we are on Hugging Face (detected by /data volume),
23
- # force use of /data for persistence regardless of other settings.
24
- if Path("/data").exists():
25
- self.index_dir = Path("/data/index")
26
- self.sessions_dir = Path("/data/sessions")
27
- return self
28
-
29
- index_dir: Path = Field(default=Path("data/index"), alias="INDEX_DIR")
30
- sessions_dir: Path = Field(default=Path("data/sessions"), alias="SESSIONS_DIR")
31
- chunk_size_tokens: int = 350 # Reduced for TPM safety
32
- chunk_overlap_tokens: int = 80
33
- top_k: int = Field(default=20, alias="TOP_K")
34
- max_context_chunks: int = 10 # Increased to 10 to ensure all list items (like leaves) are captured
35
- request_timeout_s: float = 20.0
36
- cors_allow_origins: str = Field(default="*", alias="CORS_ALLOW_ORIGINS")
37
- api_key: str = Field(default="", alias="API_KEY")
38
- rate_limit_requests: int = Field(default=60, alias="RATE_LIMIT_REQUESTS")
39
- rate_limit_window_seconds: int = Field(default=60, alias="RATE_LIMIT_WINDOW_SECONDS")
40
-
41
- # SMTP Settings for OTP
42
- smtp_server: str = Field(default="smtp.gmail.com", alias="SMTP_SERVER")
43
- smtp_port: int = Field(default=465, alias="SMTP_PORT")
44
- smtp_user: str = Field(default="", alias="SMTP_USER")
45
- smtp_pass: str = Field(default="", alias="SMTP_PASS")
46
- admin_email: str = Field(default="randomjoedown@gmail.com", alias="ADMIN_EMAIL")
47
-
48
-
49
- @lru_cache
50
- def get_settings() -> Settings:
51
- return Settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/main.py DELETED
@@ -1,196 +0,0 @@
1
- import logging
2
-
3
- from fastapi import FastAPI, HTTPException, Header, Request
4
- from fastapi.responses import RedirectResponse
5
- from fastapi.middleware.cors import CORSMiddleware
6
- from fastapi.staticfiles import StaticFiles
7
- import os
8
-
9
- from app.api.schemas import ChatRequest, ChatResponse, HealthResponse, SessionUpdateNameRequest
10
- from app.core.config import get_settings
11
- from app.services.embeddings import EmbeddingService
12
- from app.services.llm import LLMService
13
- from app.services.rag_pipeline import RAGPipeline
14
- from app.services.rate_limiter import InMemoryRateLimiter
15
- from app.services.vector_store import FaissVectorStore
16
- from app.services.reranker import RerankerService
17
- from app.ui_gradio import demo
18
- from app.admin.router import admin_router
19
- from app.services import session_store as _ss
20
- import asyncio
21
- import gradio as gr
22
-
23
- logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s - %(message)s")
24
- logger = logging.getLogger(__name__)
25
-
26
- settings = get_settings()
27
- embedding_service = EmbeddingService(settings.embedding_model)
28
- vector_store = FaissVectorStore(
29
- embedding_service=embedding_service,
30
- docs_dir=settings.docs_dir,
31
- index_dir=settings.index_dir,
32
- chunk_size_tokens=settings.chunk_size_tokens,
33
- chunk_overlap_tokens=settings.chunk_overlap_tokens,
34
- )
35
- llm_service = LLMService(
36
- provider=settings.llm_provider,
37
- groq_api_key=settings.groq_api_key,
38
- groq_model=settings.groq_model,
39
- groq_rewrite_model=settings.groq_rewrite_model,
40
- hf_api_key=settings.hf_api_key,
41
- hf_model=settings.hf_model,
42
- timeout_s=settings.request_timeout_s,
43
- )
44
- reranker_service = RerankerService(settings.reranker_model)
45
- pipeline = RAGPipeline(
46
- vector_store=vector_store,
47
- llm_service=llm_service,
48
- reranker=reranker_service,
49
- top_k=settings.top_k,
50
- max_context_chunks=settings.max_context_chunks
51
- )
52
-
53
- # ── FastAPI App ───────────────────────────────────────────────────────────────
54
- fastapi_app = FastAPI(title=settings.app_name)
55
-
56
- allow_origins = [o.strip() for o in settings.cors_allow_origins.split(",") if o.strip()]
57
- fastapi_app.add_middleware(
58
- CORSMiddleware,
59
- allow_origins=["*"], # Explicitly allow all for testing
60
- allow_credentials=True,
61
- allow_methods=["*"],
62
- allow_headers=["*"],
63
- )
64
-
65
- rate_limiter = InMemoryRateLimiter(settings.rate_limit_requests, settings.rate_limit_window_seconds)
66
-
67
- # ── Admin Panel (mounted on FastAPI before Gradio wrapping) ───────────────────
68
- fastapi_app.include_router(admin_router)
69
-
70
- # ── Static Files ──────────────────────────────────────────────────────────────
71
- static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static")
72
- if os.path.exists(static_dir):
73
- fastapi_app.mount("/static", StaticFiles(directory=static_dir), name="static")
74
-
75
- @fastapi_app.get("/widget", include_in_schema=False)
76
- async def get_widget():
77
- """Serves the chat widget HTML with explicit CORS headers."""
78
- from fastapi.responses import FileResponse
79
- path = os.path.join(static_dir, "addon.html")
80
- if not os.path.exists(path):
81
- raise HTTPException(status_code=404, detail="Widget not found")
82
- return FileResponse(
83
- path,
84
- headers={
85
- "Access-Control-Allow-Origin": "*",
86
- "Cache-Control": "no-cache, no-store, must-revalidate"
87
- }
88
- )
89
-
90
-
91
- @fastapi_app.on_event("startup")
92
- def startup_event() -> None:
93
- logger.info("─── RAG SERVICE STARTUP ───")
94
- logger.info("Index Directory: %s", settings.index_dir)
95
- logger.info("Sessions Directory: %s", settings.sessions_dir)
96
-
97
- # Verify persistence mount
98
- if str(settings.index_dir).startswith("/data"):
99
- if os.path.exists("/data"):
100
- logger.info("✅ Persistent storage /data detected and in use.")
101
- else:
102
- logger.warning("⚠️ /data requested but NOT found on filesystem!")
103
- else:
104
- logger.warning("⚠️ Using ephemeral storage (data will be lost on rebuild).")
105
-
106
- logger.info("Loading/building FAISS index...")
107
- vector_store.build_or_load()
108
- logger.info("RAG service started with %s chunks", len(vector_store.metadata))
109
-
110
-
111
- @fastapi_app.get("/", include_in_schema=False)
112
- async def root():
113
- """Prevent direct browser access to the root URL."""
114
- raise HTTPException(
115
- status_code=403,
116
- detail="Direct access to this resource is restricted. Please use the authorized interface."
117
- )
118
-
119
-
120
- @fastapi_app.get("/health", response_model=HealthResponse)
121
- def health() -> HealthResponse:
122
- h = vector_store.health()
123
- return HealthResponse(status="ok", **h)
124
-
125
-
126
- @fastapi_app.post("/api/chat", response_model=ChatResponse)
127
- async def chat(
128
- payload: ChatRequest,
129
- request: Request,
130
- x_api_key: str | None = Header(default=None),
131
- ) -> ChatResponse:
132
- if not payload.message.strip():
133
- raise HTTPException(status_code=400, detail="message must not be empty")
134
- if settings.api_key and x_api_key != settings.api_key:
135
- raise HTTPException(status_code=401, detail="unauthorized")
136
-
137
- client_ip = request.client.host if request.client else "unknown"
138
- if not rate_limiter.allow(client_ip):
139
- raise HTTPException(status_code=429, detail="rate limit exceeded")
140
-
141
- logger.info("Incoming chat request: msg='%s', sid='%s'", payload.message[:30], payload.session_id)
142
-
143
- # Fetch user_name if available for personalization
144
- user_name = None
145
- if payload.session_id:
146
- sess_data = await _ss.get_session(payload.session_id)
147
- user_name = sess_data.get("user_name")
148
-
149
- try:
150
- result = await pipeline.chat(payload.message, payload.history, user_name=user_name)
151
-
152
- # ── Fire-and-forget: persist to session store in background ──
153
- if payload.session_id:
154
- asyncio.create_task(_ss.save_message(payload.session_id, payload.message, result["reply"]))
155
-
156
- return ChatResponse(**result)
157
- except Exception as e:
158
- import httpx
159
- import traceback
160
- logger.error(f"Error in chat endpoint: {str(e)}\n{traceback.format_exc()}")
161
-
162
- error_msg = "⚠️ Oops! Something went wrong."
163
- if isinstance(e, httpx.HTTPStatusError):
164
- if e.response.status_code == 429:
165
- error_msg = "⚠️ Rate limit reached. Please slow down a bit!"
166
- else:
167
- error_msg = "⚠️ I encountered an error. Please try again in a few seconds."
168
-
169
- return ChatResponse(reply=error_msg, retrieved_chunks=[])
170
-
171
-
172
- @fastapi_app.post("/api/session/update-name")
173
- async def update_session_user_name(payload: SessionUpdateNameRequest):
174
- """Updates the user name and renames the session file to match the name."""
175
- new_id = await _ss.rename_session(payload.session_id, payload.user_name)
176
- return {"status": "ok", "new_session_id": new_id}
177
-
178
-
179
- @fastapi_app.get("/api/session/history/{session_id}")
180
- async def get_session_history(session_id: str):
181
- """Returns the chat history for a session formatted for the frontend."""
182
- data = await _ss.get_session(session_id)
183
- if not data:
184
- return {"history": []}
185
-
186
- history = []
187
- for msg in data.get("messages", []):
188
- history.append({"role": "user", "content": msg["question"]})
189
- history.append({"role": "assistant", "content": msg["answer"]})
190
- return {"history": history, "user_name": data.get("user_name", "")}
191
-
192
-
193
- # ── Mount Gradio at /ui — MUST be last, wraps the FastAPI app ─────────────────
194
- # All FastAPI routes (including /admin) are already registered above and are
195
- # preserved inside the Gradio-wrapped Starlette app.
196
- app = gr.mount_gradio_app(fastapi_app, demo, path="/ui")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/__init__.py DELETED
File without changes
backup_2026-05-04/app/services/chunker.py DELETED
@@ -1,39 +0,0 @@
1
- from typing import List, Dict
2
-
3
-
4
- def _tokenize(text: str) -> List[str]:
5
- return text.split()
6
-
7
-
8
- def chunk_documents(
9
- documents: List[Dict[str, str]],
10
- chunk_size_tokens: int = 420,
11
- chunk_overlap_tokens: int = 80,
12
- ) -> List[Dict[str, str]]:
13
- chunks: List[Dict[str, str]] = []
14
- step = max(1, chunk_size_tokens - chunk_overlap_tokens)
15
-
16
- for doc in documents:
17
- tokens = _tokenize(doc["text"])
18
- if not tokens:
19
- continue
20
-
21
- i = 0
22
- chunk_id = 0
23
- while i < len(tokens):
24
- window = tokens[i : i + chunk_size_tokens]
25
- if not window:
26
- break
27
-
28
- chunk_text = " ".join(window)
29
- chunks.append(
30
- {
31
- "id": f"{doc['source']}::chunk_{chunk_id}",
32
- "source": doc["source"],
33
- "text": chunk_text,
34
- }
35
- )
36
- chunk_id += 1
37
- i += step
38
-
39
- return chunks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/document_loader.py DELETED
@@ -1,37 +0,0 @@
1
- from pathlib import Path
2
- from typing import List
3
-
4
- from pypdf import PdfReader
5
-
6
-
7
- def _clean_text(text: str) -> str:
8
- lines = [line.strip() for line in text.splitlines() if line.strip()]
9
- merged = " ".join(lines)
10
- return " ".join(merged.split())
11
-
12
-
13
- def load_documents(docs_dir: Path) -> List[dict]:
14
- docs: List[dict] = []
15
- if not docs_dir.exists():
16
- return docs
17
-
18
- for file_path in sorted(docs_dir.rglob("*")):
19
- if not file_path.is_file():
20
- continue
21
-
22
- suffix = file_path.suffix.lower()
23
- if suffix == ".txt":
24
- raw = file_path.read_text(encoding="utf-8", errors="ignore")
25
- cleaned = _clean_text(raw)
26
- if cleaned:
27
- docs.append({"source": str(file_path), "text": cleaned})
28
- elif suffix == ".pdf":
29
- reader = PdfReader(str(file_path))
30
- pages = []
31
- for page in reader.pages:
32
- pages.append(page.extract_text() or "")
33
- cleaned = _clean_text("\n".join(pages))
34
- if cleaned:
35
- docs.append({"source": str(file_path), "text": cleaned})
36
-
37
- return docs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/embeddings.py DELETED
@@ -1,34 +0,0 @@
1
- from typing import List
2
- from functools import lru_cache
3
-
4
- import numpy as np
5
- from sentence_transformers import SentenceTransformer
6
-
7
-
8
- class EmbeddingService:
9
- def __init__(self, model_name: str) -> None:
10
- self.model = SentenceTransformer(model_name)
11
-
12
- def encode(self, texts: List[str]) -> np.ndarray:
13
- vectors = self.model.encode(
14
- texts,
15
- normalize_embeddings=True,
16
- show_progress_bar=False,
17
- convert_to_numpy=True,
18
- )
19
- return np.asarray(vectors, dtype=np.float32)
20
-
21
- @lru_cache(maxsize=2048)
22
- def encode_query_cached(self, text: str) -> bytes:
23
- vec = self.model.encode(
24
- [text],
25
- normalize_embeddings=True,
26
- show_progress_bar=False,
27
- convert_to_numpy=True,
28
- )
29
- arr = np.asarray(vec, dtype=np.float32)
30
- return arr.tobytes()
31
-
32
- def encode_query(self, text: str) -> np.ndarray:
33
- buf = self.encode_query_cached(text)
34
- return np.frombuffer(buf, dtype=np.float32).reshape(1, -1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/llm.py DELETED
@@ -1,264 +0,0 @@
1
- from typing import List, Dict
2
- import httpx
3
- import re
4
-
5
- # ═══════════════════════════════════════════════════════════════════════
6
- # MASTER SYSTEM PROMPT — Martechsol HR Assistant
7
- # Intelligence: Understand Intent → Retrieve Facts → Respond Precisely
8
- # ═══════════════════════════════════════════════════════════════════════
9
- SYSTEM_PROMPT = """You are the Martechsol HR Assistant — intelligent, precise, and formal.
10
-
11
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
- STEP 1 — UNDERSTAND THE INTENT
13
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14
- Read the question carefully. Identify the SINGLE core topic being asked. Apply intelligent defaults:
15
- • "timing" / "timings" (no context) → office working hours ONLY — not payment or any other timing
16
- • "leaves" / "leave" (no context) → leave names + day counts ONLY — NOT leave policies or eligibility
17
- • "paid leaves" / "all leaves" → enumerate EVERY leave type with its name and count
18
- • "salary" / "pay" (no context) → salary structure or amount — NOT payment date unless explicitly asked
19
- • "benefits" / "perks" / "allowances" → list EVERY benefit with its name and value
20
- • "terminate" / "termination" → resignation/termination procedure — NOT general policies
21
- If a question has an obvious workplace context, always default to the most common interpretation.
22
-
23
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
24
- STEP 2 — STRICT SCOPE DISCIPLINE
25
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
26
- Answer ONLY what was asked. NEVER expand into:
27
- • Policies, approval processes, eligibility rules, or consequences — unless user asks for policy/process
28
- • Related topics the user did not mention
29
- • Broad overviews when a specific fact was requested
30
- • Context that wasn't in the question
31
-
32
- Scope examples:
33
- "How many sick leaves?" → ONE number. Not the sick leave policy.
34
- "What are office timings?" → ONE sentence with time range. Not break times or exceptions.
35
- "What are paid leaves?" → Complete list of all paid leave types + counts. Not rules for taking them.
36
- "Tell me about maternity leave" → Count + brief key fact. Not full policies unless asked.
37
-
38
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39
- STEP 3 — CHOOSE THE RIGHT FORMAT
40
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
41
-
42
- FORMAT A — SINGLE FACT (default for most questions)
43
- When: One direct answer needed (timings, a specific leave count, a single number/date)
44
- Rule: ONE complete sentence. Maximum 25–30 words. Never cut mid-sentence.
45
- Example: Office hours are <b>9:00 AM to 6:00 PM</b>, Monday to Saturday.
46
-
47
- FORMAT B — EXHAUSTIVE LIST
48
- When: User asks for ALL items in a category ("all leaves", "all benefits", "list all X", "paid leaves")
49
- Rule:
50
- • Include EVERY single item found — omitting even one is FORBIDDEN
51
- • One item per line: <b>Item Name:</b> value<br>
52
- • No intro sentence, no closing sentence, no extra commentary
53
- • Continue until ALL items are listed
54
- Example:
55
- <b>Casual Leave:</b> 10 days<br>
56
- <b>Sick Leave:</b> 10 days<br>
57
- <b>Annual Leave:</b> 14 days<br>
58
- <b>Maternity Leave:</b> 90 days<br>
59
- <b>Paternity Leave:</b> 3 days<br>
60
- <b>Hajj Leave:</b> 30 days<br>
61
- ...(list every item — do NOT stop early)
62
-
63
- FORMAT C — BRIEF EXPLANATION
64
- When: User asks HOW something works, or asks for a process/procedure
65
- Rule: Maximum 3 bullet points. Each bullet = one complete, factual sentence. No filler words.
66
-
67
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
68
- STRICT QUALITY RULES
69
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
70
- ✓ ZERO hallucination — every fact must exist in Expert Data only. No guessing.
71
- ✓ If not in Expert Data → reply exactly: "I don't have that information."
72
- ✓ Never cut a sentence mid-way — always complete every sentence fully
73
- ✓ NEVER mention: "document", "handbook", "manual", "policy file", or any source reference
74
- ✓ Use <b>bold</b> for names, numbers, dates, leave types, and all key terms
75
- ✓ Use <br> between list items for clean vertical spacing
76
- ✓ Tone: formal, warm, and professional — never robotic, never chatty
77
- ✓ Do NOT add greetings, closings, or "Is there anything else?" type phrases"""
78
-
79
-
80
- def _build_context(chunks: List[Dict[str, str]], max_words: int = 1500) -> str:
81
- """Combines retrieved chunks into a clean context string, capped at max_words.
82
- Prevents TPM spikes when chunks are unexpectedly large."""
83
- if not chunks:
84
- return ""
85
- parts = []
86
- total_words = 0
87
- for c in chunks:
88
- text = c['text']
89
- word_count = len(text.split())
90
- if total_words + word_count > max_words:
91
- # Add a trimmed version of this chunk if possible
92
- remaining = max_words - total_words
93
- if remaining > 30: # Only worth adding if meaningful content remains
94
- trimmed_words = text.split()[:remaining]
95
- parts.append(" ".join(trimmed_words))
96
- break
97
- parts.append(text)
98
- total_words += word_count
99
- return "\n\n".join(parts)
100
-
101
-
102
- class LLMService:
103
- def __init__(
104
- self,
105
- provider: str,
106
- groq_api_key: str,
107
- groq_model: str,
108
- groq_rewrite_model: str,
109
- hf_api_key: str,
110
- hf_model: str,
111
- timeout_s: float = 20.0,
112
- ) -> None:
113
- self.provider = provider.lower().strip()
114
- self.groq_api_key = groq_api_key
115
- self.groq_model = groq_model
116
- self.groq_rewrite_model = groq_rewrite_model
117
- self.hf_api_key = hf_api_key
118
- self.hf_model = hf_model
119
- self.timeout_s = timeout_s
120
-
121
- async def generate_multi_queries(self, query: str, history: List[Dict[str, str]]) -> List[str]:
122
- """Generates multiple search queries to capture broader context from the document."""
123
- prompt = f"""You are an intelligent HR search optimizer. A user asked: "{query}"
124
-
125
- STEP 1 — UNDERSTAND THE INTENT:
126
- What is the user ACTUALLY asking about? Apply workplace common sense:
127
- - "timing" / "timings" (no qualifier) = office working hours, workday schedule
128
- - "leaves" / "paid leaves" / "all leaves" = all leave types available to employees
129
- - "salary" / "pay" = salary structure or amount
130
- - "benefits" / "allowances" = employee benefits and perks
131
- - "terminate" / "resign" = termination or resignation process
132
-
133
- STEP 2 — GENERATE 3 TARGETED SEARCH QUERIES:
134
- Write 3 diverse search queries to retrieve ALL relevant information from an employee handbook.
135
- For the identified intent, cover synonyms, related terms, and sub-categories:
136
- - For office timings → search: working hours, office schedule, workday timing, shift hours
137
- - For leaves → Query 1 MUST be a keyword dump: "casual leave sick leave annual leave maternity paternity hajj bereavement study unauthorized"
138
- - For salary → cover: salary structure, payroll, compensation, increments, deductions
139
- - For benefits → cover: allowances, perks, medical, bonuses, reimbursements
140
-
141
- Output ONLY 3 queries, one per line. No numbers, no bullets, no explanations.
142
-
143
- Queries:"""
144
-
145
- try:
146
- resp = await self._call_groq(
147
- prompt, [],
148
- "You output only search queries, one per line. No explanations, no numbering.",
149
- model_override=self.groq_rewrite_model
150
- )
151
- queries = [
152
- q.strip().lstrip('0123456789.-) ').strip()
153
- for q in resp.split("\n")
154
- if q.strip() and len(q.strip()) > 3
155
- ]
156
- # Always include original query
157
- if query not in queries:
158
- queries.append(query)
159
- return queries[:4]
160
- except Exception:
161
- return [query]
162
-
163
- async def answer(
164
- self,
165
- question: str,
166
- chunks: List[Dict[str, str]],
167
- history: List[Dict[str, str]],
168
- user_name: str = None
169
- ) -> str:
170
- # Keep only last 4 messages (2 turns) for TPM safety
171
- pruned_history = history[-4:]
172
- context = _build_context(chunks)
173
-
174
- # Build system message
175
- system_msg = SYSTEM_PROMPT
176
- if user_name and user_name not in ("default name", "", None):
177
- system_msg += f"\n\nYou are speaking with {user_name}."
178
-
179
- # Build user prompt — clean and direct
180
- if not context:
181
- user_prompt = question
182
- else:
183
- user_prompt = f"Expert Data:\n{context}\n\nQuestion: {question}"
184
-
185
- return await self._call_groq(user_prompt, pruned_history, system_msg)
186
-
187
- async def _call_groq(
188
- self,
189
- user_prompt: str,
190
- history: List[Dict[str, str]],
191
- system_msg: str,
192
- model_override: str = None
193
- ) -> str:
194
- if not self.groq_api_key:
195
- return "Expert access required."
196
-
197
- url = "https://api.groq.com/openai/v1/chat/completions"
198
- headers = {
199
- "Authorization": f"Bearer {self.groq_api_key}",
200
- "Content-Type": "application/json"
201
- }
202
-
203
- messages = [{"role": "system", "content": system_msg}]
204
- for msg in history:
205
- messages.append({"role": msg["role"], "content": msg["content"]})
206
- messages.append({"role": "user", "content": user_prompt})
207
-
208
- target_model = model_override or self.groq_model
209
-
210
- # Qwen3 and DeepSeek-R1 are thinking models
211
- is_thinking_model = any(k in target_model.lower() for k in ["deepseek", "qwen3", "qwen/qwen3"])
212
- temp = 0.6 if is_thinking_model else 0.0
213
-
214
- # For Qwen3: suppress thinking mode on answer calls to reduce latency & TPM usage.
215
- # /no_think is Qwen3's built-in signal to skip chain-of-thought reasoning.
216
- # We ONLY suppress for the main answer model, not the rewrite model.
217
- if is_thinking_model and model_override is None:
218
- messages[-1]["content"] = "/no_think\n\n" + messages[-1]["content"]
219
-
220
- payload = {
221
- "model": target_model,
222
- "temperature": temp,
223
- "max_tokens": 512, # Enough for complete listings with HTML, well within TPM limits
224
- "messages": messages,
225
- }
226
-
227
- async with httpx.AsyncClient(timeout=self.timeout_s) as client:
228
- resp = await client.post(url, headers=headers, json=payload)
229
- if resp.status_code >= 400:
230
- import logging
231
- logging.getLogger(__name__).error(
232
- f"Groq API Error {resp.status_code}: {resp.text}"
233
- )
234
- resp.raise_for_status()
235
- data = resp.json()
236
- content = data["choices"][0]["message"]["content"].strip()
237
-
238
- # ── Post-Processing: Strip all internal reasoning artifacts ──
239
-
240
- # 1. Strip <think>...</think> blocks (Qwen3, DeepSeek-R1)
241
- content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL).strip()
242
-
243
- # 2. Strip leading conversational filler (single line only, not entire content)
244
- content = re.sub(
245
- r'^(Okay[,.]?\s*|Alright[,.]?\s*|Sure[,.]?\s*|Let\'s see[,.]?\s*|'
246
- r'Based on (?:the )?(?:provided |available )?(?:data|information|context)[,.]?\s*|'
247
- r'According to (?:the )?(?:provided |available )?(?:data|information)[,.]?\s*)',
248
- '', content, flags=re.IGNORECASE
249
- ).strip()
250
-
251
- # 3. Remove lines that are pure internal self-talk (only if they appear alone at start)
252
- lines = content.split('\n')
253
- filtered = []
254
- for i, line in enumerate(lines):
255
- is_self_talk = bool(re.match(
256
- r'^\s*(I need to|I will|I should|I\'m going to|Let me|Now I|First,? I|'
257
- r'I\'ll|The user is asking|The question is about)',
258
- line, re.IGNORECASE
259
- ))
260
- if not is_self_talk:
261
- filtered.append(line)
262
- content = '\n'.join(filtered).strip()
263
-
264
- return content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/rag_pipeline.py DELETED
@@ -1,98 +0,0 @@
1
- import logging
2
- from typing import Dict, List
3
-
4
- from app.services.llm import LLMService
5
- from app.services.vector_store import FaissVectorStore
6
- from app.services.reranker import RerankerService
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- # RRF scores are small (e.g. 0.016–0.033), so threshold must be very low
11
- RELEVANCE_THRESHOLD = 0.01
12
- # Cross-encoder logit > 0 means > 50% relevance probability
13
- RERANK_THRESHOLD = 0.0
14
- # If ALL chunks fail rerank threshold, fall back to this many top chunks
15
- RERANK_FALLBACK_N = 2
16
-
17
-
18
- class RAGPipeline:
19
- def __init__(
20
- self,
21
- vector_store: FaissVectorStore,
22
- llm_service: LLMService,
23
- reranker: RerankerService,
24
- top_k: int,
25
- max_context_chunks: int
26
- ) -> None:
27
- self.vector_store = vector_store
28
- self.llm_service = llm_service
29
- self.reranker = reranker
30
- self.top_k = top_k
31
- self.max_context_chunks = max_context_chunks
32
-
33
- async def chat(self, message: str, history: List[Dict[str, str]], user_name: str = None) -> Dict[str, object]:
34
- # ── Step 1: Generate multiple search queries for broad coverage ──
35
- queries = await self.llm_service.generate_multi_queries(message, history)
36
- logger.info("Generated %d queries for: '%s' → %s", len(queries), message[:40], queries)
37
-
38
- # ── Step 2: Collect unique chunks across all queries ──
39
- seen_ids = set()
40
- all_retrieved = []
41
-
42
- for q in queries:
43
- query_chunks = self.vector_store.search(q, top_k=self.top_k)
44
- for chunk in query_chunks:
45
- if chunk["id"] not in seen_ids:
46
- all_retrieved.append(chunk)
47
- seen_ids.add(chunk["id"])
48
-
49
- # ── Step 3: Initial relevance filter ──
50
- initial_chunks = [c for c in all_retrieved if c["score"] >= RELEVANCE_THRESHOLD]
51
-
52
- if not initial_chunks:
53
- logger.info("No relevant chunks found for: '%s' — returning no-info response", message)
54
- # Pass empty chunks; LLM is instructed to say "I don't have that information"
55
- reply = await self.llm_service.answer(
56
- question=message,
57
- chunks=[],
58
- history=history,
59
- user_name=user_name
60
- )
61
- return {"reply": reply, "retrieved_chunks": []}
62
-
63
- # ── Step 4: Deep reranking via Cross-Encoder ──
64
- # Enrihc the reranker query with the LLM's expanded search terms
65
- rerank_query = message
66
- if len(queries) > 0 and queries[0] != message:
67
- rerank_query = f"{message} {queries[0]}"
68
-
69
- reranked_chunks = self.reranker.rerank(rerank_query, initial_chunks, top_n=self.max_context_chunks)
70
-
71
- # Filter by rerank score threshold
72
- final_chunks = [c for c in reranked_chunks if c.get("rerank_score", 0) > RERANK_THRESHOLD]
73
-
74
- # ── Smart Fallback: if ALL chunks fail the threshold, use the top N anyway ──
75
- # This prevents the bot from saying "I don't have info" when content WAS retrieved
76
- # but the cross-encoder wasn't confident enough (e.g. paraphrased queries)
77
- if not final_chunks and reranked_chunks:
78
- final_chunks = reranked_chunks[:RERANK_FALLBACK_N]
79
- logger.info(
80
- "Rerank fallback activated — all chunks below threshold (best score=%.3f), using top %d",
81
- reranked_chunks[0].get("rerank_score", 0),
82
- len(final_chunks)
83
- )
84
-
85
- logger.info(
86
- "Pipeline: retrieved=%d → relevance_filtered=%d → reranked=%d → final=%d (best_score=%.3f)",
87
- len(all_retrieved), len(initial_chunks), len(reranked_chunks), len(final_chunks),
88
- reranked_chunks[0].get("rerank_score", 0) if reranked_chunks else 0.0
89
- )
90
-
91
- # ── Step 5: Generate answer with top-ranked context ──
92
- reply = await self.llm_service.answer(
93
- question=message,
94
- chunks=final_chunks,
95
- history=history,
96
- user_name=user_name
97
- )
98
- return {"reply": reply, "retrieved_chunks": final_chunks}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/rate_limiter.py DELETED
@@ -1,24 +0,0 @@
1
- import time
2
- from collections import defaultdict, deque
3
- from typing import Deque, Dict
4
-
5
-
6
- class InMemoryRateLimiter:
7
- def __init__(self, max_requests: int, window_seconds: int) -> None:
8
- self.max_requests = max_requests
9
- self.window_seconds = window_seconds
10
- self._hits: Dict[str, Deque[float]] = defaultdict(deque)
11
-
12
- def allow(self, key: str) -> bool:
13
- now = time.time()
14
- window_start = now - self.window_seconds
15
- q = self._hits[key]
16
-
17
- while q and q[0] < window_start:
18
- q.popleft()
19
-
20
- if len(q) >= self.max_requests:
21
- return False
22
-
23
- q.append(now)
24
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/reranker.py DELETED
@@ -1,26 +0,0 @@
1
- from typing import List, Dict
2
- import numpy as np
3
- from sentence_transformers import CrossEncoder
4
-
5
- class RerankerService:
6
- def __init__(self, model_name: str) -> None:
7
- self.model = CrossEncoder(model_name)
8
-
9
- def rerank(self, query: str, chunks: List[Dict[str, str]], top_n: int = 4) -> List[Dict[str, str]]:
10
- if not chunks:
11
- return []
12
-
13
- # Prepare pairs for cross-encoder
14
- pairs = [[query, chunk["text"]] for chunk in chunks]
15
-
16
- # Get scores
17
- scores = self.model.predict(pairs)
18
-
19
- # Add scores to chunks and sort
20
- for i, chunk in enumerate(chunks):
21
- chunk["rerank_score"] = float(scores[i])
22
-
23
- # Sort by rerank_score descending
24
- sorted_chunks = sorted(chunks, key=lambda x: x["rerank_score"], reverse=True)
25
-
26
- return sorted_chunks[:top_n]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/session_store.py DELETED
@@ -1,284 +0,0 @@
1
- """
2
- session_store.py
3
- ────────────────
4
- Async, fire-and-forget JSON-based chat session storage.
5
- Each browser session gets its own JSON file in data/sessions/.
6
-
7
- Rules:
8
- • NEVER raises exceptions that propagate upward — any failure is silently logged.
9
- • ALL disk I/O is done with asyncio to avoid blocking the event loop.
10
- • Thread-safe: uses an asyncio.Lock per session_id to avoid race conditions.
11
- """
12
-
13
- import asyncio
14
- import json
15
- import logging
16
- import os
17
- from datetime import datetime, timezone
18
- from pathlib import Path
19
- from typing import Dict
20
-
21
- from app.core.config import get_settings
22
- logger = logging.getLogger(__name__)
23
- settings = get_settings()
24
-
25
- # ── Directory where session JSON files are stored ───────────────────────────
26
- def _get_sessions_dir() -> Path:
27
- """Returns the current sessions directory from settings."""
28
- return settings.sessions_dir
29
-
30
-
31
- # ── Per-session locks to prevent concurrent write corruption ─────────────────
32
- _session_locks: Dict[str, asyncio.Lock] = {}
33
- _locks_meta_lock = asyncio.Lock() # protects the _session_locks dict itself
34
-
35
-
36
- def _get_session_path(session_id: str) -> Path:
37
- """Returns the absolute path for a session's JSON file."""
38
- return _get_sessions_dir() / f"{session_id}.json"
39
-
40
-
41
- def _now_iso() -> str:
42
- """Returns the current UTC time as an ISO-8601 string."""
43
- return datetime.now(timezone.utc).isoformat()
44
-
45
-
46
- async def _get_lock(session_id: str) -> asyncio.Lock:
47
- """Gets or creates a per-session asyncio.Lock in a thread-safe manner."""
48
- async with _locks_meta_lock:
49
- if session_id not in _session_locks:
50
- _session_locks[session_id] = asyncio.Lock()
51
- return _session_locks[session_id]
52
-
53
-
54
- def _ensure_sessions_dir() -> None:
55
- """Creates the sessions directory if it does not exist. Sync, called once."""
56
- try:
57
- _get_sessions_dir().mkdir(parents=True, exist_ok=True)
58
- except OSError as exc:
59
- logger.error("session_store: cannot create sessions dir: %s", exc)
60
-
61
-
62
- # Call once at import time so the directory is always ready.
63
- _ensure_sessions_dir()
64
-
65
-
66
- async def ensure_session(session_id: str) -> None:
67
- """
68
- Creates the session JSON file if it doesn't exist yet.
69
- Safe to call on every page load — does nothing if file exists.
70
- """
71
- path = _get_session_path(session_id)
72
- if path.exists():
73
- return
74
- lock = await _get_lock(session_id)
75
- async with lock:
76
- # Double-check after acquiring lock
77
- if path.exists():
78
- return
79
- skeleton = {
80
- "session_id": session_id,
81
- "user_name": "default name",
82
- "created_at": _now_iso(),
83
- "last_active": _now_iso(),
84
- "message_count": 0,
85
- "messages": [],
86
- }
87
- try:
88
- await asyncio.to_thread(_write_json, path, skeleton)
89
- logger.info("session_store: created session %s", session_id)
90
- except Exception as exc:
91
- logger.error("session_store: failed to create session %s: %s", session_id, exc)
92
-
93
-
94
- async def save_message(session_id: str, question: str, answer: str) -> None:
95
- """
96
- Appends a Question+Answer pair with timestamps to the session's JSON file.
97
- Fire-and-forget safe — caller should wrap in asyncio.create_task().
98
- """
99
- path = _get_session_path(session_id)
100
- logger.info("session_store: save_message called for session_id=%s, path=%s", session_id, path)
101
- lock = await _get_lock(session_id)
102
- async with lock:
103
- try:
104
- # Load current data
105
- if path.exists():
106
- data = await asyncio.to_thread(_read_json, path)
107
- else:
108
- # Session file missing — recreate it gracefully
109
- data = {
110
- "session_id": session_id,
111
- "user_name": "default name",
112
- "created_at": _now_iso(),
113
- "last_active": _now_iso(),
114
- "message_count": 0,
115
- "messages": [],
116
- }
117
-
118
- timestamp = _now_iso()
119
- data["messages"].append({
120
- "id": data["message_count"] + 1,
121
- "timestamp": timestamp,
122
- "question": question,
123
- "answer": answer,
124
- })
125
- data["message_count"] += 1
126
- data["last_active"] = timestamp
127
-
128
- await asyncio.to_thread(_write_json, path, data)
129
- logger.info("session_store: successfully saved message to %s", path)
130
- except Exception as exc:
131
- logger.error(
132
- "session_store: failed to save message for session %s: %s",
133
- session_id,
134
- exc,
135
- )
136
-
137
-
138
- async def get_all_sessions() -> list:
139
- """
140
- Returns a list of all session summaries (metadata only, no full messages).
141
- Used by the Admin Panel sidebar.
142
- """
143
- try:
144
- files = await asyncio.to_thread(_list_json_files)
145
- sessions = []
146
- for fpath in files:
147
- try:
148
- data = await asyncio.to_thread(_read_json, fpath)
149
- sessions.append({
150
- "session_id": data.get("session_id", fpath.stem),
151
- "user_name": data.get("user_name", ""),
152
- "created_at": data.get("created_at", ""),
153
- "last_active": data.get("last_active", ""),
154
- "message_count": data.get("message_count", 0),
155
- })
156
- except Exception as exc:
157
- logger.warning("session_store: could not read %s: %s", fpath, exc)
158
- # Sort newest first
159
- sessions.sort(key=lambda s: s.get("last_active", ""), reverse=True)
160
- return sessions
161
- except Exception as exc:
162
- logger.error("session_store: get_all_sessions failed: %s", exc)
163
- return []
164
-
165
-
166
- async def get_session(session_id: str) -> dict:
167
- """
168
- Returns the full session data (including all messages) for the Admin Panel.
169
- Returns an empty dict if the session does not exist.
170
- """
171
- path = _get_session_path(session_id)
172
- try:
173
- if not path.exists():
174
- return {}
175
- return await asyncio.to_thread(_read_json, path)
176
- except Exception as exc:
177
- logger.error("session_store: get_session(%s) failed: %s", session_id, exc)
178
- return {}
179
-
180
-
181
- async def delete_session(session_id: str) -> bool:
182
- """
183
- Deletes a session file. Returns True on success, False on failure.
184
- Used by Admin Panel if needed in the future.
185
- """
186
- path = _get_session_path(session_id)
187
- lock = await _get_lock(session_id)
188
- async with lock:
189
- try:
190
- if path.exists():
191
- await asyncio.to_thread(os.remove, path)
192
- return True
193
- except Exception as exc:
194
- logger.error("session_store: delete_session(%s) failed: %s", session_id, exc)
195
- return False
196
-
197
-
198
- async def update_session_name(session_id: str, user_name: str) -> None:
199
- """Updates the user_name field in the session JSON file."""
200
- path = _get_session_path(session_id)
201
- lock = await _get_lock(session_id)
202
- async with lock:
203
- try:
204
- if path.exists():
205
- data = await asyncio.to_thread(_read_json, path)
206
- data["user_name"] = user_name
207
- await asyncio.to_thread(_write_json, path, data)
208
- logger.info("session_store: updated name for session %s to %s", session_id, user_name)
209
- else:
210
- # If session doesn't exist yet, we can't update it, but we could create it?
211
- # Usually ensure_session is called first.
212
- logger.warning("session_store: attempt to update name for non-existent session %s", session_id)
213
- except Exception as exc:
214
- logger.error("session_store: failed to update name for session %s: %s", session_id, exc)
215
-
216
-
217
- async def rename_session(old_id: str, new_name: str) -> str:
218
- """
219
- Renames a session file from old_id to a name based on new_name.
220
- Returns the new session_id used.
221
- """
222
- import re
223
- # 1. Sanitize the name for use as a filename
224
- clean_name = re.sub(r'[^\w\s-]', '', new_name).strip().replace(' ', '_')
225
- if not clean_name:
226
- clean_name = "User"
227
-
228
- new_id = clean_name
229
- old_path = _get_session_path(old_id)
230
- new_path = _get_session_path(new_id)
231
-
232
- # Ensure uniqueness: if John_Doe exists, try John_Doe_1, etc.
233
- counter = 1
234
- while new_path.exists() and new_id != old_id:
235
- new_id = f"{clean_name}_{counter}"
236
- new_path = _get_session_path(new_id)
237
- counter += 1
238
-
239
- if new_id == old_id:
240
- return old_id
241
-
242
- # Lock both IDs to prevent race conditions during move
243
- lock_old = await _get_lock(old_id)
244
- lock_new = await _get_lock(new_id)
245
-
246
- async with lock_old:
247
- async with lock_new:
248
- try:
249
- if old_path.exists():
250
- data = await asyncio.to_thread(_read_json, old_path)
251
- data["session_id"] = new_id
252
- data["user_name"] = new_name
253
-
254
- # Write to new path and delete old
255
- await asyncio.to_thread(_write_json, new_path, data)
256
- await asyncio.to_thread(os.remove, old_path)
257
-
258
- logger.info("session_store: renamed session %s -> %s", old_id, new_id)
259
- return new_id
260
- else:
261
- logger.warning("session_store: cannot rename non-existent session %s", old_id)
262
- return old_id
263
- except Exception as exc:
264
- logger.error("session_store: failed to rename session %s: %s", old_id, exc)
265
- return old_id
266
-
267
-
268
- # ── Sync helper functions (run via asyncio.to_thread) ────────────────────────
269
-
270
- def _write_json(path: Path, data: dict) -> None:
271
- """Atomically writes JSON to a file using a temp file + rename pattern."""
272
- tmp_path = path.with_suffix(".tmp")
273
- with open(tmp_path, "w", encoding="utf-8") as f:
274
- json.dump(data, f, ensure_ascii=False, indent=2)
275
- os.replace(tmp_path, path) # Atomic on POSIX, best-effort on Windows
276
-
277
-
278
- def _read_json(path: Path) -> dict:
279
- with open(path, "r", encoding="utf-8") as f:
280
- return json.load(f)
281
-
282
-
283
- def _list_json_files() -> list:
284
- return list(_get_sessions_dir().glob("*.json"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/services/vector_store.py DELETED
@@ -1,167 +0,0 @@
1
- import hashlib
2
- import json
3
- from pathlib import Path
4
- from typing import Dict, List, Tuple
5
-
6
- import faiss
7
- import numpy as np
8
- import pickle
9
- from rank_bm25 import BM25Okapi
10
-
11
- from app.services.chunker import chunk_documents
12
- from app.services.document_loader import load_documents
13
- from app.services.embeddings import EmbeddingService
14
-
15
-
16
- class FaissVectorStore:
17
- def __init__(
18
- self,
19
- embedding_service: EmbeddingService,
20
- docs_dir: Path,
21
- index_dir: Path,
22
- chunk_size_tokens: int,
23
- chunk_overlap_tokens: int,
24
- ) -> None:
25
- self.embedding_service = embedding_service
26
- self.docs_dir = docs_dir
27
- self.index_dir = index_dir
28
- self.chunk_size_tokens = chunk_size_tokens
29
- self.chunk_overlap_tokens = chunk_overlap_tokens
30
-
31
- self.faiss_index_path = index_dir / "faiss.index"
32
- self.bm25_index_path = index_dir / "bm25.pkl"
33
- self.metadata_path = index_dir / "metadata.json"
34
- self.state_path = index_dir / "state.json"
35
-
36
- self.index = None
37
- self.bm25 = None
38
- self.metadata: List[Dict[str, str]] = []
39
- self.docs_loaded = False
40
- self.last_retrieved: List[Dict[str, str]] = []
41
-
42
- def _compute_docs_fingerprint(self) -> str:
43
- hasher = hashlib.sha256()
44
- # Include chunk settings in fingerprint so changing them triggers re-index
45
- hasher.update(str(self.chunk_size_tokens).encode("utf-8"))
46
- hasher.update(str(self.chunk_overlap_tokens).encode("utf-8"))
47
-
48
- if not self.docs_dir.exists():
49
- return "no_docs"
50
- for path in sorted(self.docs_dir.rglob("*")):
51
- if path.is_file() and path.suffix.lower() in {".txt", ".pdf"}:
52
- stat = path.stat()
53
- hasher.update(str(path).encode("utf-8"))
54
- hasher.update(str(stat.st_mtime_ns).encode("utf-8"))
55
- hasher.update(str(stat.st_size).encode("utf-8"))
56
- return hasher.hexdigest()
57
-
58
- def _can_use_cached_index(self, fingerprint: str) -> bool:
59
- if not (self.faiss_index_path.exists() and self.metadata_path.exists() and self.state_path.exists()):
60
- return False
61
- try:
62
- state = json.loads(self.state_path.read_text(encoding="utf-8"))
63
- return state.get("docs_fingerprint") == fingerprint
64
- except Exception:
65
- return False
66
-
67
- def build_or_load(self) -> None:
68
- self.index_dir.mkdir(parents=True, exist_ok=True)
69
- fingerprint = self._compute_docs_fingerprint()
70
-
71
- if self._can_use_cached_index(fingerprint):
72
- self.index = faiss.read_index(str(self.faiss_index_path))
73
- with open(self.bm25_index_path, "rb") as f:
74
- self.bm25 = pickle.load(f)
75
- self.metadata = json.loads(self.metadata_path.read_text(encoding="utf-8"))
76
- self.docs_loaded = len(self.metadata) > 0
77
- return
78
-
79
- docs = load_documents(self.docs_dir)
80
- chunks = chunk_documents(docs, self.chunk_size_tokens, self.chunk_overlap_tokens)
81
- if not chunks:
82
- self.index = None
83
- self.metadata = []
84
- self.docs_loaded = False
85
- return
86
-
87
- vectors = self.embedding_service.encode([c["text"] for c in chunks])
88
- dim = vectors.shape[1]
89
- index = faiss.IndexFlatIP(dim)
90
- index.add(vectors)
91
-
92
- tokenized_corpus = [c["text"].lower().split() for c in chunks]
93
- bm25 = BM25Okapi(tokenized_corpus)
94
-
95
- self.index = index
96
- self.bm25 = bm25
97
- self.metadata = chunks
98
- self.docs_loaded = True
99
-
100
- faiss.write_index(index, str(self.faiss_index_path))
101
- with open(self.bm25_index_path, "wb") as f:
102
- pickle.dump(bm25, f)
103
- self.metadata_path.write_text(json.dumps(chunks, ensure_ascii=False, indent=2), encoding="utf-8")
104
- self.state_path.write_text(
105
- json.dumps({"docs_fingerprint": fingerprint, "chunk_count": len(chunks)}, indent=2),
106
- encoding="utf-8",
107
- )
108
-
109
- def search(self, query: str, top_k: int = 4) -> List[Dict[str, str]]:
110
- if self.index is None or not self.metadata or self.bm25 is None:
111
- self.last_retrieved = []
112
- return []
113
-
114
- # Vector Search (FAISS)
115
- query_vec = self.embedding_service.encode_query(query)
116
- faiss_scores, faiss_indices = self.index.search(np.asarray(query_vec, dtype=np.float32), top_k * 2)
117
-
118
- # BM25 Search
119
- tokenized_query = query.lower().split()
120
- bm25_scores = self.bm25.get_scores(tokenized_query)
121
-
122
- # Get top indices for BM25
123
- bm25_top_indices = np.argsort(bm25_scores)[::-1][:top_k * 2]
124
-
125
- # Combine using Reciprocal Rank Fusion (RRF)
126
- # RRF_score = 1 / (k + rank)
127
- k = 60
128
- rrf_scores = {}
129
-
130
- # Add FAISS ranks
131
- for rank, idx in enumerate(faiss_indices[0]):
132
- if idx < 0 or idx >= len(self.metadata):
133
- continue
134
- rrf_scores[idx] = rrf_scores.get(idx, 0.0) + (1.0 / (k + rank + 1))
135
-
136
- # Add BM25 ranks
137
- for rank, idx in enumerate(bm25_top_indices):
138
- if idx < 0 or idx >= len(self.metadata):
139
- continue
140
- rrf_scores[idx] = rrf_scores.get(idx, 0.0) + (1.0 / (k + rank + 1))
141
-
142
- # Sort by combined RRF score
143
- sorted_indices = sorted(rrf_scores.keys(), key=lambda x: rrf_scores[x], reverse=True)[:top_k]
144
-
145
- results: List[Dict[str, str]] = []
146
- for idx in sorted_indices:
147
- chunk = self.metadata[idx]
148
- # Fetch the original FAISS score for fallback relevance checking if needed, or just use RRF score
149
- # Note: RRF scores are small (e.g. 0.03), so we must adjust the threshold in rag_pipeline
150
- results.append(
151
- {
152
- "id": chunk["id"],
153
- "source": chunk["source"],
154
- "text": chunk["text"],
155
- "score": float(rrf_scores[idx]), # RRF score
156
- }
157
- )
158
- self.last_retrieved = results
159
- return results
160
-
161
- def health(self) -> Dict[str, object]:
162
- return {
163
- "docs_loaded": self.docs_loaded,
164
- "index_ready": self.index is not None,
165
- "chunk_count": len(self.metadata),
166
- "index_path": str(self.faiss_index_path),
167
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/app/ui_gradio.py DELETED
@@ -1,275 +0,0 @@
1
- import asyncio
2
- import logging
3
- import os
4
- import random
5
- import string
6
- import traceback
7
- import uuid
8
- from typing import Dict, List, Tuple
9
-
10
- from app.services import session_store as _ss
11
-
12
- import gradio as gr
13
- import httpx
14
-
15
- from app.core.config import get_settings
16
- from app.services.embeddings import EmbeddingService
17
- from app.services.llm import LLMService
18
- from app.services.rag_pipeline import RAGPipeline
19
- from app.services.vector_store import FaissVectorStore
20
- from app.services.reranker import RerankerService
21
-
22
- logger = logging.getLogger(__name__)
23
- settings = get_settings()
24
- API_URL = os.getenv("RAG_API_URL", "").strip()
25
-
26
- # Initialize services
27
- embedding_service = EmbeddingService(settings.embedding_model)
28
- vector_store = FaissVectorStore(
29
- embedding_service=embedding_service,
30
- docs_dir=settings.docs_dir,
31
- index_dir=settings.index_dir,
32
- chunk_size_tokens=settings.chunk_size_tokens,
33
- chunk_overlap_tokens=settings.chunk_overlap_tokens,
34
- )
35
- llm_service = LLMService(
36
- provider=settings.llm_provider,
37
- groq_api_key=settings.groq_api_key,
38
- groq_model=settings.groq_model,
39
- groq_rewrite_model=settings.groq_rewrite_model,
40
- hf_api_key=settings.hf_api_key,
41
- hf_model=settings.hf_model,
42
- timeout_s=settings.request_timeout_s,
43
- )
44
- reranker_service = RerankerService(settings.reranker_model)
45
- pipeline = RAGPipeline(
46
- vector_store=vector_store,
47
- llm_service=llm_service,
48
- reranker=reranker_service,
49
- top_k=settings.top_k,
50
- max_context_chunks=settings.max_context_chunks
51
- )
52
-
53
- # Ensure index is ready
54
- vector_store.build_or_load()
55
-
56
-
57
- # ── Keyboard layout for realistic typos ──────────────────────────────
58
- NEARBY_KEYS = {
59
- 'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'sfecx', 'e': 'wrsdf',
60
- 'f': 'dgrtcv', 'g': 'fhtybn', 'h': 'gjyunb', 'i': 'uojkl', 'j': 'hkunmi',
61
- 'k': 'jlomi', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'ipkl',
62
- 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
63
- 'u': 'yihj', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
64
- 'z': 'xas',
65
- }
66
-
67
- TYPO_CHANCE = 0.07 # 7% chance per word
68
-
69
-
70
- def _nearby_char(ch: str) -> str:
71
- """Return a plausible neighbouring key for the given character."""
72
- lower = ch.lower()
73
- if lower in NEARBY_KEYS:
74
- replacement = random.choice(NEARBY_KEYS[lower])
75
- return replacement.upper() if ch.isupper() else replacement
76
- return random.choice(string.ascii_lowercase)
77
-
78
-
79
- def _to_history(messages: List[Dict[str, str]]) -> list:
80
- """Returns the history as a list of dicts (already in this format for Gradio 5)."""
81
- return messages
82
-
83
-
84
- async def _chat_via_api(message: str, history: List[Dict[str, str]]) -> str:
85
- payload = {"message": message, "history": history}
86
- headers = {"Content-Type": "application/json"}
87
- if settings.api_key:
88
- headers["x-api-key"] = settings.api_key
89
-
90
- async with httpx.AsyncClient(timeout=30.0) as client:
91
- resp = await client.post(API_URL, json=payload, headers=headers)
92
- if resp.status_code == 429:
93
- return "⚠️ I'm receiving too many requests right now. Please wait a moment before sending another message."
94
- resp.raise_for_status()
95
- return resp.json()["reply"]
96
-
97
-
98
- async def _get_reply(message: str, chat_history: List[Dict[str, str]]) -> str:
99
- """Fetch the full reply from the LLM (API or direct pipeline)."""
100
- if API_URL:
101
- return await _chat_via_api(message, chat_history)
102
- result = await pipeline.chat(message=message, history=chat_history)
103
- return result["reply"]
104
-
105
-
106
- async def chat_fn(message: str, chat_history: List[Dict[str, str]], session_id: str = ""):
107
- """Streaming generator: yields word-by-word with human-like timing & typos."""
108
- if not message or not message.strip():
109
- yield gr.update(interactive=True), chat_history
110
- return
111
-
112
- original_history = chat_history.copy()
113
- chat_history = chat_history + [
114
- {"role": "user", "content": message},
115
- {"role": "assistant", "content": ""}
116
- ]
117
- yield gr.update(value="", interactive=False), chat_history
118
-
119
- try:
120
- reply = await _get_reply(message, original_history)
121
- except httpx.HTTPStatusError as e:
122
- logger.error(f"HTTP Error: {e.response.status_code} - {e.response.text}")
123
- reply = "⚠️ Rate limit reached. Please slow down a bit!" if e.response.status_code == 429 \
124
- else "⚠️ I encountered an error. Please try again in a few seconds."
125
- except Exception as e:
126
- logger.error(f"Unexpected error: {str(e)}\n{traceback.format_exc()}")
127
- reply = f"⚠️ Oops! Something went wrong."
128
-
129
- # ── Humanistic letter-by-letter typing — HTML-safe ────────────────────
130
- displayed = "" # what is shown in the chat bubble
131
- buffer = "" # invisible accumulator for mid-HTML-tag characters
132
- in_tag = False # True while we are inside a < … > sequence
133
-
134
- for char in reply:
135
- if char == '<':
136
- in_tag = True
137
- buffer += char
138
- continue
139
-
140
- if in_tag:
141
- buffer += char
142
- if char == '>':
143
- # Tag is now complete — flush it to the display all at once
144
- in_tag = False
145
- displayed += buffer
146
- buffer = ""
147
- chat_history[-1] = {"role": "assistant", "content": displayed}
148
- yield gr.update(value="", interactive=False), chat_history
149
- # Small pause after a <br> to mimic line-break pacing
150
- if displayed.endswith('<br>') or displayed.endswith('<br/>'):
151
- await asyncio.sleep(random.uniform(0.1, 0.2))
152
- continue
153
-
154
- # ── Normal character (not inside a tag) ───────────────────────────
155
- # Decide typo chance only for plain alphabetic words
156
- do_typo = (
157
- char.isalpha()
158
- and len(displayed) > 8 # skip the very beginning
159
- and random.random() < TYPO_CHANCE
160
- )
161
-
162
- if do_typo:
163
- wrong_char = _nearby_char(char)
164
- displayed += wrong_char
165
- chat_history[-1] = {"role": "assistant", "content": displayed}
166
- yield gr.update(value="", interactive=False), chat_history
167
- await asyncio.sleep(random.uniform(0.15, 0.3))
168
-
169
- # Backspace the wrong character
170
- displayed = displayed[:-1]
171
- chat_history[-1] = {"role": "assistant", "content": displayed}
172
- yield gr.update(value="", interactive=False), chat_history
173
- await asyncio.sleep(random.uniform(0.1, 0.2))
174
-
175
- # Type the correct character
176
- displayed += char
177
- chat_history[-1] = {"role": "assistant", "content": displayed}
178
- yield gr.update(value="", interactive=False), chat_history
179
-
180
- # Pacing: punctuation pauses, space micro-pause, normal char speed
181
- if char in '.!?':
182
- await asyncio.sleep(random.uniform(0.35, 0.7))
183
- elif char in ',:;':
184
- await asyncio.sleep(random.uniform(0.15, 0.3))
185
- elif char == ' ':
186
- await asyncio.sleep(random.uniform(0.06, 0.13))
187
- else:
188
- await asyncio.sleep(random.uniform(0.02, 0.06))
189
-
190
- # ── Fire-and-forget: persist to session store (never blocks the UI) ───
191
- # We only save successful responses, not error messages
192
- if session_id and not displayed.startswith("⚠️"):
193
- asyncio.create_task(_ss.save_message(session_id, message, displayed))
194
-
195
- # Re-enable the input box at the end
196
- yield gr.update(interactive=True), chat_history
197
-
198
-
199
- custom_css = """
200
- /* Aggressively force height: auto and min-height: 40px on the chatbot and flex containers */
201
- .gradio-container, .flex, .block, #chatbot-window {
202
- height: auto !important;
203
- min-height: 40px !important;
204
- }
205
-
206
- /* Ensure the chatbot doesn't overflow the viewport if it gets too full */
207
- #chatbot-window {
208
- max-height: 70vh !important;
209
- border: none !important;
210
- }
211
-
212
- /* Aggressively hide the footer, including HF injected footers if inside the container */
213
- footer, .footer, footer * {
214
- display: none !important;
215
- visibility: hidden !important;
216
- height: 0 !important;
217
- margin: 0 !important;
218
- padding: 0 !important;
219
- }
220
- """
221
-
222
- # JS: runs immediately on page load — generates/loads session_id from localStorage
223
- # and restores previous chat history. Completely non-blocking.
224
- _SESSION_JS = """
225
- async () => {
226
- // ── Session ID: generate once, persist forever in localStorage ──
227
- let sid = localStorage.getItem('martech_session_id');
228
- if (!sid) {
229
- sid = 'sess-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
230
- localStorage.setItem('martech_session_id', sid);
231
- }
232
- // Write the session_id into the hidden Gradio textbox
233
- const el = document.getElementById('session-id-box')?.querySelector('textarea');
234
- if (el) { el.value = sid; el.dispatchEvent(new Event('input', {bubbles:true})); }
235
- return sid;
236
- }
237
- """
238
-
239
- with gr.Blocks(title="Martechsol Assistant", theme=gr.themes.Soft(), css=custom_css) as demo:
240
- gr.Markdown("# Martechsol Assistant")
241
- gr.Markdown("Welcome! How can I help you today?")
242
-
243
- chatbot = gr.Chatbot(
244
- show_label=False,
245
- elem_id="chatbot-window",
246
- render_markdown=True,
247
- )
248
- with gr.Row():
249
- msg = gr.Textbox(
250
- placeholder="Type your question here...",
251
- show_label=False,
252
- scale=9
253
- )
254
- send = gr.Button("Send", variant="primary", scale=1)
255
-
256
- clear = gr.Button("Clear Chat History")
257
-
258
- # Hidden textbox holds the session_id — invisible to the user
259
- session_id = gr.Textbox(
260
- value="",
261
- visible=False,
262
- elem_id="session-id-box",
263
- label="session_id",
264
- )
265
-
266
- # On load: run JS to set session_id from localStorage (non-blocking)
267
- demo.load(fn=None, inputs=None, outputs=session_id, js=_SESSION_JS)
268
-
269
- # Wire up the events — streaming generator for live typing effect (UNCHANGED)
270
- send.click(chat_fn, inputs=[msg, chatbot, session_id], outputs=[msg, chatbot])
271
- msg.submit(chat_fn, inputs=[msg, chatbot, session_id], outputs=[msg, chatbot])
272
- clear.click(lambda: [], None, chatbot)
273
-
274
- if __name__ == "__main__":
275
- demo.launch(server_name="0.0.0.0", server_port=7860, show_api=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/docker-compose.yml DELETED
@@ -1,14 +0,0 @@
1
- version: "3.9"
2
- services:
3
- rag-api:
4
- build: .
5
- container_name: rag-api
6
- ports:
7
- - "8000:8000"
8
- env_file:
9
- - .env
10
- volumes:
11
- - ./docs:/app/docs
12
- - ./data/index:/app/data/index
13
- - ./data/sessions:/app/data/sessions
14
- restart: unless-stopped
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/docs/MartechSol_Employee_HandBook (7) (1).pdf DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a48b14cc28d7892ae03a5dfe06a244832e2807ce178ca1f1acdbc58d6af4e8ef
3
- size 3844502
 
 
 
 
backup_2026-05-04/docs/MartechSol_Employee_Handbook_Extracted.txt DELETED
@@ -1,1091 +0,0 @@
1
- Employee Handbook
2
- Last Modified: September 1st, 2024
3
-
4
- Contents
5
- Introduction ....................................................................................................... 4
6
- Workplace Orientation ....................................................................................... 4
7
- Employment Status .......................................................................................... 5
8
- Employment At Will .......................................................................................... 5
9
- Employee Responsibilities ........................................................................................ 5
10
- Workweek and Workday .................................................................................... 5
11
- Time Clocks and Time Cards ............................................................................... 5
12
- General Attendance Requirements ....................................................................... 6
13
- Work from Home .............................................................................................. 6
14
- Problem Resolution ........................................................................................... 7
15
- Personal Appearance ......................................................................................... 7
16
- Health Safety .................................................................................................. 7
17
- Employee Relationships ..................................................................................... 8
18
- Policy Against Harassment ................................................................................. 8
19
- Activity Permission ........................................................................................... 9
20
- Confidentiality ................................................................................................ 9
21
- Intangible Property ......................................................................................... 10
22
- Restraint of Trade, Non-Compete, and Cooling Off Period ....................................... 11
23
- Solicitation ................................................................................................... 11
24
- Cell Phone Usage ........................................................................................... 12
25
- Computer, E-Mail and Internet Policy ................................................................. 12
26
- Tobacco Use .................................................................................................. 13
27
- Grievance Procedure ....................................................................................... 13
28
- Copyright Violation ......................................................................................... 13
29
- Data Theft .................................................................................................... 13
30
- Separation from the Company .......................................................................... 14
31
- Exit Interviews .............................................................................................. 14
32
- Final Settlement ............................................................................................. 14
33
- Return of Property .......................................................................................... 15
34
-
35
-
36
- Acceptability ................................................................................................. 15
37
- Work Rules ................................................................................................... 15
38
- Level 1 Actions .............................................................................................. 16
39
- Level 2 Actions .............................................................................................. 16
40
- Level 3 Actions .............................................................................................. 17
41
- Disciplinary Procedures ..................................................................................... 18
42
- Employee Benefits ................................................................................................. 18
43
- Referral Program ............................................................................................ 18
44
- Game Room .................................................................................................. 18
45
- Holidays and Holiday Pay ................................................................................. 18
46
- Pay Period and Paychecks ................................................................................ 19
47
- Leaves ......................................................................................................... 19
48
- Sick Leaves ................................................................................................... 19
49
- Casual Leaves ............................................................................................... 20
50
- Annual Leaves ............................................................................................... 21
51
- Hajj Leave .................................................................................................... 22
52
- Maternity Leave ............................................................................................. 23
53
- Paternity Leave .............................................................................................. 23
54
- Bereavement Leave ........................................................................................ 24
55
- Unauthorized Leaves ....................................................................................... 24
56
- Unapproved Absence Without Pay ..................................................................... 25
57
- Leave Encashment ......................................................................................... 25
58
- Other Benefits ..................................................................................................... 25
59
- Loans & Advance Salary .................................................................................. 25
60
- Maternity (Pregnancy) Benefit .......................................................................... 26
61
- Company Maintained Vehicle ............................................................................ 27
62
- Healthcare Insurance – For Self & Spouse ............................................................ 27
63
- EOBI: .......................................................................................................... 30
64
- Provident Fund: ............................................................................................. 30
65
-
66
-
67
- Introduction
68
- We are providing you this employee handbook to introduce you to our policies. The handbook will
69
- describe some of the more important employee benefits and obligations.
70
- Our business is constantly changing, and therefore MartechSol reserves the right to change all of our
71
- policies, including those covered in this handbook, at any time. You will be informed of any changes
72
- in policy through posting on portal or through other appropriate means. If you are uncertain about
73
- any policy or procedure, please check with your Supervising Authority.
74
- Workplace Orientation
75
- Orientati
76
- on Training and Initial Evaluation Period
77
- All employees will receive orientation training at the beginning of employment with MartechSol. This
78
- period of orientation will include familiarization with Logicose’s work policies, disciplinary policies, job
79
- duties, and reporting responsibilities.
80
- Upon completion of orientation, all employees will undergo evaluation during the first ninety (90)
81
- calendar days of employment. If performance is unsatisfactory for any reason during this period of time,
82
- the employee may be immediately dismissed from employment. At the end of this 90-day period, the
83
- employee’s Supervising Authority will determine if the employee’s performance warrants continued
84
- employment.
85
- If the employee’s performance is not up to the Supervising Authority’s expectations in terms of quality of
86
- work, output, behavior, or any other performance indicator, the employee will not be confirmed for
87
- further employment at the company after this 90-day period. It is also up to the Supervising Authority to
88
- end the probation period at any point before 90 days and either confirm or terminate the employee.
89
- The Supervising Authority is required to make a decision regarding the confirmation of an employee after
90
- 90 days. If the Supervising Authority requires more time to evaluate an employee further, (s)he has the
91
- right extend the probation period.
92
- Confirmation
93
- If performance exceeds expectations, an employee may receive a salary increment upon confirmation.
94
- The date of confirmation depends on date of joining and performance. Revised salaries for employees
95
- that are confirmed before the 15th of a given month will be effective from the 1st of that same month. On
96
- the other hand, revised salaries will be effective from the 1st of the following month for employees that
97
- are confirmed after the 15th of a given month. For example, if someone is confirmed on the 10th of April,
98
- the revised salary will be effective from the 1st April, however if they are confirmed on the 16th of April,
99
- the revised salary will be effective from the 1st of May.
100
- Casual and sick leaves will also be assigned on a pro-rata basis based on this calculation.
101
-
102
-
103
- Employment Status
104
- A “regular full-time employee” is any employee who is employed to work on a year-round basis, and who
105
- is regularly scheduled to work their full shift hours.
106
- Employment At Will
107
- All employees are employed on an "at-will" basis. This means that employees are not employed for any
108
- specific term or period of time, and that the employer may terminate the employment relationship at
109
- any time for any reason. Similarly, employees may resign and terminate employment with MartechSol
110
- at any time and for any reason with prior notice of 30-90 days depending on their role within the
111
- organization.
112
- N
113
- o employee has any authority to change the nature of this relationship through any statements,
114
- representations, or conduct. The at-will relationship may be changed only through written and signed
115
- authorization of the Supervising Authority.
116
- Employee Responsibilities
117
- Workweek and Workday
118
- The nor
119
- mal workweek shall be considered full shift hours, six days per week, from 9:00 a.m. to 5:30 p.m.
120
- The first Saturday of every month is off and the remaining three Saturday’s in each month are six and a
121
- half hours (half-day). One hour is allocated for lunch/dinner in all shifts.
122
-  Morning Shift: 1:30 pm - 2:30 pm
123
-  Evening Shift: 9:30 pm - 10:30 pm
124
- Smoking Breaks: There are a maximum of 3 breaks allowed in a workday (other than lunch/dinner
125
- break). These breaks are not to be longer than 5 minutes.
126
- If any other break is required, the employee is to contact his or her Supervising Authority for approval.
127
- Time Clocks and Time Cards
128
- All empl
129
- oyees shall use the portal to record their starting time and ending time. For wage payment
130
- purposes, time worked will be figured to the nearest tenth of an hour.
131
- Sharing login information (username and password) is strictly prohibited. Each person is to use their own
132
- login information to time in at work. If any discrepancy is observed in the time in or time out process,
133
- harsh disciplinary action will be taken against the person or persons involved.
134
- In case an employee forgets to time in or is unable to time in, they are to submit a trouble ticket to the
135
- Human Resources department. It is the employee’s responsibility to maintain their time sheet accurately.
136
- Monthly salary will be calculated according to the time sheet.
137
- Employees who want to change their work shift should contact their Supervising Authority for approval.
138
- Necessary paperwork and legitimate reason are required for the shift change process. The decision to
139
- change the shift of an employee is at the discretion of the Supervising Authority.
140
-
141
-
142
- General Attendance Requirements
143
- Regular and pro
144
- mpt attendance of employees is essential to our business. Employees who cannot meet
145
- our attendance requirements will be subject to counseling and disciplinary action. Excessive absenteeism
146
- or tardiness will result in termination of employment. The Supervisor has the authority to mark a half
147
- day, day off, or full day at his/her discretion.
148
-  Late Coming: Late coming is permitted till 20 minutes from your shift start time. If you arrive
149
- after 20 minutes of your shift start time, then it will be considered as a late arrival. 4 late arrivals
150
- in a month will be considered as a half day off. In addition, an employee is to inform his/her
151
- Supervising Authority if they will be more than 30 minutes late to work.
152
-  Early Out: If you need to leave before your shift ends, verbal approval is mandatory from your
153
- Supervising Authority. There will be no deductions for early out.
154
-  Half Day: Half day will be marked if you arrive 2 hours after your shift start time. If you complete
155
- less than six and a half hours on any given day, then it will also be considered as a half day off.
156
-  Full Day: Employees who spend less than 2 hours at work on any workday will be marked absent
157
- that day.
158
-  Employees who are late to work on any given workday are still responsible to complete all the
159
- work assigned to them. If they are unable to, they should contact their Supervising Authority
160
- immediately. If an employee arrives at work after 1 hour of their shift start time, by default,
161
- the 8.5 hours shift has to be completed. If an employee is more than 1 hour late and wants
162
- to
163
- leave early (without completing the total (8.5 hr) shift time), he/she has to contact their
164
- Supervising Authority for approval.
165
-  For
166
- employees who are unable to complete at least 50% of their work during a workday, the
167
- Supervising Authority reserves the right to decide the status of the workday (full day, off day,
168
- half day) is at his or her discretion.
169
- Work from Home
170
- In case o
171
- f emergency (bad weather, temporary disability, unrest in city, etc) when employees cannot
172
- reach work, they will be requested to work from home. In case an employee has personal valid reason(s)
173
- as to why they cannot reach work, they can work from home requesting approval from their Supervising
174
- Authority. The approval of this request is at the Supervising Authority’s discretion.
175
- If less than 30% of the work has been completed, the day will be counted as a full day off. If 30-70% of
176
- the work has been completed, it will be counted as a half day. If more than 70% of the work has been
177
- completed, it will be counted as a full day.
178
- Work can be submitted no later than 3 hours after the end of the employee’s regular shift timing. If the
179
- work is not submitted within the given time, it will not be marked as work provided for the same day. If
180
- an employee faces problems submitting their work within the given duration and wants to submit their
181
- work later, approval is required from their Supervising Authority.
182
-
183
-
184
- Problem Resolution
185
- If an emplo
186
- yee faces difficulty or problems related to anything within the workplace, it is their
187
- responsibility to submit a trouble ticket through the Portal. There is no need to contact the Supervising
188
- Authority unless their ticket has not been addressed for more than 24 hours.
189
- You are requested to use this trouble ticket system for your computer, admin and HR related
190
- issues. How to use this system:
191
- 1. If you have any computer, admin, or HR related issue, go to the tickets link in the portal and
192
- select ‘Submit a ticket’.
193
- 2. Select the concerned department for your problem e.g. if your mouse, keyboard, CPU or LCD is
194
- not working properly, please select Networking. If there is any issue with your attendance or
195
- payroll, select Human Resources. If there is any issue with fan, AC, kitchen item etc. please select
196
- Admin department.
197
- 3. Once your trouble ticket is submitted, you may view the status of your ticket from ‘View open
198
- tickets’ link.
199
- As soon as your ticket is submitted, the Company will make every possible effort to resolve your issue on
200
- an urgent basis.
201
- There is also a messaging feature in this trouble ticket system. If there is any update related to your
202
- ticket, you will receive a message within your open ticket. Similarly, if you want to send any message
203
- related to your problem, you can also do that from ‘View open tickets’ section.
204
- Once your issue is resolved, your ticket will be closed. You may view your closed tickets from ‘View your
205
- closed tickets’ section.
206
- This system will help resolve your issues quickly and will allow you to view status of your open tickets.
207
- Personal Appearance
208
- A company
209
- is judged by its employees, as well as by its products and services. All employees are
210
- expected to maintain good standards of personal neatness, including hair, clothes, and shoes.
211
- Casual to business-style dress is appropriate. Employees should be neatly groomed and clothes should be
212
- clean and in good condition. Leisure clothes such as cut-offs or halter tops are not acceptable attire for
213
- the business office.
214
- Health Safety
215
- If an employee has had a contagious illness or serious health issue which may affect the health of other
216
- employees (e.g. chicken pox, flu), a doctor’s written permission to return to work will be necessary.
217
-
218
-
219
- Employee Relationships
220
- MartechSol employs a number of people and, as a result, you will be working with people whose
221
- personalities may differ from yours. It is your responsibility to get along with all of your co-workers. A
222
- spirit of cooperation shown by all of us will help to accomplish our purpose of providing new
223
- customers with the best possible service.
224
- This does not mean that you need to become a personal friend of all your co-workers, but it does mean
225
- that you should treat your co-workers with respect. Regardless of your personal opinions, your fellow
226
- employee should be treated exactly as you would like to have that person treat you.
227
- Senior employees should remember that they were once new employees and therefore should do
228
- everything possible to aid new employees in acclimating themselves to their new jobs.
229
- Policy
230
- Against Harassment
231
- MartechSol is committed to maintaining a work environment that is free of discrimination and
232
- harassment. In keeping with this commitment, we will not tolerate harassment of our employees by
233
- anyone, including any Supervising Authority, co-worker, vendor, client, or customer.
234
- Harassment consists of unwelcome conduct, whether verbal, physical, or visual, that is based upon a
235
- person’s protected status, such as sex, color, race, ancestry, religion, national origin, age, physical
236
- handicap, medical condition, disability, marital status, veteran status, citizenship status, or other
237
- protected group status.
238
- MartechSol will not tolerate harassing conduct that affects tangible job benefits, that interferes
239
- unreasonably with an individual’s work performance, or that creates an intimidating, hostile, or
240
- offensive working environment.
241
- Sexual harassment deserves special mention. Unwelcome sexual advances, requests for sexual favors,
242
- and other physical, verbal, or visual conduct based on sex constitute sexual harassment when (1)
243
- submission to the conduct is made a term or condition of employment, (2) submission to or rejection of
244
- the conduct is used as the basis for an employment decision, or (3) the conduct has the purpose or effect
245
- of unreasonably interfering with an individual’s work performance or creating an intimidating, hostile, or
246
- offensive working environment. Sexual harassment may include explicit sexual proposition, sexual
247
- innuendo, suggestive comments, sexually oriented “kidding” or “teasing,” “practical jokes,” jokes about
248
- gender-specific traits, foul or obscene language or gestures, displays of foul or obscene printed or visual
249
- material, and physical contact, such as patting, pinching, or brushing against another’s body.
250
- All employees are responsible for helping to assure we avoid harassment. If you feel that you
251
- have experienced or witnessed harassment, you should immediately notify your Supervising
252
- Authority. MartechSol forbids retaliation against anyone who has reported harassment.
253
- It is our policy to investigate all such complaints thoroughly and promptly. To the fullest extent
254
- practicable, MartechSol will keep complaints and the terms of their resolution confidential. If an
255
- investigation confirms that harassment has occurred, MartechSol will take appropriate corrective
256
- action, including discipline up to and including immediate dismissal from employment.
257
-
258
-
259
- Activity Permission
260
- MartechSol is concerned with outside activities of employees only where such activities might involve a
261
- conflict of interest with Logicose’s best interests. A conflict of interest exists if an employee has a
262
- private financial interest or other relationship outside MartechSol that is detrimental to the best
263
- interests of MartechSol.
264
- You shall not without prior written consent of the company, engage directly or indirectly, in any trade,
265
- business or occupation or any other income generation activity.
266
- If an employee desires to attend an academic institution during his/her employment, (s)he must receive
267
- permission from the company by submitting relevant documentation. It is mandatory to fill the Activity
268
- Permission form for approval.
269
- An employee should discuss any possible conflict of interest with the HR Department as full disclosure
270
- and open knowledge are essential. For the employee’s own protection, written approval should be
271
- obtained in all cases where a possible conflict of interest is involved.
272
- Other possible conflict of interest situations may include any type of “moonlighting work” or a second
273
- job, which interferes with the employee’s work for MartechSol, any type of external business
274
- relationship with MartechSol, any of its competitors or any of its customers, and any type of public
275
- activity which may affect adversely on MartechSol.
276
- Confidentiality
277
- MartechSol is a service provider that creates, completes, and delivers work for clients that do not want
278
- our involvement disclosed in their projects. The ownership of the work we create for our clients is
279
- transferred over to them and they hold the copyright to the work delivered. Disclosing such work may
280
- result in Copyright Infringement, so it is in your, and the company’s best interest, to not share
281
- involvement in such work.
282
- Moreover, employees are prohibited from maintaining records of confidential information or intellectual
283
- property on personal e-mail accounts, computers, hard drives, smart phones, cloud, or online.
284
- Both during employment with MartechSol and after separation, employees are not permitted to
285
- disclose to any third party or share any confidential information that is made available to you.
286
- Confidential information includes, but is not limited to:
287
-  Web copy, eBooks, Press Releases, Reports, Infographics, Articles, Blogs, and/or any copy that was
288
- created or revised for clients while employed at MartechSol is the property of either the clients or
289
- MartechSol. Disclosing involvement in such projects to those outside the company, mentioning it
290
- on your CV or resume, and/or making public your involvement in such projects is prohibited.
291
-  Search Engine Optimization strategy, campaigns, processes, proposals, accounts for posting, list
292
- of clients, contact information of clients, content created or revised for client is confidential
293
- information. Mentioning your involvement and/or disclosing such information to those outside
294
- the company or on your CV or resume is considered breach of policy.
295
-  Client Information, leads, pricing strategy, proposals, account information, portal information,
296
- samples, and promotional campaigns are the intellectual property of MartechSol. They may
297
- neither be disclosed nor shared with those outside the organization.
298
-
299
-
300
-  Sensitive company information, such as employee contact details, software code, promotional
301
- campaigns, client list, project(s) details, performance evaluation system, and in-house software.
302
-  Any and all such information is either Logicose’s or its Client’s intellectual property.
303
- Immediately upon termination of employment, or at any other time upon the company’s request, all
304
- employees will return to the Company:
305
-  All memoranda, notes and data, and computer software and hardware, records or other
306
- data/documents compiled by the employee or made available to the employee during the
307
- employee’s employment with the company concerning the business of the company, its
308
- affiliates, or their customers.
309
-  All other Confidential Information that is disclosed to you during employment.
310
-  All physical and intellectual property of the company or its affiliates, including without limitation,
311
- all the content, information, plans, files, records, documents, lists, equipment, supplies,
312
- promotional materials, keys, vehicles, and similar items and all copies thereof or extracts there
313
- from.
314
- Disclosure of such information while employed or after separation from MartechSol is unethical, and
315
- such actions will be considered intellectual property theft.
316
- Furthermore, you cannot claim ownership of any project or developed content/material in any manner.
317
- For example, giving a reference on your CV to a specific website for which you developed content while
318
- working at the Company is prohibited. Any project or client details relating to your work carried out at
319
- this company cannot be mentioned on your CV.
320
- Intangible Property
321
- During your course of employment with MartechSol, you will come across information that is privy to
322
- those outside the organization. Such information is the intellectual (intangible) property of the company.
323
- You may not at any time, during or after employment with MartechSol, have or claim any right, title, or
324
- interest in any trade name, trademark, patent, copyright, work for hire, or other similar rights belonging
325
- to or used by the company and shall not have or claim any right, title, or interest in any material or
326
- matter of any sort prepared for or used in connection with the business or promotion of the company,
327
- whatever your involvement with such matters may have been, and whether procured, produced,
328
- prepared, or published in whole or in part by you.
329
- MartechSol, or its clients, have and will retain the sole and exclusive rights in any and all such trade
330
- names, trademarks, patents, copyrights, material, and matter unless transferred over to a third party.
331
- Any and all documents, software, or copy that was created for MartechSol, its clients, or its affiliate
332
- brand names, is the intellectual property of the company or its clients. You may not claim ownership,
333
- maintain personal records of, or disclose your involvement in the creation or modification of such
334
- information, software, or projects.
335
-
336
-
337
- Restraint of Trade, Non-Compete, and Cooling Off Period
338
- Any current or separated employee is prohibited from incorporating a new company or starting a new
339
- business in the same industry before the time of at least 24 months.
340
- Employee shall not, on his or her own behalf or behalf of others, hold any ownership interest in any
341
- business engaged in the following industries:
342
-  Copywriting
343
-  Online Marketing
344
-  SEO
345
-  SMO
346
-  Ghostwriting
347
-  Industries that MartechSol, or any of its brand names, are currently or in the process of operating in
348
- Employee agrees and covenants that the use of sensitive and confidential information that is made available,
349
- in certain circumstances, may cause irreparable damage to MartechSol and its reputation. Therefore,
350
- employee shall not until the expiration of two years after the termination of the employment,
351
- through any corporations or associates in any business, form a business that is directly competitive with
352
- Company for a period of two years.
353
- Since the region or territory in which MartechSol operates is based online, the jurisdiction of the
354
- restraint of trade is not limited to a specific city, state, or country. Forming an organization that is based
355
- in any city MartechSol operates in, and/or creation of an online business is a breach of policy.
356
- Failure to comply with activity permission, restraint of trade, confidentiality, intangible property, non-
357
- disclosure, and non-compete will result in immediate termination. The Company also reserves the right
358
- to take appropriate legal action upon breach of policy both during employment and after separation.
359
- Solicitation
360
- Both during employment with the company and for a period of two (2) years following the termination/
361
- resignation of employment with the company at any time and for any reason, employee’s will not, directly
362
- or indirectly, on the employee’s own behalf or on behalf of any other person or entity, hire or solicit to
363
- hire for employment or consulting other provision of services, any person who is actively employed or
364
- engaged (or in the preceding six months was actively employed or engaged) by MartechSol.
365
- This includes, but is not limited to, inducting or attempting to induce, or influencing or attempting to
366
- influence, any person employed or engaged by the company to terminate his or her relationship with the
367
- company. MartechSol reserves the right to take appropriate legal action against solicitation of employees.
368
- Furthermore, both during the employee’s employment and after separation from the company at any
369
- time and for any reason, the employee will not directly or indirectly, on the employee’s own behalf of
370
- any other person or entity, solicit the business of any Customer or any other entity with which
371
- MartechSol has worked with or discussed the possibility of any work, at the time of employee’s
372
- termination, to provide services to such entity (a “Contractor”). Client information is confidential, and
373
- MartechSol reserves the right to take legal action upon disclosure of privy information.
374
-
375
-
376
- Cell Phone Usage
377
- Emp
378
- loyees shall not use cell phones for personal purposes while engaged in work activities. If the
379
- employee needs to use the cell phone for important personal reasons, the employee shall excuse
380
- themselves from the premises. Keep your cell phone on silent/vibrate. Use it responsibly, and make sure
381
- that you do not disturb others while answering your calls.
382
- Computer, E-Mail and Internet Policy
383
- MartechSol maintains certain policy requirements for use of its “electronic communication systems.”
384
- “Electronic communication systems” include computers, file servers, electronic information storage
385
- systems, internal and external electronic mail systems, voice mail systems, local area networks and
386
- any other system or technology used now or in the future by MartechSol to store or transmit data of
387
- any kind electronically.
388
- Our
389
- policy requires the following:
390
- 1. Employees shall use the electronic communications systems for business purposes.
391
- 2. Logicose’s electronic communications systems and all information transmitted or received by, or
392
- stored or contained in, these systems are the property of MartechSol. No employee shall have
393
- any property rights in or expectation of privacy for any information, even if of a personal nature,
394
- communicated or received through Logicose’s electronic communications systems. MartechSol
395
- may review, delete and copy any such information without first obtaining the consent of the
396
- employee who created, sent or received the information. No employee, including system
397
- administrators and Supervising Authority, shall use any electronic communications systems for
398
- purposes of satisfying idle curiosity about the affairs of others. There must be a business purpose
399
- for obtaining access to the files or communications of others.
400
- 3. No employee shall use the electronic communications systems in a manner that constitutes a
401
- violation of provincial or federal law or that is offensive to others.
402
- 4. Due to the threat to system stability posed by computer viruses and system incompatibility, no
403
- employee shall install or download software from the Internet or elsewhere on his or her
404
- computer without the supervision of the office administrator.
405
- If you have any questions about the policy or its administration, please contact your Supervising
406
- Authority. Violations of the policy may result in disciplinary action up to and including termination.
407
- USBs cannot be used on any office computer. If necessary, please contact the System Administrator.
408
- The completion of assigned work is the employee’s responsibility. If an employee has any issues
409
- regarding their PC or internet, they are to raise a trouble ticket immediately.
410
- The use of the following is prohibited:
411
-  Social media websites (e.g. Facebook)
412
-  Chatting websites (e.g. Meebo)
413
-  Chatting applications (e.g. MSN Messenger, Skype)
414
-  Online gaming websites (e.g. miniclips.com)
415
-  E-mail sites (e.g. Hotmail)
416
-
417
-
418
-  Political Websites
419
-  Pornography
420
- Tobacco Use
421
- MartechSol maintains a smoke-free workplace for all of its employees, which is why smoking is
422
- prohibited in the workplace. Employees who must smoke are encouraged to do so away from Company
423
- property and during work and/or meal breaks only.
424
- Grievance
425
- Procedure
426
- MartechSol has a grievance procedure to make sure that problems and complaints an employee has
427
- concerning his or her treatment or working conditions is called to our attention. This does not
428
- include computer-related issues or similar technical issues. For such problems, the trouble ticket
429
- system is provided. Employees with a problem should take the following steps:
430
- 1. Explai
431
- n the problem to your immediate Supervising Authority. Allow 48 hours for an answer.
432
- 2. If you are still not satisfied, put your grievance in writing and submit it to MartechSol President via
433
- the Feedback Form on the Portal. If possible, a decision will be given within three working days.
434
- Copyright Violation
435
- Any work performed within the premises of MartechSol for its clients, employees, or the Company itself
436
- is under the sole ownership of MartechSol or those that the company transfers the rights to. Employees
437
- cannot claim ownership of this work or use it outside company premises for other purposes, this
438
- includes creating personal records on computers or e-mail accounts that aren’t the property of
439
- MartechSol. Employees cannot use this work as reference to anyone else either, unless they get written
440
- consent from MartechSol. Listed below are some examples of copyright violation:
441
- Showing, sharing, using, creating a personal record of, or claiming ownership or involvement in the
442
- creation of:
443
-  Co
444
- py written
445
-  Software developed
446
-  Client contact information
447
-  Payments received at MartechSol
448
-  In
449
- tellectual property
450
- Data Theft
451
- Any data relating to MartechSol, its clients, or employees is under the sole ownership of MartechSol.
452
- Employees cannot use or transmit this data, as it is not under their ownership. Employees cannot use
453
- this data as reference either.
454
- Archiving such work on external hard drives, online, cloud, and/or on computers that are not property
455
- of MartechSol is a breach of policy.
456
-
457
-
458
- Separation from the Company
459
-  Prio
460
- r to Confirmation:
461
- o Minimum one day notice period, or as communicated in the offer letter, is required
462
- during probationary period on behalf of the employee.
463
- o If employee performance/behavior is not up to Company expectations, then the
464
- Supervising Authority has the right to end the probation period and terminate the
465
- employee.
466
-  After Confirmation:
467
- o The employment relationship may be terminated either by you or by the company with a
468
- prior written notice of one month or salary in lieu thereof, unless mentioned below. It is,
469
- however, understood that no notice is required in case your services are terminated on
470
- grounds of misconduct, willful neglect of duty, breach of trust or any other dereliction of
471
- duty or misdemeanor prejudicial to the interest of the company. It is further understood
472
- that any financial indebtedness of yours, standing to the detriment of the company’s
473
- prestige will amount to a very serious misdemeanor towards the company
474
- o A minimum notice period of at least 30 days is to served unless listed below otherwise:
475
- Managerial Roles
476
- Vice Team Lead 30 days
477
- Team Lead 60 days
478
- Group Team Lead 90 days
479
- Exit Interviews
480
- All employees separating from MartechSol must complete an exit interview with their immediate
481
- supervisor. An exit interview gives the Human Resource Department the opportunity to obtain feedback
482
- from employees as they transition out of their positions.
483
- Final Settlement
484
- All employees must undergo a final settlement before they separate from the organization. All
485
- adjustments will be made according to below mentioned procedures:
486
-  Bef
487
- ore Salary is Processed:
488
- If any employee resigns before the salary is processed of any given month, their adjustments
489
- (loans, advances, pro-rata leaves, etc.) will be made in the same month and the salary will be
490
- deposited along with the rest of the employees. The remaining amount of the following month’s
491
- salary will be processed as per the payroll process. No adjustments will be made in the following
492
- salary except for early exit. For example, someone resigns on the 20th of April and his last working
493
- day is the 20th of May, the adjustments will be made in the month of April’s salary. The salary for
494
- the remaining days of the notice period to be served in May will be processed with May’s payroll
495
- with Early Exit deduction of 11 days (21st May-31st May).
496
-  After/At the time Salary is Processed:
497
- If any employee resigns once the salary is processed of any given month, their adjustments (loans,
498
- advances, pro-rata leaves, etc.) will be made in the following month and the salary will be
499
- deposited along with the rest of the employees. For example, someone resigns on 25th of April and
500
-
501
-
502
- his last working day is on the 25th of May, the adjustments will be made in the month of May’s
503
- salary.
504
- Return of Property
505
- Employees who separate from MartechSol service must return all issued property such as identification
506
- cards, health insurance card, company maintained vehicle, and intellectual property of the organization.
507
- Acceptability
508
- Any changes in Policies, Rules, and Code of Conduct will first be shared through the MartechSol Portal.
509
- A reasonable period of time will be allotted to discuss suggestions and amendments in changes before
510
- its implementation.
511
- Timing In to work after date of implementation implies that you understand and will abide by
512
- organization policy.
513
- Work
514
- Rules
515
- Any work performed within the premises of MartechSol is under the Company’s ownership and/or its
516
- clients. Employees cannot claim ownership of any work done in or for MartechSol, neither can they use
517
- it in the future.
518
- All work performed at MartechSol has to be done in a legal and ethical manner so there is never
519
- any copyright violation.
520
- Employees shall perform their assignments within the specifics of the position description. Employees
521
- who consistently fail to conform to the specifics of their position description or exhibit inappropriate
522
- behavior or poor performance shall be required to meet with their supervisor.
523
- This meeting will attempt to identify the problems, find ways to improve the situation and suggest
524
- adequate solutions, concluding with a recommended course of action and an appropriate time frame
525
- in which the employee will be expected to improve to the satisfaction of MartechSol. Details of the
526
- meeting will be documented, signed by all parties as a correct representation of points discussed and
527
- placed in the employee’s personal file.
528
- Disciplinary action and corrective measures are taken at the discretion of the Management at
529
- MartechSol. The possible steps for disciplinary action are:
530
- 1. Co
531
- unseling/Verbal Warning
532
- 2. One or more formal written warnings through email
533
- 3. Issuance of Notice
534
- 4. Suspension
535
- 5. Dismissal
536
- The choice of options depends on the seriousness of the behavior. Exceptions or deviations from the
537
- progressive discipline sequence may occur whenever the Supervising Authority, in conjunction with the
538
- President/Director, deems that circumstances warrant.
539
- For level 1 offense, discussions between the employee and his or her supervisor will occur to allow the
540
- employee to correct the situation. When a warning notice is issued, it becomes a part of an employee's
541
-
542
-
543
- record and is considered when evaluating an employee for promotion, transfer, training or additional
544
- discipline. Various offenses have been divided into different levels according to severity.
545
- Three warning notices within twelve (12) months’ time, regardless of the type of level offense, will result
546
- in discharge.
547
- Level 1 Actions
548
- These actions are taken for behaviors such as:
549
-  Unautho
550
- rized or excessive absence, tardiness or early timing out
551
-  Unauthorized time away from work station
552
-  Slowing or interfering with the work of other employees
553
-  Failure to notify supervisor of incompletion of assigned work before leaving
554
-  Obscene, abusive, harassing, or disruptive language or behavior
555
-  Failure to perform assigned job responsibilities
556
-  Failure to follow prescribed work procedures
557
-  Failure to notify supervisor of absences
558
-  Neglect of organization property
559
-  Excessive personal use of cell telephone and/or email
560
-  Work shift not completed
561
- Procedure For Dealing With Level 1 Behavior: These procedures are at the discretion of the Supervising
562
- Authority.
563
-  Counseling/Verbal warning
564
-  Formal written warning in the form of email
565
-  Issuance of notice
566
-  Suspension
567
- Level 2 Actions
568
- These are more serious and must be dealt with firmly and immediately. Typical behaviors in this level
569
- include:
570
-  Reoccurrin
571
- g tardiness without reasonable explanation
572
-  Absences without approved leave
573
-  Refusal to comply with instructions of a supervisor
574
-  Conduct endangering the safety of the employee, co-workers or members
575
-  Working when ability is impaired by the use of alcohol, and/or illegal drugs
576
-  Unscheduled leaving from the workplace without informing supervisor
577
-  Habitual sleeping during work hours
578
-  Unauthorized use of organization materials and supplies
579
-  Fighting or threatening violence in the workplace
580
-  Unauthorized possession of weapons on organization property
581
-
582
-
583
-  Knowingly timing in another employee
584
-  Sharing portal login info
585
-  Inappropriate usage of internet, or any usage of internet unrelated to work during work hours
586
-  Harassing or bullying coworkers
587
- Procedure for dealing with Level 2 behavior: These procedures are at the discretion of the Supervising
588
- Authority.
589
-  Issuance of Notice
590
-  Suspension or probation
591
-  Termination
592
- Level 3 Actions
593
- These are b
594
- ehaviors that are serious enough to justify either a suspension or, in extreme situations,
595
- termination of employment without following the preceding disciplinary steps. Behaviors for which
596
- immediate termination can be justified include, but are not limited to, the following:
597
-  Sexual harassment
598
-  Involvement in any income-generating activity without the consent of supervising authority
599
-  Insubordination, or the refusal to comply with the specific instructions of a supervisor in the
600
- context of an assigned job duty
601
-  Falsification of personnel records, time records, or any other organization documents and records
602
-  Fighting during work time or on work premises
603
-  Use of, or possession of, alcohol or illegal drugs during work time or on work property
604
-  Damaging, defacing, or misusing organization property or the property of coworkers
605
-  Theft, misappropriation, embezzlement, unauthorized possession or removal of organization
606
- property or the property of employees or customers
607
-  Immoral or indecent conduct which occurs on organization property
608
-  Possession of explosives, firearms, or other dangerous weapons on work premises
609
-  Failure to report an absence for a three-day period without a satisfactory explanation
610
-  Unauthorized release of work-related data or information
611
-  Continued unsatisfactory job performance
612
-  Violation of the organization's conflict of interest/ethical standards
613
-  Data theft: Stealing any digital or other type of data (e.g. email, company records, client records)
614
- which belongs to MartechSol.
615
-  Copyright violation: Showing ownership of specific work performed at MartechSol. Discussion
616
- of clients, websites, revenue generated, payments, or any work-related details outside the
617
- Company’s premises.
618
-  Ot
619
- her behaviors that, in the opinion of the Supervising Authority, seriously threaten the well-
620
- being of co-workers.
621
- In case of any ambiguity, please contact your supervising authority.
622
-
623
-
624
- Procedure for dealing with Level 3 behavior: These procedures are at the discretion of the Supervising
625
- Authority.
626
-  Suspension
627
-  Termination
628
- Disciplinary Procedures
629
- MartechSol may use progressive discipline as a means of enforcing minor violations of the work rules.
630
- Progressive discipline may include steps such as counseling, written warning, and suspension.
631
- Depending on the nature of the misconduct and the position involved, steps in the process may be
632
- omitted or employment may be immediately terminated. The decision whether to apply progressive
633
- discipline in any particular situation will rest within the sole discretion of management.
634
- Examples of misconduct which will usually result in immediate discharge include theft, fighting or
635
- physical assault, insubordination, dishonesty, falsification of time cards, production records, or other
636
- Company documents, possession of alcohol or illegal drugs in the workplace, and disloyalty or breach of
637
- confidentiality. This list is not intended to limit our right to discharge in other situations when we believe
638
- termination of employment is appropriate.
639
- Employee Benefits
640
- Referral Program
641
- Any employ
642
- ee can benefit from the referral program by referring qualified friends or acquaintances.
643
- The employee who has referred an individual is eligible for a PKR. 5,000 bonus once the new
644
- candidate joins the organization.
645
- Game Room
646
- In order to promote a relaxed environment and provide employees with more forms of
647
- entertainment, MartechSol has set up a game room in Room 301.
648
- This game ro
649
- om is accessible during lunchtime, so the morning shift has access to it from 1:30-2:30 pm
650
- and the evening/night shift has access from 9:00-10:00pm. The game room can also be used after work.
651
- Females are to use the game room on Monday, Wednesday, and Friday during lunchtime. Males should
652
- use the game room on Tuesday, Thursday, and Saturday during lunch hour.
653
- Holidays and Holiday Pay
654
- MartechSol observes all Federal holidays as paid holidays. Due to our nature of work, employees
655
- may be asked to work on specific holidays, according to their availability. They will be awarded
656
- compensatory leave allowance or compensatory leave (employee’s choice), which they can utilize at
657
- their convenience.
658
- To qualify for holiday pay, an employee must have worked his or her regularly scheduled shift on the
659
- day preceding the holiday and must work his or her regularly scheduled shift on the day following the
660
- holiday.
661
-
662
-
663
- Pay Period and Paychecks
664
- For pay c
665
- alculation purposes, the workweek (or “pay period”) will commence 12:01 a.m. on Sunday
666
- morning and end at 12:00 p.m. on Saturday evening. Employees will be paid monthly on (payday), for the
667
- preceding month. If payday falls on a holiday, then paychecks will be provided the day before the
668
- holiday.
669
- Employees are expected to immediately report any mistakes in their paychecks. Underpayments will be
670
- corrected as soon as possible. If it is not practical to issue an employee a new paycheck to correct any
671
- overpayment, the overpayment will be deducted from the employee's next paycheck.
672
- Salary deduction for days off will be on “per-day” basis. {(Gross Salary x 12) / 365}.
673
- Payroll for June and December
674
- The salary for June and December will be processed and transferred during the first week of July and
675
- January respectively. This is to ensure that the transition from the current to the revised salary is smooth
676
- and all adjustments for the preceding month are processed correctly.
677
- Leaves
678
- Prior to Confirmation:
679
-  No leaves are allowed prior to your confirmation.
680
-  In case you skip a day, you will be marked absent, and your salary will be deducted for the day.
681
-  Prior approval from your supervising authority is required for situations where you would be
682
- unable to come into work.
683
-  If you are absent from work as a result of sickness or any other unforeseeable personal matter,
684
- you are required to notify your supervisory authority or the HR department by telephone or SMS
685
- within two hours of your scheduled shift starting time.
686
- After Confirmation:
687
- Sick Leaves
688
- Definition:
689
- Sick Leave is defined as the period of time an employee is absent from work with full pay as a
690
- result of an illness or injury.
691
- Policy:
692
-  All full time confirmed employees are entitled for a maximum of 8 days of sick leave per annum.
693
-  Sick leaves cannot be carried forward to the next year and neither can they be encashed.
694
-  Each year on the 1st of January, your leave account will be credited with the sick leave
695
- entitlement.
696
-  If anyone is confirmed in MartechSol during the year, then his/her sick leave entitlement will be
697
- on a pro-rata basis.
698
-  In case of separation from the company during the year, sick leave availed will be adjusted on
699
-
700
-
701
- pro- rata basis from your final salary payment.
702
-  If an employee takes sick leave from work starting any day of the week, any weekend or holiday
703
- that falls in between will be counted as a full day sick leave.
704
-  Sick leave availed in excess of entitlement will be treated as casual or annual leave (if available
705
- and subject to approval from reporting authority); incase of insufficient casual/annual leave
706
- balance, excess sick leave will be treated as unpaid leave.
707
-  During notice period, no sick leave will be entertained even if the leave shows credit balance.
708
- Process and Documentation:
709
-  Planned appointments, surgery, or any medical instance which can be deemed as anticipated is
710
- subject to prior approval from your supervising authority.
711
-  If you are absent from work as a result of sickness or injury, you are required to notify your
712
- supervisory authority or HR department by telephone or SMS within two hours of your
713
- scheduled shift starting time (or before if possible). Failure to comply with the process may result
714
- in absent without leave.
715
-  You must apply for sick leave immediately after returning from your sick leave by submitting
716
- your leave application to the HR department after approval from your supervising authority.
717
-  The submission of a medical certificate or relevant documentation when applying for a sick leave
718
- is mandatory, the leave will be considered absent without pay if proper documentation is not
719
- submitted.
720
-  Sick leave can only be availed when sick and cannot be claimed for other purposes. Falsifying
721
- information or using sick leave for a purpose other than illness or injury will make you liable for
722
- disciplinary action.
723
- Casual Leaves
724
- Definition:
725
- Casual leaves may be availed by employees in cases of urgent or unforeseen contingencies.
726
- These cases include, but are not limited to, situations which may arise on account of unforeseen
727
- circumstances e.g. birth/illness/death in the immediate family or of a close relative.
728
- Any personal reason that prevents you from coming into work can be used for your casual leave.
729
- Policy:
730
-  All full time confirmed employees are entitled for a maximum of 10 days casual leave per annum.
731
-  Casual leaves cannot be carried forward to the next year.
732
-  Casual leaves that have not been exhausted over the calendar year will be subject to encashment
733
- with the paycheck of the following month.
734
-  Each year on the 1st of January, your leave account will be credited with the casual leave
735
- entitlement if eligible.
736
-  If anyone is confirmed in MartechSol during the year, then his/her casual leave entitlement will
737
- be on a pro-rata basis.
738
-  In case of separation from the company during the year, casual leave availed will be adjusted on
739
-
740
-
741
- pro-rata basis from your final salary payment.
742
-  If an employee takes casual leave from work starting any day of the week, any weekend or
743
- holiday that falls in between will be counted as a full day casual leave.
744
-  Casual leave cannot be taken for 3 consecutive days at any given time. If leave availed is for or in
745
- excess of 3 days, the entire period will be treated as unpaid leave. However, if circumstances
746
- warrant, the department head may approve the first 3 days to be treated as casual leave, and
747
- only the additional days to be treated as annual leaves (if available).
748
-  Employees can take up to 2 consecutive casual leaves. If the number of casual leaves is 3 or
749
- more, the whole duration will be treated as unpaid automatically unless approved by the line
750
- manager.
751
-  In case of any urgent requirement, your reporting authority may ask you to report to work during
752
- your casual leave.
753
-  Combining casual leave with a weekly holiday or a public holiday shall be inadmissible and that
754
- additional leave shall be adjusted as leave without pay. For example, if a public holiday falls on
755
- Friday and any employee takes leave on Saturday, all three days will be counted as casual leave.
756
-  During notice period, no casual leave will be allowed even if the leave shows credit balance.
757
- Documentation and Process:
758
-  If you are absent from work as a result of any urgent personal responsibilities or any
759
- unforeseeable circumstances, you are required to notify your supervisory authority or HR
760
- department by telephone or SMS within two hours of your scheduled shift starting time (or
761
- before if possible). Failure to comply with the process may result in absent without leave.
762
-  You must apply for casual leave immediately after returning from your casual leave by submitting
763
- your leave application to the HR department after approval from your supervising authority.
764
-  In cases of planned or known circumstances, an employee must apply for casual leave to the
765
- immediate supervisor for approval in advance.
766
- Annual Leaves
767
- Definition:
768
- The purpose of annual leave is to provide employees with a reasonable period of rest and
769
- a significant break from the workplace in order to maintain a healthy work life balance.
770
- Policy:
771
-  All Full-time confirmed employee(s) shall be entitled to 14 days paid annual leave after
772
- completion of one year of service.
773
-  Annuals leaves are not subject to encashment, except for TL roles or greater and for the Sales &
774
- Support Department.
775
-  Up to 7 annual leaves can be carried forward to the next year, if approved by supervising
776
- authority, for the following reasons: 
777
-  Marriage
778
-  Obligation outside the country (Hajj, Residence Visa Process, etc.)
779
-  Or for any planned reason that warrants extended leave. 
780
-
781
-
782
-  Carried forward annual leaves can’t be carried forward for more than a year. 
783
-  If any employee completes one year of service at MartechSol during the calendar year, then his/
784
- her annual leave entitlement will be on a pro-rata basis for that year.
785
-  In case of separation from the company during the year, annual leave availed will be adjusted on
786
- pro-rata basis from your final salary payment.
787
-  Annual leave may be availed at one time during the year or may be divided and availed at various
788
- times during the year, subject to approval.
789
-  Annual leaves will be granted in keeping with the operational exigencies of the organization, and
790
- will be scheduled by the supervisor to meet the requirements of individual employees as far as
791
- possible.
792
-  Management reserves the right to refuse a leave request, allow a partial request, revoke
793
- approval if already granted, and/or to recall an employee before expiry of the leave period.
794
-  If you are hospitalized during your annual leave and produce a valid medical certificate stating
795
- the fact, then these days will be adjusted as sick leave (if available).
796
-  If an employee takes annual leave from work starting any day of the week, any weekend or
797
- holiday that falls in between will be counted as an annual leave.
798
-  During notice period, no annual leave will be allowed even if the leave shows credit balance.
799
- Documentation and Process:
800
-  A completed leave application form is to be submitted for approval in advance to the HR
801
- department prior to the anticipated start of the leave accordingly:
802
- Number of leaves Approval time required
803
- 1-3 days Before 2 days
804
- 4-7 days Before 1 week
805
- More than 7 days Before 1 month
806
-  An employee who wishes to obtain annual leaves shall apply in writing to HR in advance before
807
- the commencement of leaves.
808
-  If the leave request is refused or postponed on account of company exigencies, the fact of such
809
- refusal or postponement and reasons thereto shall be conveyed to the employee concerned.
810
-  If the requested leave is granted the employee will proceed on his leave on the approved date.
811
-  If an employee remains absent beyond the approved period of leave, he shall be liable for
812
- disciplinary action unless the employee has justifiable reasons for absenteeism.
813
-  Annual leaves may only be availed after prior approval has been granted, however in case of an
814
- emergency, annual leaves may be entertained subject to your supervising authority’s discretion.
815
- Hajj Leave
816
- Definition:
817
- Employees who wish to make their pilgrimage to Mecca during Dhu'l Hijja may do so during
818
- their tenure at MartechSol. These leaves are separate from the employee's annual leaves or any
819
- other leave which he/she is entitled to.
820
-
821
-
822
- Policy:
823
-  All full time permanent employees with 4 years of service are entitled for paid Hajj Leave for 10
824
- days.
825
-  Any leaves beyond 10 days needs to come from your annual or casual leaves.
826
-  Hajj leave will be granted only once during the whole employment period.
827
-
828
- Documentation and Process:
829
-  Your leave application must be submitted three months in advance for the necessary approval.
830
-  Copy of your Hajj Passport/Visa needs to be submitted to HR.
831
- Maternity Leave
832
- Definition:
833
- A period of approved absence for female employees that wish to continue employment
834
- at MartechSol after delivery, granted for the purpose of giving birth and taking care of
835
- newborn children.
836
- Policy:
837
-  All full-time female employees shall be entitled to 90 days paid maternity leave, taken no later
838
- than two weeks before the expected delivery date and is only applicable after completion of one
839
- year of service.
840
-  This entitlement shall be available only twice during the entire period of service.
841
-  Premature birth shall be considered as maternity leave. In case of still birth, female employee
842
- shall be entitled to 30 days paid maternity leave.
843
- Documentation and Process:
844
-  Maternity Leave form needs to be submitted and approved five months before planned
845
- maternity leave.
846
-
847
- Paternity Leave
848
- Definition:
849
- A period of approved absence for male employees granted after or shortly before the birth of his
850
- child.
851
- Policy:
852
-  All full-time male employees are entitled to 3 days paid paternity leave.
853
-  This entitlement shall be available only twice during the entire period of service.
854
-
855
-
856
-
857
-  Male employees may combine other leaves with paternity leave subject to approval from
858
- supervising authority. 
859
-  Premature birth shall be considered as paternity leave. In case of still birth, male employee shall
860
- be entitled to up to 3 days paid paternity leave.
861
- Documentation and Process:
862
-  Application for paternity leave should be submitted to HR department at least two months in
863
- advance supported by the gynecologist’s certification.
864
- Bereavement Leave
865
- Definition:
866
- Bereavement leaves are paid leaves for employees that cover absences related to the death of
867
- immediate family members.
868
- Policy:
869
-  All full-time employee(s) shall be entitled to 3 days paid bereavement leave.
870
-  Bereavement leave will normally be granted unless there are unusual business needs or staffing
871
- requirements. An employee may, with his or her supervisor’s approval, use any available leaves
872
- for additional time off as necessary.
873
- Documentation and Process:
874
-  You must apply for bereavement leave immediately after returning from your leave by
875
- submitting your leave application to the HR department after approval from your supervising
876
- authority.
877
- Unauthorized Leaves
878
-  Any
879
- absence without prior approval from your reporting authority will be considered as an
880
- unauthorized leave.
881
-  All unauthorized and/or unreported absences shall be considered Absences without Leave,
882
- (AWOL). Each unauthorized leave will result in a disciplinary notice and will be added in your file.
883
- 3 unauthorized leaves in a quarter will result in a severe disciplinary action.
884
-  In case you were not able to inform your reporting authority due to some unavoidable reasons,
885
- you may discuss it with your reporting authority later. Based on discretion, he/she may authorize
886
- your leave.
887
-  For employees serving their probationary period, failure to communicate with the respective
888
- authority for more than 3 consecutive days of leave will result in termination.
889
-  For permanent employees, failure to communicate with the respective authority for more than 7
890
- consecutive days of leave will result in termination.
891
-
892
-
893
- Unapproved Absence Without Pay
894
- Definition:
895
- Any leave, in excess of the 10 Casual leaves that you are entitled to, is an Absence without Pay.
896
- These leaves, when taken without prior approval from your supervising authority are considered
897
- Unapproved Absence without Pay.
898
- Policy:
899
-
900
-  Unapproved Absence without Pay will result in salary deduction(s) per day absent.
901
-  Taking more than two consecutive Absences without Pay prior to the weekend will result in salary
902
- deduction for the weekend. For example, if an employee is absent on Thursday, Friday, and
903
- Saturday then Sunday will also be marked absent. In other words, employees must report to work
904
- on the last working day of the week to avoid further deductions. 
905
- Leave Encashment
906
- Each employee is entitled to a number of Sick, Casual, Annual, and other leaves. However, only Casual
907
- leaves are subject to encashment for specific departments.
908
- Listed below is breakdown of Department eligibility of leave encashment:
909
- Department Casual Annual
910
- Copywriting Yes No
911
- SEO Yes No
912
- Sales Yes Yes
913
- Software Yes No
914
- Networking Yes No
915
- Human Resources Yes No
916
- Admin Yes No
917
- Other Benefits
918
- Loans & Advance Salary
919
- Definition:
920
- The purpose of loans and advance salary is to meet an emergency need that can’t be
921
- accommodated through other financial arrangements. Only genuine emergency needs such as
922
- extraordinary medical costs not covered by insurance, or any other extreme financial or medical
923
- emergency will be considered. The decision to approve or reject a loan/advance salary request will
924
- be taken by a 3-person committee which will comprise of line manager/head of department, a
925
- representative from HR, and a representative from finance.
926
- Policy:
927
-  Decisions regarding loan requests will be relayed within 3 to 7 working days. For extremely urgent
928
- matters, the committee will take a decision within 27 hours of request sent.
929
-  The decision of the committee will be final and non-negotiable.
930
-  A loans/advance salary can only be taken once in 6 months.
931
-
932
-
933
-  If an employee has an ongoing loan/advance salary, he/she will not be allowed to avail another
934
- loan/advance salary.
935
-  Repayment period is up to 6 months.
936
-  Loan amount can’t exceed 3 months’ salary.
937
-  Advance salary amount shall not exceed from your monthly salary.
938
- Documentation & Process:
939
-  If possible, the loan/advance salary form should be submitted at least a week (7 days) before it is
940
- required.
941
-  The form can be acquired from HR and must be completed including all the signatures required.
942
-  The necessary documentation & supporting documents should be provided along with the filled
943
- form.
944
- Maternity (Pregnancy) Benefit
945
- Definition:
946
- This benefit provides financial assistance to female and male employees to cover the cost of childbirth and
947
- other delivery related expenses.
948
- Policy:
949
-  Male and female employees that have completed at least one year of service can avail maternity
950
- benefit of PKR 50,000.
951
-  Maternity benefit can be utilized twice during tenure at MartechSol.
952
-  This benefit also covers stillbirth.
953
- Documentation & Process:
954
-  Maternity Benefit Form and relevant documentation must be provided at least 60 days before
955
- expected delivery.
956
-  Employees will be awarded this benefit a week before date of expected delivery.
957
-  In case of premature delivery, employees can either contact Supervising Authority or Admin
958
- Department to arrange the benefit urgently or they may pay out of pocket at the time and avail the
959
- benefit when they rejoin the office.
960
-
961
-
962
- Company Maintained Vehicle
963
- Definition:
964
- MartechSol provides company maintained cars to employees at certain designation levels. The
965
- policy varies across different departments. The company maintained car will be owned and
966
- maintained by the company for the use of the employee.
967
- Policy:
968
-  Employees should drive their allotted vehicle in a safe and responsible vehicle. They must abide by
969
- driving laws.
970
-  Scheduled maintenance will be carried out by the company after every 5,000 KMs or 3 months
971
- (whichever comes first).
972
-  Employees are requested to keep a copy of the following documents in the company vehicle:
973
- o Authority Letter
974
- o Motor Vehicle Tax paper
975
- o Insurance paper
976
- o Copy of the first page of the vehicle’s registration book
977
- Documentation & Process:
978
-  Prior to vehicle allocation, employee must provide a copy of valid driving license of self (or driver)
979
- for company record.
980
-  All maintenance and wear & tear requests need to be made through submitting a ticket on portal or
981
- by contacting Admin Department.
982
-  In case of an accident:
983
- o All accidents must be reported to the Admin Department.
984
- o If the company vehicle is stolen, report the theft immediately to the local police and to the
985
- Admin Department. Obtain a copy of the police report filed.
986
- Healthcare Insurance – For Self & Spouse
987
- Health care benefits are provided to all full-time employees and their spouse. Complete details of this
988
- program are provided below:
989
- “ANNEXURE A”
990
- PROCEDURE FOR UTILIZATION OF HOSPITALIZATION EXPENSE BENEFIT
991
- Emergency Hospitalization
992
- In case of emergency, approach the nearest hospital. In case it is a PANEL HOSPITAL
993
- 1. Identify yourself as an NJI insured.
994
- 2. Produce Health Card / Letter of Authority
995
- 3. Get the treatment
996
- 4. Get the treatment on credit, up to the limits available.
997
- 5. Pay only the amount that exceeds the entitlement, if any, before discharge.
998
-
999
-
1000
- In case it is a NON-PANEL HOSPITAL
1001
- 1. Inform NJI within 24 hours of the hospitalization.
1002
- 2. Pay cash for the treatment.
1003
- 3. Submit all original bills /supporting documents* with our claim form for reimbursement,
1004
- within 30 days of discharge from the hospital.
1005
- 4. Settlement of claim will be done in line of the policy terms.
1006
- “ANNEXURE B”
1007
- PROCEDURE FOR UTILIZATION OF HOSPITALIZATION EXPENSE BENEFIT
1008
- Emergency Hospitalization
1009
- If hospitalization is advised by a licensed physician
1010
- In case it is a PANEL HOSPITAL
1011
- 1. Approach any of our panel hospital with our Letter of Authority / Health Card
1012
- 2. Identify yourself as an NJI insured
1013
- 3. Get the treatment on credit, up to the limits available.
1014
- 4. Pay only the amount that exceeds the entitlement, if any, before discharge.
1015
- In case it is a NON-PANEL HOSPITAL
1016
- 1. Send us the cost estimate from the concerned doctor of the treatment with details, for prior
1017
- approval.
1018
- 2. Get the treatment and pay cash for the treatment.
1019
- 3. Submit all original bills /supporting documents* including our approval letter with our claim
1020
- form for reimbursement, within 30 days of discharge from the hospital.
1021
- 4. Settlement of claim will be done in line of prior approval given and other policy terms.
1022
- *Supporting Documents:
1023
-  Duly completed original NJI Claim Form
1024
-  Original itemized bill/invoice (breakup of charged) on hospital
1025
- bill-book
1026
-  Discharge card / clinical summary
1027
-  Copy of NJI card or letter of admission
1028
-  Diagnostic reports
1029
-  Doctors prescriptions
1030
-  Original pharmacy vouchers
1031
-  Original payment receipts
1032
-
1033
-
1034
- JUBILEE GENERAL INSURANCE COMPANY LTD
1035
- GROUP HEALTHCARE INSURANCE PROPOSAL FOR
1036
- MartechSol
1037
- Hospitalization & Related Benefits
1038
- Plan A Plan B Plan C Plan D
1039
- H&R Limits (Per Person / Per Year) Rs.500,000 Rs.400,000 Rs.300,000 Rs.150,000
1040
- Room & Board (per day) Rs.16,360 Rs.6,720 Rs.6,720 Rs.1,800
1041
- Per Hospitalization
1042
- Pre-Hospitalization Sub Limit (Diagnosis, 30 Days 30 Days 30 Days 30 Days
1043
- Consultation, & Medicines)
1044
- Post-Hospitalization Sub Limit (Follow-Ups) 30 Days 30 Days 30 Days 30 Days
1045
- Daycare Surgeries & Specialized
1046
- Investigations In Outpatient Settings
1047
- Including but not limited to:
1048
- COVERED
1049
- Dialysis, Cataract Surgery, MRI, CT Scan,
1050
- Endoscopy, Thallium Scan, Angiography, Treatment
1051
- of Fractures, Local Road Ambulance for
1052
- Emergencies only, Emergency Dental Treatment
1053
- due to accidental injuries within 48 hours (for pain
1054
- relief only).
1055
- Plan A Presidential Layer
1056
- Plan B Associate Managers, Managers & Senior Managers
1057
- Plan C Executives, Senior Executives & Assistant Managers
1058
- Plan D Office Boys & Other Support Staff
1059
-  Send us the cost estimate from the concerned doctor of the treatment with details, for prior
1060
- approval. Get the treatment and pay cash for the treatment.
1061
-  Submit all original bills /supporting documents* including our approval letter with our claim form
1062
- for reimbursement, within 30 days of discharge from the hospital.
1063
-  Settlement of claim will be done in line of prior approval given and other policy terms.
1064
- Supporting documents
1065
- Itemized hospital bill
1066
- Discharge card/clinical summary
1067
- Diagnostic reports
1068
- Prescriptions
1069
- Payment receipts
1070
-
1071
-
1072
- EOBI
1073
- Employee Old Age Benefit Income is a benefit provided to all full-time employees upon confirmation.
1074
- The sole purpose of EOBI is to provide compulsory social insurance to employees. It extends following
1075
- benefits to insured persons or their survivors:
1076
- • Old-Age Pension
1077
- • Survivor's Pension
1078
- • Invalidity Pension
1079
- • Old-Age Grant 
1080
- RATES:
1081
- • 1- Employer's Contribution @ 5 % of the worker's minimum wages (i.e. Rs. 8000) - Rs. 400/= Per
1082
- Month
1083
- • 2- Employee's Contribution @ 1 % of the worker's minimum wages (i.e. Rs.8000) - Rs. 80/= Per
1084
- Month
1085
- Provident Fund
1086
- Provident Fund is an investment/ saving fund contributed to - by employees & employers, out of which a
1087
- lump sum amount is provided to each employee on retirement. This benefit is provided to all full time,
1088
- confirmed employees.
1089
- Click Here To View PF Policies in Detail
1090
-
1091
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/floaitng-icon/KhloeSAC.lottie DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:532a144eeea5a1a6e49ab22ae4c0dc4f94e12cf8540e3514f1bc676d929eeac2
3
- size 462684
 
 
 
 
backup_2026-05-04/get_models.py DELETED
@@ -1,25 +0,0 @@
1
- import asyncio
2
- import os
3
- import httpx
4
- from dotenv import load_dotenv
5
-
6
- load_dotenv()
7
-
8
- async def main():
9
- api_key = os.getenv("GROQ_API_KEY")
10
- if not api_key:
11
- print("No API Key")
12
- return
13
- url = "https://api.groq.com/openai/v1/models"
14
- headers = {"Authorization": f"Bearer {api_key}"}
15
- async with httpx.AsyncClient() as client:
16
- resp = await client.get(url, headers=headers)
17
- if resp.status_code == 200:
18
- models = resp.json().get("data", [])
19
- for m in models:
20
- if "deepseek" in m["id"].lower() or "qwen" in m["id"].lower():
21
- print(m["id"])
22
- else:
23
- print(resp.status_code, resp.text)
24
-
25
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/martech_sol_logo.jpg DELETED
Binary file (6.68 kB)
 
backup_2026-05-04/requirements.txt DELETED
@@ -1,14 +0,0 @@
1
- fastapi
2
- uvicorn[standard]
3
- faiss-cpu
4
- sentence-transformers
5
- pydantic-settings
6
- pypdf
7
- python-dotenv
8
- numpy
9
- httpx
10
- gradio>=5.0.0
11
- python-multipart
12
- pandas
13
- openpyxl
14
- rank_bm25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/static/addon.html DELETED
@@ -1,543 +0,0 @@
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>Martechsol Assistant Widget</title>
7
- <style>
8
- /* Premium Design for Martechsol Assistant */
9
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
10
-
11
- #martech-chat-wrapper {
12
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
13
- }
14
-
15
- /* Floating Action Button (FAB) */
16
- #martech-chat-fab {
17
- position: fixed;
18
- right: 10px;
19
- bottom: 25px;
20
- width: auto;
21
- height: auto;
22
- background: transparent;
23
- border-radius: 50%;
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- cursor: pointer;
28
- z-index: 999999;
29
- transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.3s ease, box-shadow 0.3s ease;
30
- -webkit-tap-highlight-color: transparent;
31
- }
32
-
33
- #martech-chat-fab:hover {
34
- transform: scale(1.1);
35
- }
36
-
37
- #martech-chat-fab.is-active {
38
- background: #1e293b;
39
- box-shadow: 0 10px 25px rgba(30, 41, 59, 0.4);
40
- transform: scale(0.8);
41
- }
42
-
43
- /* Chat Window Styling */
44
- #martech-chat-box {
45
- position: fixed;
46
- right: 25px;
47
- bottom: 100px;
48
- width: 400px;
49
- height: 550px;
50
- background: #ffffff;
51
- border-radius: 16px;
52
- overflow: hidden;
53
- display: none;
54
- flex-direction: column;
55
- box-shadow: 0 12px 50px rgba(0, 0, 0, 0.2);
56
- z-index: 999998;
57
- border: 1px solid #e2e8f0;
58
- opacity: 0;
59
- transform: translateY(20px);
60
- transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
61
- }
62
-
63
- #martech-chat-box.is-visible {
64
- display: flex;
65
- opacity: 1;
66
- transform: translateY(0);
67
- }
68
-
69
- #martech-chat-header {
70
- background: #ffffff;
71
- padding: 16px 20px;
72
- border-bottom: 1px solid #f1f5f9;
73
- display: flex;
74
- align-items: center;
75
- gap: 12px;
76
- }
77
-
78
- @keyframes pulse-dot {
79
- 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
80
- 70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
81
- 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
82
- }
83
-
84
- #martech-chat-header .dot {
85
- width: 10px;
86
- height: 10px;
87
- background: #10b981;
88
- border-radius: 50%;
89
- animation: pulse-dot 2s infinite;
90
- }
91
-
92
- #martech-chat-header span {
93
- font-weight: 600;
94
- color: #1e293b;
95
- font-size: 15px;
96
- }
97
-
98
- /* Custom Chat Area Styling */
99
- #chat-messages {
100
- flex: 1;
101
- overflow-y: auto;
102
- padding: 16px;
103
- display: flex;
104
- flex-direction: column;
105
- gap: 12px;
106
- background: #f8fafc;
107
- scroll-behavior: smooth;
108
- }
109
-
110
- /* Custom Scrollbar for messages */
111
- #chat-messages::-webkit-scrollbar { width: 6px; }
112
- #chat-messages::-webkit-scrollbar-track { background: transparent; }
113
- #chat-messages::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
114
-
115
- .chat-message {
116
- max-width: 85%;
117
- padding: 12px 16px;
118
- border-radius: 16px;
119
- font-size: 14px;
120
- line-height: 1.5;
121
- word-wrap: break-word;
122
- white-space: pre-wrap;
123
- }
124
-
125
- .chat-message.user {
126
- align-self: flex-end;
127
- background: #1e293b;
128
- color: white;
129
- border-bottom-right-radius: 4px;
130
- }
131
-
132
- .chat-message.bot {
133
- align-self: flex-start;
134
- background: #ffffff;
135
- color: #334155;
136
- border-bottom-left-radius: 4px;
137
- border: 1px solid #e2e8f0;
138
- box-shadow: 0 2px 4px rgba(0,0,0,0.02);
139
- }
140
-
141
- /* Surgical Bolding & List Style */
142
- .chat-message b {
143
- font-weight: 800;
144
- color: #0f172a;
145
- }
146
- .chat-message.user b {
147
- color: #ffffff;
148
- }
149
-
150
- #chat-input-area {
151
- padding: 16px;
152
- background: #ffffff;
153
- border-top: 1px solid #f1f5f9;
154
- display: flex;
155
- gap: 8px;
156
- align-items: center;
157
- }
158
-
159
- #chat-input {
160
- flex: 1;
161
- border: 1px solid #e2e8f0;
162
- border-radius: 20px;
163
- padding: 12px 16px;
164
- font-size: 14px;
165
- outline: none;
166
- transition: border-color 0.2s;
167
- font-family: inherit;
168
- box-shadow: inset 0 1px 2px rgba(0,0,0,0.02);
169
- }
170
-
171
- #chat-input:focus {
172
- border-color: #10b981;
173
- }
174
-
175
- #chat-input:disabled {
176
- background: #f1f5f9;
177
- cursor: not-allowed;
178
- }
179
-
180
- #chat-send {
181
- background: #10b981;
182
- color: white;
183
- border: none;
184
- width: 44px;
185
- height: 44px;
186
- border-radius: 50%;
187
- display: flex;
188
- align-items: center;
189
- justify-content: center;
190
- cursor: pointer;
191
- transition: background 0.2s, transform 0.1s;
192
- }
193
-
194
- #chat-send:hover {
195
- background: #059669;
196
- }
197
-
198
- #chat-send:active {
199
- transform: scale(0.95);
200
- }
201
-
202
- #chat-send:disabled {
203
- background: #cbd5e1;
204
- cursor: not-allowed;
205
- transform: none;
206
- }
207
-
208
- /* Loading / Typing Animation */
209
- .typing-indicator {
210
- display: flex;
211
- gap: 4px;
212
- padding: 4px 2px;
213
- align-items: center;
214
- height: 18px;
215
- }
216
- .typing-indicator span {
217
- width: 6px;
218
- height: 6px;
219
- background: #94a3b8;
220
- border-radius: 50%;
221
- animation: typing-dots 1.4s infinite ease-in-out both;
222
- }
223
- .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
224
- .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
225
- @keyframes typing-dots {
226
- 0%, 80%, 100% { transform: scale(0); }
227
- 40% { transform: scale(1); }
228
- }
229
-
230
- @media (max-width: 480px) {
231
- #martech-chat-box {
232
- right: 15px;
233
- bottom: 100px;
234
- width: calc(100vw - 30px);
235
- height: 70vh;
236
- }
237
- }
238
- </style>
239
- </head>
240
- <body>
241
-
242
- <!-- Lottie Player Script -->
243
- <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
244
-
245
- <div id="martech-chat-wrapper">
246
- <div id="martech-chat-fab">
247
- <!-- Animated Chatbot Icon -->
248
- <div id="chat-icon" style="width: 233px; height: 238px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
249
- <dotlottie-player
250
- src="https://joedown11-chatrag.hf.space/static/bot-icon.lottie"
251
- background="transparent" speed="1" style="width: 100%; height: 100%; will-change: transform;" loop autoplay>
252
- </dotlottie-player>
253
- </div>
254
- <!-- Close Icon -->
255
- <svg id="close-icon" viewBox="0 0 24 24" style="display:none; width: 60px; height: 60px;">
256
- <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="white" />
257
- </svg>
258
- </div>
259
-
260
- <div id="martech-chat-box">
261
- <div id="martech-chat-header">
262
- <div class="dot"></div>
263
- <span>Martechsol Assistant</span>
264
- </div>
265
-
266
- <!-- Custom Chat UI replacing iframe -->
267
- <div id="chat-messages">
268
- <div class="chat-message bot">Welcome! How can I help you today?</div>
269
- </div>
270
-
271
- <div id="chat-input-area">
272
- <input type="text" id="chat-input" placeholder="Type your question here..." autocomplete="off" />
273
- <button id="chat-send" title="Send">
274
- <!-- Send Icon -->
275
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
276
- <line x1="22" y1="2" x2="11" y2="13"></line>
277
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
278
- </svg>
279
- </button>
280
- </div>
281
- </div>
282
- </div>
283
-
284
- <script>
285
- (function () {
286
- // UI Elements
287
- const fab = document.getElementById('martech-chat-fab');
288
- const box = document.getElementById('martech-chat-box');
289
- const chatIcon = document.getElementById('chat-icon');
290
- const closeIcon = document.getElementById('close-icon');
291
- const messagesContainer = document.getElementById('chat-messages');
292
- const inputField = document.getElementById('chat-input');
293
- const sendButton = document.getElementById('chat-send');
294
-
295
- // FAB Toggle Logic
296
- fab.addEventListener('click', function () {
297
- const isVisible = box.classList.contains('is-visible');
298
- if (isVisible) {
299
- box.classList.remove('is-visible');
300
- setTimeout(() => { box.style.display = 'none'; }, 300);
301
- chatIcon.style.display = 'block';
302
- closeIcon.style.display = 'none';
303
- fab.classList.remove('is-active');
304
- } else {
305
- box.style.display = 'flex';
306
- setTimeout(() => { box.classList.add('is-visible'); inputField.focus(); }, 10);
307
- chatIcon.style.display = 'none';
308
- closeIcon.style.display = 'block';
309
- fab.classList.add('is-active');
310
- }
311
- });
312
-
313
- // Chat Logic
314
- const API_URL = "https://joedown11-chatrag.hf.space/api/chat";
315
- const BASE_URL = API_URL.replace("/api/chat", "");
316
- let chatHistory = [];
317
-
318
- // ── Session ID: generate once, persist in localStorage ──
319
- let sessionId = localStorage.getItem('martech_session_id');
320
- if (!sessionId) {
321
- // Use a temporary name/id until we get the real one
322
- sessionId = 'user-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
323
- localStorage.setItem('martech_session_id', sessionId);
324
- }
325
-
326
- function formatMessage(text) {
327
- if (!text) return "";
328
- // 1. Convert Markdown Bold to HTML Bold (safety)
329
- let formatted = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
330
- // 2. Remove the '*' at the start of lines and use a bullet
331
- formatted = formatted.replace(/^\* /gm, '• ');
332
- // 3. Newlines to BR
333
- return formatted.replace(/\n/g, '<br>');
334
- }
335
-
336
- function appendMessage(role, content) {
337
- const div = document.createElement('div');
338
- div.className = `chat-message ${role}`;
339
- div.innerHTML = formatMessage(content);
340
- messagesContainer.appendChild(div);
341
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
342
- return div;
343
- }
344
-
345
- // ── User Integration & History ──────────────────────────────────────────
346
- async function initUserSession() {
347
- // 1. Discover User Name immediately (0.2s)
348
- let userName = null;
349
- try {
350
- const storageKeys = ['userInfo', 'user', 'profile', 'auth', 'currentUser', 'account'];
351
- for (const key of storageKeys) {
352
- const stored = localStorage.getItem(key) || sessionStorage.getItem(key);
353
- if (stored) {
354
- try {
355
- const parsed = JSON.parse(stored);
356
- userName = parsed.name || parsed.fullName || (parsed.user && parsed.user.name) || parsed.display_name;
357
- if (userName) break;
358
- } catch(err) {
359
- if (typeof stored === 'string' && stored.length < 50 && !stored.includes('{')) {
360
- userName = stored; break;
361
- }
362
- }
363
- }
364
- }
365
- } catch (e) {}
366
-
367
- if (!userName) {
368
- try {
369
- const pResp = await fetch("https://hrmmartechsol-ba71e8dc3fa2.herokuapp.com/api/users/profile", {
370
- credentials: 'include'
371
- });
372
- if (pResp.ok) {
373
- const pData = await pResp.json();
374
- userName = pData.name || (pData.personalDetails && pData.personalDetails.fullName);
375
- }
376
- } catch (e) {}
377
- }
378
-
379
- // Multi-user logic: if name changed, clear old session and start fresh
380
- const lastKnownName = localStorage.getItem('martech_last_user_name');
381
- if (userName && lastKnownName && userName !== lastKnownName) {
382
- localStorage.removeItem('martech_session_id');
383
- sessionId = 'user-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
384
- localStorage.setItem('martech_session_id', sessionId);
385
- messagesContainer.innerHTML = '<div class="chat-message bot">Welcome! How can I help you today?</div>';
386
- }
387
- if (userName) {
388
- localStorage.setItem('martech_last_user_name', userName);
389
- }
390
-
391
- // 2. Fetch history for this session
392
- try {
393
- const hResp = await fetch(BASE_URL + "/api/session/history/" + sessionId);
394
- if (hResp.ok) {
395
- const hData = await hResp.json();
396
- if (hData.history && hData.history.length > 0) {
397
- messagesContainer.innerHTML = '';
398
- chatHistory = hData.history;
399
- chatHistory.forEach(m => {
400
- appendMessage(m.role === 'assistant' ? 'bot' : 'user', m.content);
401
- });
402
- }
403
- }
404
- } catch (e) {}
405
-
406
- // 3. Sync name with backend
407
- if (userName) {
408
- await fetch(BASE_URL + "/api/session/update-name", {
409
- method: 'POST',
410
- headers: { 'Content-Type': 'application/json' },
411
- body: JSON.stringify({ session_id: sessionId, user_name: userName })
412
- }).then(r => r.json()).then(rData => {
413
- if (rData.new_session_id && rData.new_session_id !== sessionId) {
414
- sessionId = rData.new_session_id;
415
- localStorage.setItem('martech_session_id', sessionId);
416
- }
417
- }).catch(() => {});
418
- }
419
- }
420
-
421
- initUserSession();
422
-
423
- function showTypingIndicator() {
424
- const div = document.createElement('div');
425
- div.className = 'chat-message bot';
426
- div.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
427
- messagesContainer.appendChild(div);
428
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
429
- return div;
430
- }
431
-
432
- // Typo Configuration for Human-like Typing
433
- const NEARBY_KEYS = {
434
- 'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'sfecx', 'e': 'wrsdf',
435
- 'f': 'dgrtcv', 'g': 'fhtybn', 'h': 'gjyunb', 'i': 'uojkl', 'j': 'hkunmi',
436
- 'k': 'jlomi', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'ipkl',
437
- 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
438
- 'u': 'yihj', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
439
- 'z': 'xas',
440
- };
441
- const TYPO_CHANCE = 0.07;
442
-
443
- function getNearbyChar(ch) {
444
- const lower = ch.toLowerCase();
445
- if (NEARBY_KEYS[lower]) {
446
- const options = NEARBY_KEYS[lower];
447
- const replacement = options.charAt(Math.floor(Math.random() * options.length));
448
- return ch === ch.toUpperCase() ? replacement.toUpperCase() : replacement;
449
- }
450
- const abc = "abcdefghijklmnopqrstuvwxyz";
451
- return abc.charAt(Math.floor(Math.random() * abc.length));
452
- }
453
-
454
- const sleep = ms => new Promise(r => setTimeout(r, ms));
455
-
456
- async function typeMessageWithTypos(element, text) {
457
- const fullHtml = formatMessage(text);
458
-
459
- // Simple HTML-aware typing: split by tags
460
- const tokens = fullHtml.split(/(<[^>]*>)/);
461
- let currentHtml = "";
462
-
463
- for (const token of tokens) {
464
- if (token.startsWith("<")) {
465
- // Instantly add tags
466
- currentHtml += token;
467
- element.innerHTML = currentHtml;
468
- } else {
469
- // Type text content char by char
470
- for (let char of token) {
471
- currentHtml += char;
472
- element.innerHTML = currentHtml;
473
- await sleep(Math.random() * 15 + 5);
474
- }
475
- }
476
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
477
- }
478
- }
479
-
480
- async function handleSend() {
481
- const text = inputField.value.trim();
482
- if (!text) return;
483
-
484
- // UI state lock
485
- inputField.value = '';
486
- inputField.disabled = true;
487
- sendButton.disabled = true;
488
-
489
- appendMessage('user', text);
490
- const typingEl = showTypingIndicator();
491
-
492
- try {
493
- // Call the FastAPI Backend directly
494
- const response = await fetch(API_URL, {
495
- method: 'POST',
496
- headers: { 'Content-Type': 'application/json' },
497
- body: JSON.stringify({
498
- message: text,
499
- history: chatHistory,
500
- session_id: sessionId
501
- })
502
- });
503
-
504
- if (!response.ok) {
505
- if (response.status === 429) throw new Error("Rate limit exceeded.");
506
- throw new Error("Server error.");
507
- }
508
-
509
- const data = await response.json();
510
- const reply = data.reply;
511
-
512
- typingEl.remove();
513
- const botEl = appendMessage('bot', '');
514
-
515
- // Update local memory
516
- chatHistory.push({ role: "user", content: text });
517
- chatHistory.push({ role: "assistant", content: reply });
518
-
519
- // Stream text out
520
- await typeMessageWithTypos(botEl, reply);
521
-
522
- } catch (error) {
523
- console.error(error);
524
- typingEl.remove();
525
- appendMessage('bot', "⚠️ I'm sorry, I encountered an error. Please try again.");
526
- } finally {
527
- inputField.disabled = false;
528
- sendButton.disabled = false;
529
- inputField.focus();
530
- }
531
- }
532
-
533
- // Event Listeners
534
- sendButton.addEventListener('click', handleSend);
535
- inputField.addEventListener('keypress', function(e) {
536
- if (e.key === 'Enter') handleSend();
537
- });
538
-
539
- })();
540
- </script>
541
-
542
- </body>
543
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/static/bot-icon.lottie DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:532a144eeea5a1a6e49ab22ae4c0dc4f94e12cf8540e3514f1bc676d929eeac2
3
- size 462684
 
 
 
 
backup_2026-05-04/static/chat-loader.js DELETED
@@ -1,48 +0,0 @@
1
- /**
2
- * Martechsol Assistant Loader
3
- * Usage: <script src="https://joedown11-chatrag.hf.space/static/chat-loader.js"></script>
4
- */
5
- (function() {
6
- const baseUrl = 'https://joedown11-chatrag.hf.space';
7
-
8
- // Create container
9
- const container = document.createElement('div');
10
- container.id = 'martech-chat-widget-container';
11
- document.body.appendChild(container);
12
-
13
- // Fetch the addon HTML from the dedicated /widget endpoint
14
- fetch(`${baseUrl}/widget?v=${Date.now()}`) // Cache busting
15
- .then(response => response.text())
16
- .then(html => {
17
- // Create a temporary element to parse the HTML
18
- const parser = new DOMParser();
19
- const doc = parser.parseFromString(html, 'text/html');
20
-
21
- // 1. Extract and inject styles
22
- doc.querySelectorAll('style').forEach(st => {
23
- document.head.appendChild(st.cloneNode(true));
24
- });
25
-
26
- // 2. Extract and inject body content
27
- const wrapper = doc.querySelector('#martech-chat-wrapper');
28
- if (wrapper) {
29
- container.innerHTML = wrapper.outerHTML;
30
- } else {
31
- // Fallback: just take the body content
32
- container.innerHTML = doc.body.innerHTML;
33
- }
34
-
35
- // 3. Extract and execute scripts (critical for logic)
36
- doc.querySelectorAll('script').forEach(oldScript => {
37
- const newScript = document.createElement('script');
38
- Array.from(oldScript.attributes).forEach(attr => {
39
- newScript.setAttribute(attr.name, attr.value);
40
- });
41
- newScript.innerHTML = oldScript.innerHTML;
42
- document.body.appendChild(newScript);
43
- });
44
-
45
- console.log("Martechsol Assistant Loaded Successfully");
46
- })
47
- .catch(err => console.error("Failed to load Martechsol Assistant:", err));
48
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/test_groq.py DELETED
@@ -1,26 +0,0 @@
1
- import asyncio
2
- import os
3
- import httpx
4
- from dotenv import load_dotenv
5
-
6
- load_dotenv()
7
-
8
- async def main():
9
- api_key = os.getenv("GROQ_API_KEY")
10
- url = "https://api.groq.com/openai/v1/chat/completions"
11
- headers = {
12
- "Authorization": f"Bearer {api_key}",
13
- "Content-Type": "application/json"
14
- }
15
- payload = {
16
- "model": "deepseek-r1-distill-llama-70b",
17
- "temperature": 0.0,
18
- "max_tokens": 1200,
19
- "messages": [{"role": "system", "content": "hello"}, {"role": "user", "content": "test"}]
20
- }
21
- async with httpx.AsyncClient() as client:
22
- resp = await client.post(url, headers=headers, json=payload)
23
- print(resp.status_code)
24
- print(resp.text)
25
-
26
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/wordpress code/WP_INTEGRATION_SCRIPT.txt DELETED
@@ -1,12 +0,0 @@
1
- <!--
2
- Martechsol Chatbot Integration Script
3
- Paste this snippet right before the closing </body> tag on your WordPress site.
4
- It will automatically load the UI and logic from your Hugging Face space.
5
- -->
6
-
7
- <script>
8
- fetch('https://joedown11-chatrag.hf.space/static/addon.html').then(r => r.text()).then(h => {
9
- const d = document.createElement('div'); d.innerHTML = h; document.body.appendChild(d);
10
- d.querySelectorAll('script').forEach(s => { const n = document.createElement('script'); Array.from(s.attributes).forEach(a => n.setAttribute(a.name, a.value)); n.innerHTML = s.innerHTML; document.body.appendChild(n); });
11
- });
12
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/wordpress code/addon.html DELETED
@@ -1,477 +0,0 @@
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>Martechsol Assistant Widget</title>
7
- <style>
8
- /* Premium Design for Martechsol Assistant */
9
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
10
-
11
- #martech-chat-wrapper {
12
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
13
- }
14
-
15
- /* Floating Action Button (FAB) */
16
- #martech-chat-fab {
17
- position: fixed;
18
- right: 10px;
19
- bottom: 25px;
20
- width: auto;
21
- height: auto;
22
- background: transparent;
23
- border-radius: 50%;
24
- display: flex;
25
- align-items: center;
26
- justify-content: center;
27
- cursor: pointer;
28
- z-index: 999999;
29
- transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.3s ease, box-shadow 0.3s ease;
30
- -webkit-tap-highlight-color: transparent;
31
- }
32
-
33
- #martech-chat-fab:hover {
34
- transform: scale(1.1);
35
- }
36
-
37
- #martech-chat-fab.is-active {
38
- background: #1e293b;
39
- box-shadow: 0 10px 25px rgba(30, 41, 59, 0.4);
40
- transform: scale(0.8);
41
- }
42
-
43
- /* Chat Window Styling */
44
- #martech-chat-box {
45
- position: fixed;
46
- right: 25px;
47
- bottom: 100px;
48
- width: 400px;
49
- height: 550px;
50
- background: #ffffff;
51
- border-radius: 16px;
52
- overflow: hidden;
53
- display: none;
54
- flex-direction: column;
55
- box-shadow: 0 12px 50px rgba(0, 0, 0, 0.2);
56
- z-index: 999998;
57
- border: 1px solid #e2e8f0;
58
- opacity: 0;
59
- transform: translateY(20px);
60
- transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
61
- }
62
-
63
- #martech-chat-box.is-visible {
64
- display: flex;
65
- opacity: 1;
66
- transform: translateY(0);
67
- }
68
-
69
- #martech-chat-header {
70
- background: #ffffff;
71
- padding: 16px 20px;
72
- border-bottom: 1px solid #f1f5f9;
73
- display: flex;
74
- align-items: center;
75
- gap: 12px;
76
- }
77
-
78
- @keyframes pulse-dot {
79
- 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
80
- 70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
81
- 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
82
- }
83
-
84
- #martech-chat-header .dot {
85
- width: 10px;
86
- height: 10px;
87
- background: #10b981;
88
- border-radius: 50%;
89
- animation: pulse-dot 2s infinite;
90
- }
91
-
92
- #martech-chat-header span {
93
- font-weight: 600;
94
- color: #1e293b;
95
- font-size: 15px;
96
- }
97
-
98
- /* Custom Chat Area Styling */
99
- #chat-messages {
100
- flex: 1;
101
- overflow-y: auto;
102
- padding: 16px;
103
- display: flex;
104
- flex-direction: column;
105
- gap: 12px;
106
- background: #f8fafc;
107
- scroll-behavior: smooth;
108
- }
109
-
110
- /* Custom Scrollbar for messages */
111
- #chat-messages::-webkit-scrollbar { width: 6px; }
112
- #chat-messages::-webkit-scrollbar-track { background: transparent; }
113
- #chat-messages::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }
114
-
115
- .chat-message {
116
- max-width: 85%;
117
- padding: 12px 16px;
118
- border-radius: 16px;
119
- font-size: 14px;
120
- line-height: 1.5;
121
- word-wrap: break-word;
122
- white-space: pre-wrap;
123
- }
124
-
125
- .chat-message.user {
126
- align-self: flex-end;
127
- background: #1e293b;
128
- color: white;
129
- border-bottom-right-radius: 4px;
130
- }
131
-
132
- .chat-message.bot {
133
- align-self: flex-start;
134
- background: #ffffff;
135
- color: #334155;
136
- border-bottom-left-radius: 4px;
137
- border: 1px solid #e2e8f0;
138
- box-shadow: 0 2px 4px rgba(0,0,0,0.02);
139
- }
140
-
141
- #chat-input-area {
142
- padding: 16px;
143
- background: #ffffff;
144
- border-top: 1px solid #f1f5f9;
145
- display: flex;
146
- gap: 8px;
147
- align-items: center;
148
- }
149
-
150
- #chat-input {
151
- flex: 1;
152
- border: 1px solid #e2e8f0;
153
- border-radius: 20px;
154
- padding: 12px 16px;
155
- font-size: 14px;
156
- outline: none;
157
- transition: border-color 0.2s;
158
- font-family: inherit;
159
- box-shadow: inset 0 1px 2px rgba(0,0,0,0.02);
160
- }
161
-
162
- #chat-input:focus {
163
- border-color: #10b981;
164
- }
165
-
166
- #chat-input:disabled {
167
- background: #f1f5f9;
168
- cursor: not-allowed;
169
- }
170
-
171
- #chat-send {
172
- background: #10b981;
173
- color: white;
174
- border: none;
175
- width: 44px;
176
- height: 44px;
177
- border-radius: 50%;
178
- display: flex;
179
- align-items: center;
180
- justify-content: center;
181
- cursor: pointer;
182
- transition: background 0.2s, transform 0.1s;
183
- }
184
-
185
- #chat-send:hover {
186
- background: #059669;
187
- }
188
-
189
- #chat-send:active {
190
- transform: scale(0.95);
191
- }
192
-
193
- #chat-send:disabled {
194
- background: #cbd5e1;
195
- cursor: not-allowed;
196
- transform: none;
197
- }
198
-
199
- /* Loading / Typing Animation */
200
- .typing-indicator {
201
- display: flex;
202
- gap: 4px;
203
- padding: 4px 2px;
204
- align-items: center;
205
- height: 18px;
206
- }
207
- .typing-indicator span {
208
- width: 6px;
209
- height: 6px;
210
- background: #94a3b8;
211
- border-radius: 50%;
212
- animation: typing-dots 1.4s infinite ease-in-out both;
213
- }
214
- .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
215
- .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
216
- @keyframes typing-dots {
217
- 0%, 80%, 100% { transform: scale(0); }
218
- 40% { transform: scale(1); }
219
- }
220
-
221
- @media (max-width: 480px) {
222
- #martech-chat-box {
223
- right: 15px;
224
- bottom: 100px;
225
- width: calc(100vw - 30px);
226
- height: 70vh;
227
- }
228
- }
229
- </style>
230
- </head>
231
- <body>
232
-
233
- <!-- Lottie Player Script -->
234
- <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
235
-
236
- <div id="martech-chat-wrapper">
237
- <div id="martech-chat-fab">
238
- <!-- Animated Chatbot Icon -->
239
- <div id="chat-icon" style="width: 233px; height: 238px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
240
- <dotlottie-player
241
- src="https://seashell-cattle-543014.hostingersite.com/wp-content/uploads/2026/04/KhloeSAC.lottie"
242
- background="transparent" speed="1" style="width: 100%; height: 100%; will-change: transform;" loop autoplay>
243
- </dotlottie-player>
244
- </div>
245
- <!-- Close Icon -->
246
- <svg id="close-icon" viewBox="0 0 24 24" style="display:none; width: 60px; height: 60px;">
247
- <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="white" />
248
- </svg>
249
- </div>
250
-
251
- <div id="martech-chat-box">
252
- <div id="martech-chat-header">
253
- <div class="dot"></div>
254
- <span>Martechsol Assistant</span>
255
- </div>
256
-
257
- <!-- Custom Chat UI replacing iframe -->
258
- <div id="chat-messages">
259
- <div class="chat-message bot">Welcome! How can I help you today?</div>
260
- </div>
261
-
262
- <div id="chat-input-area">
263
- <input type="text" id="chat-input" placeholder="Type your question here..." autocomplete="off" />
264
- <button id="chat-send" title="Send">
265
- <!-- Send Icon -->
266
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
267
- <line x1="22" y1="2" x2="11" y2="13"></line>
268
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
269
- </svg>
270
- </button>
271
- </div>
272
- </div>
273
- </div>
274
-
275
- <script>
276
- (function () {
277
- // UI Elements
278
- const fab = document.getElementById('martech-chat-fab');
279
- const box = document.getElementById('martech-chat-box');
280
- const chatIcon = document.getElementById('chat-icon');
281
- const closeIcon = document.getElementById('close-icon');
282
- const messagesContainer = document.getElementById('chat-messages');
283
- const inputField = document.getElementById('chat-input');
284
- const sendButton = document.getElementById('chat-send');
285
-
286
- // FAB Toggle Logic
287
- fab.addEventListener('click', function () {
288
- const isVisible = box.classList.contains('is-visible');
289
- if (isVisible) {
290
- box.classList.remove('is-visible');
291
- setTimeout(() => { box.style.display = 'none'; }, 300);
292
- chatIcon.style.display = 'block';
293
- closeIcon.style.display = 'none';
294
- fab.classList.remove('is-active');
295
- } else {
296
- box.style.display = 'flex';
297
- setTimeout(() => { box.classList.add('is-visible'); inputField.focus(); }, 10);
298
- chatIcon.style.display = 'none';
299
- closeIcon.style.display = 'block';
300
- fab.classList.add('is-active');
301
- }
302
- });
303
-
304
- // Chat Logic
305
- const API_URL = "https://joedown11-chatrag.hf.space/api/chat";
306
- let chatHistory = [];
307
-
308
- // ── Session ID: generate once, persist in localStorage ──
309
- let sessionId = localStorage.getItem('martech_session_id');
310
- if (!sessionId) {
311
- sessionId = 'sess-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
312
- localStorage.setItem('martech_session_id', sessionId);
313
- }
314
-
315
- function appendMessage(role, content) {
316
- const div = document.createElement('div');
317
- div.className = `chat-message ${role}`;
318
- div.textContent = content;
319
- messagesContainer.appendChild(div);
320
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
321
- return div;
322
- }
323
-
324
- function showTypingIndicator() {
325
- const div = document.createElement('div');
326
- div.className = 'chat-message bot';
327
- div.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
328
- messagesContainer.appendChild(div);
329
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
330
- return div;
331
- }
332
-
333
- // Typo Configuration for Human-like Typing
334
- const NEARBY_KEYS = {
335
- 'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'sfecx', 'e': 'wrsdf',
336
- 'f': 'dgrtcv', 'g': 'fhtybn', 'h': 'gjyunb', 'i': 'uojkl', 'j': 'hkunmi',
337
- 'k': 'jlomi', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'ipkl',
338
- 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
339
- 'u': 'yihj', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
340
- 'z': 'xas',
341
- };
342
- const TYPO_CHANCE = 0.07;
343
-
344
- function getNearbyChar(ch) {
345
- const lower = ch.toLowerCase();
346
- if (NEARBY_KEYS[lower]) {
347
- const options = NEARBY_KEYS[lower];
348
- const replacement = options.charAt(Math.floor(Math.random() * options.length));
349
- return ch === ch.toUpperCase() ? replacement.toUpperCase() : replacement;
350
- }
351
- const abc = "abcdefghijklmnopqrstuvwxyz";
352
- return abc.charAt(Math.floor(Math.random() * abc.length));
353
- }
354
-
355
- const sleep = ms => new Promise(r => setTimeout(r, ms));
356
-
357
- async function typeMessageWithTypos(element, text) {
358
- let displayed = "";
359
- const words = text.split(" ");
360
-
361
- for (let i = 0; i < words.length; i++) {
362
- const word = words[i];
363
- const separator = i > 0 ? " " : "";
364
- const isAlpha = /^[a-zA-Z]+$/.test(word);
365
-
366
- // Randomly insert a typo
367
- if (i > 0 && word.length > 4 && Math.random() < TYPO_CHANCE && isAlpha) {
368
- const typoPos = Math.floor(Math.random() * (word.length - 2)) + 2;
369
-
370
- // 1. Type correct part
371
- for (let char of (separator + word.substring(0, typoPos))) {
372
- displayed += char;
373
- element.textContent = displayed;
374
- await sleep(Math.random() * 40 + 30);
375
- }
376
- // 2. Type wrong char
377
- const wrongChar = getNearbyChar(word[typoPos]);
378
- displayed += wrongChar;
379
- element.textContent = displayed;
380
- await sleep(Math.random() * 150 + 150); // Realize error
381
-
382
- // 3. Backspace
383
- await sleep(Math.random() * 200 + 200);
384
- displayed = displayed.slice(0, -1);
385
- element.textContent = displayed;
386
- await sleep(Math.random() * 100 + 100);
387
-
388
- // 4. Type rest
389
- for (let char of word.substring(typoPos)) {
390
- displayed += char;
391
- element.textContent = displayed;
392
- await sleep(Math.random() * 40 + 40);
393
- }
394
- } else {
395
- // Type normally
396
- for (let char of (separator + word)) {
397
- displayed += char;
398
- element.textContent = displayed;
399
- await sleep(char === " " ? Math.random() * 70 + 80 : Math.random() * 40 + 20);
400
- }
401
- }
402
-
403
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
404
-
405
- // Punctuation pauses
406
- if (word && [".", "!", "?"].includes(word[word.length - 1])) {
407
- await sleep(Math.random() * 400 + 400);
408
- } else if (word && [",", ":", ";"].includes(word[word.length - 1])) {
409
- await sleep(Math.random() * 200 + 200);
410
- }
411
- }
412
- }
413
-
414
- async function handleSend() {
415
- const text = inputField.value.trim();
416
- if (!text) return;
417
-
418
- // UI state lock
419
- inputField.value = '';
420
- inputField.disabled = true;
421
- sendButton.disabled = true;
422
-
423
- appendMessage('user', text);
424
- const typingEl = showTypingIndicator();
425
-
426
- try {
427
- // Call the FastAPI Backend directly
428
- const response = await fetch(API_URL, {
429
- method: 'POST',
430
- headers: { 'Content-Type': 'application/json' },
431
- body: JSON.stringify({
432
- message: text,
433
- history: chatHistory,
434
- session_id: sessionId
435
- })
436
- });
437
-
438
- if (!response.ok) {
439
- if (response.status === 429) throw new Error("Rate limit exceeded.");
440
- throw new Error("Server error.");
441
- }
442
-
443
- const data = await response.json();
444
- const reply = data.reply;
445
-
446
- typingEl.remove();
447
- const botEl = appendMessage('bot', '');
448
-
449
- // Update local memory
450
- chatHistory.push({ role: "user", content: text });
451
- chatHistory.push({ role: "assistant", content: reply });
452
-
453
- // Stream text out
454
- await typeMessageWithTypos(botEl, reply);
455
-
456
- } catch (error) {
457
- console.error(error);
458
- typingEl.remove();
459
- appendMessage('bot', "⚠️ I'm sorry, I encountered an error. Please try again.");
460
- } finally {
461
- inputField.disabled = false;
462
- sendButton.disabled = false;
463
- inputField.focus();
464
- }
465
- }
466
-
467
- // Event Listeners
468
- sendButton.addEventListener('click', handleSend);
469
- inputField.addEventListener('keypress', function(e) {
470
- if (e.key === 'Enter') handleSend();
471
- });
472
-
473
- })();
474
- </script>
475
-
476
- </body>
477
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-04/wordpress code/addon_backup.html DELETED
@@ -1,175 +0,0 @@
1
- <style>
2
- /* Premium Design for Martechsol Assistant */
3
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
4
-
5
- #martech-chat-wrapper {
6
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
7
- }
8
-
9
- /* Floating Action Button (FAB) */
10
- #martech-chat-fab {
11
- position: fixed;
12
- right: 10px;
13
- bottom: 25px;
14
- width: auto;
15
- height: auto;
16
- background: transparent;
17
- border-radius: 50%;
18
- display: flex;
19
- align-items: center;
20
- justify-content: center;
21
- cursor: pointer;
22
- z-index: 999999;
23
- transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.3s ease, box-shadow 0.3s ease;
24
- -webkit-tap-highlight-color: transparent;
25
- }
26
-
27
- #martech-chat-fab:hover {
28
- transform: scale(1.1);
29
- }
30
-
31
- #martech-chat-fab.is-active {
32
- background: #1e293b;
33
- box-shadow: 0 10px 25px rgba(30, 41, 59, 0.4);
34
- transform: scale(0.8);
35
- }
36
-
37
- /* Chat Window Styling */
38
- #martech-chat-box {
39
- position: fixed;
40
- right: 25px;
41
- bottom: 100px;
42
- width: 400px;
43
- height: 550px;
44
- background: #ffffff;
45
- border-radius: 16px;
46
- overflow: hidden;
47
- display: none;
48
- flex-direction: column;
49
- box-shadow: 0 12px 50px rgba(0, 0, 0, 0.2);
50
- z-index: 999998;
51
- border: 1px solid #e2e8f0;
52
- opacity: 0;
53
- transform: translateY(20px);
54
- transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
55
- }
56
-
57
- #martech-chat-box.is-visible {
58
- display: flex;
59
- opacity: 1;
60
- transform: translateY(0);
61
- }
62
-
63
- #martech-chat-header {
64
- background: #ffffff;
65
- padding: 16px 20px;
66
- border-bottom: 1px solid #f1f5f9;
67
- display: flex;
68
- align-items: center;
69
- gap: 12px;
70
- }
71
-
72
- @keyframes pulse-dot {
73
- 0% {
74
- transform: scale(1);
75
- box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
76
- }
77
-
78
- 70% {
79
- transform: scale(1.1);
80
- box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
81
- }
82
-
83
- 100% {
84
- transform: scale(1);
85
- box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
86
- }
87
- }
88
-
89
- #martech-chat-header .dot {
90
- width: 10px;
91
- height: 10px;
92
- background: #10b981;
93
- border-radius: 50%;
94
- animation: pulse-dot 2s infinite;
95
- }
96
-
97
- #martech-chat-header span {
98
- font-weight: 600;
99
- color: #1e293b;
100
- font-size: 15px;
101
- }
102
-
103
- #martech-chat-box iframe {
104
- width: 100%;
105
- flex: 1;
106
- border: 0;
107
- }
108
-
109
- @media (max-width: 480px) {
110
- #martech-chat-box {
111
- right: 15px;
112
- bottom: 100px;
113
- width: calc(100vw - 30px);
114
- height: 70vh;
115
- }
116
- }
117
- </style>
118
-
119
- <!-- Lottie Player Script -->
120
- <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
121
-
122
- <div id="martech-chat-wrapper">
123
- <div id="martech-chat-fab">
124
- <!-- Animated Chatbot Icon -->
125
- <div id="chat-icon"
126
- style="width: 233px; height: 238px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
127
- <dotlottie-player
128
- src="https://seashell-cattle-543014.hostingersite.com/wp-content/uploads/2026/04/KhloeSAC.lottie"
129
- background="transparent" speed="1" style="width: 100%; height: 100%; will-change: transform;" loop
130
- autoplay>
131
- </dotlottie-player>
132
- </div>
133
- <!-- Close Icon -->
134
- <svg id="close-icon" viewBox="0 0 24 24" style="display:none; width: 60px; height: 60px;">
135
- <path
136
- d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
137
- fill="white" />
138
- </svg>
139
- </div>
140
-
141
- <div id="martech-chat-box">
142
- <div id="martech-chat-header">
143
- <div class="dot"></div>
144
- <span>Martechsol Assistant</span>
145
- </div>
146
- <iframe src="https://joedown11-chatrag.hf.space" allow="clipboard-write; microphone" loading="lazy"
147
- referrerpolicy="no-referrer-when-downgrade"></iframe>
148
- </div>
149
- </div>
150
-
151
- <script>
152
- (function () {
153
- const fab = document.getElementById('martech-chat-fab');
154
- const box = document.getElementById('martech-chat-box');
155
- const chatIcon = document.getElementById('chat-icon');
156
- const closeIcon = document.getElementById('close-icon');
157
-
158
- fab.addEventListener('click', function () {
159
- const isVisible = box.classList.contains('is-visible');
160
- if (isVisible) {
161
- box.classList.remove('is-visible');
162
- setTimeout(() => { box.style.display = 'none'; }, 300);
163
- chatIcon.style.display = 'block';
164
- closeIcon.style.display = 'none';
165
- fab.classList.remove('is-active');
166
- } else {
167
- box.style.display = 'flex';
168
- setTimeout(() => { box.classList.add('is-visible'); }, 10);
169
- chatIcon.style.display = 'none';
170
- closeIcon.style.display = 'block';
171
- fab.classList.add('is-active');
172
- }
173
- });
174
- })();
175
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-13_pre_intelligent_retrieval/.env.example DELETED
@@ -1,19 +0,0 @@
1
- LLM_PROVIDER=fireworks
2
- FIREWORKS_API_KEY=your_fireworks_api_key
3
- FIREWORKS_MODEL=accounts/fireworks/models/qwen3-32b
4
- FIREWORKS_REWRITE_MODEL=accounts/fireworks/models/llama-v3p1-8b-instruct
5
- GROQ_API_KEY=your_groq_api_key
6
- GROQ_MODEL=qwen/qwen3-32b
7
- GROQ_REWRITE_MODEL=llama-3.1-8b-instant
8
- HF_API_KEY=your_huggingface_api_key
9
- HF_MODEL=meta-llama/Llama-3.1-8B-Instruct
10
- EMBEDDING_MODEL=BAAI/bge-small-en-v1.5
11
- DOCS_DIR=docs
12
- INDEX_DIR=data/index
13
- SESSIONS_DIR=data/sessions
14
- TOP_K=4
15
- CORS_ALLOW_ORIGINS=*
16
- API_KEY=
17
- RATE_LIMIT_REQUESTS=60
18
- RATE_LIMIT_WINDOW_SECONDS=60
19
- RAG_API_URL=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backup_2026-05-13_pre_intelligent_retrieval/.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- *.lottie filter=lfs diff=lfs merge=lfs -text
2
- *.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
backup_2026-05-13_pre_intelligent_retrieval/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- .env
2
- __pycache__/
3
- .pytest_cache/
4
- .mypy_cache/
5
- .venv/
6
- venv/
7
- # Ignoring data directory to prevent pushing binary index files
8
- data/index/
9
- data/sessions/