nexus-web-assistant 1.2.0 โ 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -193
- package/dist/nexus-assistant.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,265 +1,185 @@
|
|
|
1
|
-
# Nexus Web Assistant
|
|
1
|
+
# Nexus Web Assistant
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/nexus-web-assistant)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://cdn.jsdelivr.net/npm/nexus-web-assistant@1.2.0/dist/nexus-assistant.min.js)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
[](https://github.com/thekaifansari01/NexusWebAssistant/network)
|
|
7
|
-
[](https://github.com/thekaifansari01/NexusWebAssistant/issues)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
[](https://github.com/thekaifansari01/NexusWebAssistant/pulls)
|
|
7
|
+
**Nexus Web Assistant** is a lightweight, embeddable AI chat widget that turns any webpage into a conversational knowledge base. It automatically scrapes the page content (including metadata, structured data, and clean main text) and lets users ask questions directly to an AI assistant powered by [Groq](https://groq.com/).
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
**Nexus Web Assistant** is a cutting-edge, fully-featured AI chatbot widget that seamlessly integrates into any website. Powered by Groq's ultra-fast inference engine and equipped with advanced web scraping capabilities, it delivers intelligent, context-aware responses about your web content in real-time.
|
|
9
|
+
- **Zeroโdependency** โ works on any static or dynamic website.
|
|
10
|
+
- **Instant context** โ the AI answers based on the current page, not just general knowledge.
|
|
11
|
+
- **Beautiful dark/light themes** โ autoโdetects your siteโs theme and adapts.
|
|
12
|
+
- **CDNโready** โ drop in a single `<script>` and you're live.
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
โก๏ธ [Live Demo](https://your-demo-link.com) ยท [GitHub](https://github.com/thekaifansari01/nexusWebAssistant/)
|
|
18
15
|
|
|
19
16
|
---
|
|
20
17
|
|
|
21
|
-
##
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **Readability Integration** - Clean, distraction-free content extraction
|
|
31
|
-
- **Markdown Conversion** - Transforms HTML to clean Markdown
|
|
32
|
-
- **JSON-LD Support** - Extracts structured data (Schema.org)
|
|
33
|
-
- **SPA Navigation Detection** - Automatically refreshes context on page changes
|
|
34
|
-
- **Smart Content Detection** - Identifies page types (Blog, Product, Portfolio, etc.)
|
|
35
|
-
|
|
36
|
-
### ๐พ **Intelligent Caching**
|
|
37
|
-
- **IndexedDB Storage** - Unlimited local cache capacity
|
|
38
|
-
- **24-Hour Auto-Expiry** - Smart cache invalidation
|
|
39
|
-
- **Offline Support** - Persistent data across sessions
|
|
40
|
-
- **Cache Statistics** - Real-time cache monitoring
|
|
41
|
-
|
|
42
|
-
### ๐จ **Premium UI/UX**
|
|
43
|
-
- **Glassmorphism Design** - Modern, elegant aesthetic
|
|
44
|
-
- **Dark Mode Optimized** - Perfect for any website
|
|
45
|
-
- **Responsive Layout** - Works on all screen sizes
|
|
46
|
-
- **Smooth Animations** - Delightful user experience
|
|
47
|
-
- **Typing Indicators** - Real-time feedback
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Pageโaware AI** โ scrapes headings, paragraphs, metadata, JSONโLD, and more to build a rich context for each query.
|
|
21
|
+
- **Smart Caching** โ stores scraped data in IndexedDB (24โhour TTL) for faster repeat interactions.
|
|
22
|
+
- **File Attachments** โ users can attach images (visionโcapable models supported).
|
|
23
|
+
- **Markdown & Syntax Highlighting** โ responses are rendered in clean Markdown with code highlighting.
|
|
24
|
+
- **Theme Switching** โ automatically follows your websiteโs theme; users can toggle manually.
|
|
25
|
+
- **SPAโready** โ detects navigation changes (pushState/popState) and refreshes context.
|
|
26
|
+
- **Fully Customizable** โ override API key, model, greeting, system prompt, and UI colours.
|
|
48
27
|
|
|
49
28
|
---
|
|
50
29
|
|
|
51
|
-
##
|
|
30
|
+
## Installation
|
|
52
31
|
|
|
53
|
-
###
|
|
32
|
+
### CDN (recommended)
|
|
54
33
|
|
|
55
|
-
Add
|
|
34
|
+
Add the following script to your HTML โ it loads the widget and all its dependencies (Font Awesome, Marked, Highlight.js, and scrapers).
|
|
56
35
|
|
|
57
36
|
```html
|
|
58
|
-
<script defer src="https://cdn.jsdelivr.net/npm/nexus-web-assistant/dist/nexus-assistant.min.js"></script>
|
|
37
|
+
<script defer src="https://cdn.jsdelivr.net/npm/nexus-web-assistant@1.2.0/dist/nexus-assistant.min.js"></script>
|
|
59
38
|
```
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Clone the repository
|
|
65
|
-
git clone https://github.com/thekaifansari01/NexusWebAssistant.git
|
|
40
|
+
> **Note:** The script is loaded asynchronously; it wonโt block your page rendering.
|
|
66
41
|
|
|
67
|
-
|
|
68
|
-
cd NexusWebAssistant
|
|
42
|
+
---
|
|
69
43
|
|
|
70
|
-
|
|
71
|
-
npm install
|
|
44
|
+
## Quick Start
|
|
72
45
|
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
1. **Include the script** (as above) on your page.
|
|
47
|
+
2. **Configure your Groq API key** (and optional settings) via `window.NexusConfig`:
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
<script
|
|
49
|
+
```html
|
|
50
|
+
<script>
|
|
51
|
+
window.NexusConfig = {
|
|
52
|
+
apiKey: 'YOUR_GROQ_API_KEY', // Required
|
|
53
|
+
model: 'meta-llama/llama-4-scout-17b-16e-instruct', // optional
|
|
54
|
+
botName: 'My Assistant', // optional
|
|
55
|
+
greeting: 'Hello! How can I help?', // optional
|
|
56
|
+
systemPrompt: 'You are a helpful assistant...', // optional
|
|
57
|
+
theme: 'dark' // 'dark' | 'light' | auto-detect
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
78
60
|
```
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
# Install dependencies
|
|
84
|
-
npm install
|
|
85
|
-
|
|
86
|
-
# Start development server
|
|
87
|
-
npm run dev
|
|
88
|
-
|
|
89
|
-
# Build for production
|
|
90
|
-
npm run build
|
|
91
|
-
```
|
|
62
|
+
3. **That's it!** A floating chat button will appear in the bottomโright corner of your page.
|
|
92
63
|
|
|
93
64
|
---
|
|
94
65
|
|
|
95
|
-
##
|
|
66
|
+
## Configuration Options
|
|
96
67
|
|
|
97
|
-
|
|
68
|
+
All settings are optional except `apiKey`. Set them in `window.NexusConfig` before the widget script loads.
|
|
98
69
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
70
|
+
| Option | Type | Default | Description |
|
|
71
|
+
|--------|------|---------|-------------|
|
|
72
|
+
| `apiKey` | `string` | `''` | **Required** โ Your Groq API key. |
|
|
73
|
+
| `model` | `string` | `'meta-llama/llama-4-scout-17b-16e-instruct'` | Groq model to use. |
|
|
74
|
+
| `botName` | `string` | `'Nexus AI'` | Display name in the header. |
|
|
75
|
+
| `greeting` | `string` | (default message) | Initial bot message shown when panel opens. |
|
|
76
|
+
| `systemPrompt` | `string` | (default prompt) | System instructions for the AI (contextualises page data). |
|
|
77
|
+
| `theme` | `'dark' \| 'light'` | autoโdetected | Force a theme; otherwise detects from body classes, meta tags, or system preference. |
|
|
105
78
|
|
|
106
|
-
|
|
107
|
-
BOT_NAME: "Nexus AI",
|
|
108
|
-
GREETING: "Hey! I'm Nexus AI, your intelligent assistant. Ask me anything about this page!",
|
|
79
|
+
---
|
|
109
80
|
|
|
110
|
-
|
|
111
|
-
SYSTEM_PROMPT: `...`, // Customize AI behavior
|
|
81
|
+
## Customisation
|
|
112
82
|
|
|
113
|
-
|
|
114
|
-
COLORS: {
|
|
115
|
-
bg: '#0a0a0a',
|
|
116
|
-
panelBg: 'rgba(12, 12, 12, 0.85)',
|
|
117
|
-
// ... customize theme
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
```
|
|
83
|
+
### Theming
|
|
121
84
|
|
|
122
|
-
|
|
85
|
+
The widget automatically adapts to your siteโs theme by checking:
|
|
86
|
+
- `body` classes (`.dark`, `.dark-theme`, `.dark-mode`, etc.)
|
|
87
|
+
- `data-theme` attribute on `<html>`
|
|
88
|
+
- `<meta name="theme-color">` brightness
|
|
89
|
+
- System preference (`prefers-color-scheme`)
|
|
123
90
|
|
|
124
|
-
|
|
125
|
-
2. **Update `API_KEY`** in `config.js`
|
|
126
|
-
3. **(Optional)** Customize branding and appearance
|
|
91
|
+
You can force a theme by setting `theme` in `NexusConfig`.
|
|
127
92
|
|
|
128
|
-
|
|
93
|
+
Users can toggle the theme manually using the moon/sun icon in the header.
|
|
129
94
|
|
|
130
|
-
|
|
95
|
+
### CSS Variables
|
|
131
96
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
โ โโโ styles.js # Dynamic styles injection
|
|
143
|
-
โโโ dist/ # Production build
|
|
144
|
-
โโโ docs/ # Documentation
|
|
145
|
-
โโโ package.json # Dependencies
|
|
146
|
-
โโโ webpack.config.js # Build configuration
|
|
147
|
-
โโโ README.md # You are here!
|
|
97
|
+
All UI colours are controlled by CSS variables inside the widgetโs shadowโlike scope. To override, simply set these variables in your own stylesheet (with higher specificity).
|
|
98
|
+
|
|
99
|
+
Example (dark theme override):
|
|
100
|
+
```css
|
|
101
|
+
#ai-widget-root {
|
|
102
|
+
--bg-panel: rgba(20, 20, 20, 0.95);
|
|
103
|
+
--text-primary: #ffffff;
|
|
104
|
+
--user-bubble-bg: #333333;
|
|
105
|
+
/* ... see styles.js for full list */
|
|
106
|
+
}
|
|
148
107
|
```
|
|
149
108
|
|
|
150
109
|
---
|
|
151
110
|
|
|
152
|
-
##
|
|
111
|
+
## How It Works
|
|
153
112
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
| **Readability** | Content extraction |
|
|
159
|
-
| **Turndown** | HTML โ Markdown conversion |
|
|
160
|
-
| **Marked.js** | Markdown rendering |
|
|
161
|
-
| **Highlight.js** | Code syntax highlighting |
|
|
162
|
-
| **Font Awesome** | Icon library |
|
|
163
|
-
| **Webpack** | Build tool |
|
|
113
|
+
1. **Page Scraping** โ uses [Readability](https://github.com/mozilla/readability) and [Turndown](https://github.com/mixmark-io/turndown) to extract clean HTML, convert to Markdown, and capture JSONโLD structured data.
|
|
114
|
+
2. **Caching** โ the scraped content is stored in IndexedDB for 24 hours, reducing redundant scraping on subsequent visits.
|
|
115
|
+
3. **Conversation** โ user messages, together with the page context, are sent to the Groq API. The response is streamed (or not) and rendered with Markdown.
|
|
116
|
+
4. **SPA Support** โ a `MutationObserver` watches for URL changes and reโscrapes when navigation occurs.
|
|
164
117
|
|
|
165
118
|
---
|
|
166
119
|
|
|
167
|
-
##
|
|
168
|
-
|
|
169
|
-
### ๐ง **Smart Context Retrieval**
|
|
170
|
-
The assistant automatically scrapes and caches page content, providing the AI with rich context for accurate responses.
|
|
171
|
-
|
|
172
|
-
### ๐ผ๏ธ **Image Analysis**
|
|
173
|
-
Users can attach images for AI-powered analysis alongside text queries.
|
|
174
|
-
|
|
175
|
-
### ๐ **SPA Support**
|
|
176
|
-
Automatically detects Single Page Application navigation and refreshes context.
|
|
177
|
-
|
|
178
|
-
### ๐ **Structured Data Extraction**
|
|
179
|
-
Extracts JSON-LD, meta tags, Open Graph, and Twitter Cards for enhanced understanding.
|
|
120
|
+
## API Key & Security
|
|
180
121
|
|
|
181
|
-
|
|
182
|
-
|
|
122
|
+
- You **must** provide a valid [Groq API key](https://console.groq.com/keys) to use the assistant.
|
|
123
|
+
- The key is sent directly from the client to Groq. **Do not expose** your key in public repositories. We recommend:
|
|
124
|
+
- Using environment variables if you build the widget yourself.
|
|
125
|
+
- Treating the key as a userโsupplied value (e.g., from a backend proxy).
|
|
183
126
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
## ๐ Performance
|
|
187
|
-
|
|
188
|
-
- **Cache Hit Rate:** >90% for repeat visits
|
|
189
|
-
- **Average Response Time:** <1.5s (Groq optimized)
|
|
190
|
-
- **Bundle Size:** ~45KB (gzipped)
|
|
191
|
-
- **Memory Usage:** <30MB
|
|
192
|
-
- **Compatibility:** All modern browsers (Chrome 90+, Firefox 88+, Safari 14+)
|
|
127
|
+
For production, consider proxying requests through your own backend to keep the key secret.
|
|
193
128
|
|
|
194
129
|
---
|
|
195
130
|
|
|
196
|
-
##
|
|
197
|
-
|
|
198
|
-
We welcome contributions! Here's how you can help:
|
|
199
|
-
|
|
200
|
-
### ๐ **Report Bugs**
|
|
201
|
-
[Open an Issue](https://github.com/thekaifansari01/NexusWebAssistant/issues)
|
|
131
|
+
## Development
|
|
202
132
|
|
|
203
|
-
###
|
|
204
|
-
[Submit a Feature Request](https://github.com/thekaifansari01/NexusWebAssistant/issues/new?labels=enhancement)
|
|
133
|
+
### Build from Source
|
|
205
134
|
|
|
206
|
-
|
|
135
|
+
Clone the repository and install dependencies:
|
|
207
136
|
|
|
208
137
|
```bash
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
# Create a feature branch
|
|
214
|
-
git checkout -b feature/amazing-feature
|
|
215
|
-
|
|
216
|
-
# Make your changes
|
|
217
|
-
# Commit and push
|
|
218
|
-
git push origin feature/amazing-feature
|
|
219
|
-
|
|
220
|
-
# Open a Pull Request
|
|
138
|
+
git clone https://github.com/thekaifansari01/nexusWebAssistant.git
|
|
139
|
+
cd nexusWebAssistant
|
|
140
|
+
npm install
|
|
221
141
|
```
|
|
222
142
|
|
|
223
|
-
|
|
224
|
-
- Follow existing code style
|
|
225
|
-
- Write meaningful commit messages
|
|
226
|
-
- Update documentation
|
|
227
|
-
- Add tests where applicable
|
|
143
|
+
Build the minified bundle:
|
|
228
144
|
|
|
229
|
-
|
|
145
|
+
```bash
|
|
146
|
+
npm run build
|
|
147
|
+
```
|
|
230
148
|
|
|
231
|
-
|
|
149
|
+
### File Structure
|
|
232
150
|
|
|
233
|
-
|
|
151
|
+
- `src/` โ source code
|
|
152
|
+
- `core/` โ config and state
|
|
153
|
+
- `ui/` โ DOM manipulation and styles
|
|
154
|
+
- `api/` โ Groq communication
|
|
155
|
+
- `scraper/` โ advanced scraping engine
|
|
156
|
+
- `utils/` โ caching helpers
|
|
157
|
+
- `dist/` โ output bundle
|
|
158
|
+
- `index.js` โ entry point
|
|
234
159
|
|
|
235
160
|
---
|
|
236
161
|
|
|
237
|
-
##
|
|
162
|
+
## Contributing
|
|
238
163
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
- **
|
|
164
|
+
Contributions are welcome! Please read the [Contributing Guidelines](CONTRIBUTING.md) before submitting a PR.
|
|
165
|
+
|
|
166
|
+
- **Report bugs** via [Issues](https://github.com/thekaifansari01/nexusWebAssistant/issues)
|
|
167
|
+
- **Request features** โ weโre always open to suggestions.
|
|
242
168
|
|
|
243
169
|
---
|
|
244
170
|
|
|
245
|
-
##
|
|
171
|
+
## License
|
|
246
172
|
|
|
247
|
-
|
|
248
|
-
- **Issues:** [GitHub Issues](https://github.com/thekaifansari01/NexusWebAssistant/issues)
|
|
249
|
-
- **Email:** contact@kaifansari.com
|
|
173
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
250
174
|
|
|
251
175
|
---
|
|
252
176
|
|
|
253
|
-
##
|
|
177
|
+
## Acknowledgements
|
|
254
178
|
|
|
255
|
-
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
- ๐ฆ Sharing on social media
|
|
259
|
-
- ๐งโ๐ป Contributing to the codebase
|
|
179
|
+
- [Groq](https://groq.com/) for the lightningโfast inference.
|
|
180
|
+
- [Readability](https://github.com/mozilla/readability) & [Turndown](https://github.com/mixmark-io/turndown) for content extraction.
|
|
181
|
+
- [Font Awesome](https://fontawesome.com/) & [Highlight.js](https://highlightjs.org/) for UI enhancements.
|
|
260
182
|
|
|
261
183
|
---
|
|
262
184
|
|
|
263
|
-
**
|
|
264
|
-
|
|
265
|
-
[โฌ Back to Top](#nexus-web-assistant-)
|
|
185
|
+
**Nexus Web Assistant** โ making every webpage intelligent. ๐
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";const n={API_URL:"https://api.groq.com/openai/v1/chat/completions",API_KEY:"",MODEL:"meta-llama/llama-4-scout-17b-16e-instruct",BOT_NAME:"Nexus AI",GREETING:"๐ Hey there! I'm **Nexus AI** โ your smart assistant for this page.\n\nAsk me anything about the content you see here. I can:\nโข ๐ Summarize articles\nโข ๐ Find specific details\nโข ๐ Explain tables and data\nโข ๐ก Answer questions instantly\n\nJust type your question below and I'll do my best to help! ๐",SYSTEM_PROMPT:"\nYou are Nexus AI, a smart assistant. Use the page context provided in every response.\n\n**Rules:**\n- Always prioritize the given page context over general knowledge.\n- Quote specific details (headings, tables, code, lists, etc.) from the context.\n- Structure answers clearly with bullet points or headings where helpful.\n- Be crisp, professional, and friendly. Use Markdown.\n- If information is not in context, politely say so and offer to help with related topics.\n- Keep responses concise (under 600 tokens).\n ",MAX_FILE_SIZE:5242880,ALLOWED_FILE_TYPES:["image/*","application/pdf","text/*","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document"],COLORS:{bg:"#0a0a0a",panelBg:"rgba(12, 12, 12, 0.85)",border:"#2a2a2a",borderHover:"#444444",textPrimary:"#f5f5f5",textSecondary:"#a0a0a0",userBubble:"#1e1e1e",botBubble:"transparent"}},e=n=>document.getElementById(n);function t(n){if(!n)return"";try{if("undefined"!=typeof marked&&marked.parse)return marked.parse(n,{breaks:!0,gfm:!0})}catch(n){console.warn("Markdown parse failed:",n)}return n.replace(/\n/g,"<br>")}function a(){const a=window.__NEXUS_CONFIG?.BOT_NAME||n.BOT_NAME,o=t(window.__NEXUS_CONFIG?.GREETING||n.GREETING),r=document.createElement("div");r.id="ai-widget-root",r.innerHTML=`\n <div class="ai-panel" id="aiPanel">\n <div class="ai-header">\n <div class="ai-header-title">\n <span class="status-dot"></span>\n ${a}\n </div>\n <div class="ai-header-actions">\n <button class="ai-theme-toggle" id="aiThemeToggle" aria-label="Toggle theme">\n <i class="fas fa-moon"></i>\n </button>\n <button class="ai-close-btn" id="aiCloseBtn" aria-label="Close chat">\n <i class="fas fa-xmark"></i>\n </button>\n </div>\n </div>\n <div class="ai-messages" id="aiMessages">\n <div class="msg bot">\n ${o}\n </div>\n </div>\n <div class="ai-input-area" id="aiInputArea">\n <div id="aiPreviewContainer"></div>\n <div class="ai-input-container">\n <button class="icon-btn" id="aiAttachBtn" aria-label="Attach image or file">\n <i class="fas fa-paperclip"></i>\n </button>\n <textarea \n id="aiInput" \n placeholder="Message..." \n rows="1"\n autocomplete="off"\n spellcheck="true"\n ></textarea>\n <button class="icon-btn send-btn" id="aiSendBtn">\n <i class="fas fa-arrow-up"></i>\n </button>\n </div>\n <div class="ai-input-footer">\n <span class="ai-char-count" id="aiCharCount">0</span>\n </div>\n </div>\n </div>\n <button class="ai-fab" id="aiFab" aria-label="Open AI Chat">\n <span class="bot-icon">\n <i class="fas fa-robot" style="color: #ffffff !important; font-size: 22px;"></i>\n </span>\n </button>\n `,document.body.appendChild(r);const i=e("aiInput");return i&&i.addEventListener("input",()=>{!function(n){n.style.height="auto",n.style.height=Math.min(n.scrollHeight,150)+"px"}(i),function(n){const t=e("aiCharCount");if(t){const e=n.value.length;t.textContent=e>0?`${e}`:"0",t.style.color=e>500?"#ef4444":"#a0a0a0"}}(i)}),r}function o(n,t){if(!n)return!1;const a=void 0!==t?t:!n.classList.contains("open");return n.classList.toggle("open",a),a&&setTimeout(()=>{const n=e("aiInput");n&&n.focus()},100),a}function r(n,e,a="bot",o=null){if(!n)return null;const r=document.createElement("div");if(r.className=`msg ${a}`,"bot"===a){const n=t(e);r.innerHTML=n,"undefined"!=typeof hljs&&r.querySelectorAll("pre code").forEach(n=>{hljs.highlightElement(n)})}else{let n=e||"";o&&(n+=`<br><img src="${o}" class="image-preview" alt="attached">`),r.innerHTML=n}return n.appendChild(r),n.scrollTo({top:n.scrollHeight,behavior:"smooth"}),r}let i=null;function s(n,e){n&&(i&&(i.remove(),i=null),e&&(i=document.createElement("div"),i.className="typing-indicator",i.innerHTML="<span></span><span></span><span></span>",n.appendChild(i),n.scrollTo({top:n.scrollHeight,behavior:"smooth"})))}function c(n,e){if(!n)return;if(n.innerHTML="",!e)return;const t=e.type?.startsWith("image/")||e.dataUrl?.startsWith("data:image"),a="application/pdf"===e.type||e.name?.endsWith(".pdf"),o=e.type?.includes("document")||e.name?.endsWith(".docx")||e.name?.endsWith(".txt"),r=document.createElement("div");r.className="image-preview-pill";let i="fa-file",s="rgba(255,255,255,0.04)";var c;t?(i="fa-image",s="rgba(255,255,255,0.04)"):a?(i="fa-file-pdf",s="rgba(255,255,255,0.04)"):o&&(i="fa-file-lines",s="rgba(255,255,255,0.04)"),r.innerHTML=`\n ${t?`<img src="${e.dataUrl}" alt="preview" class="preview-thumb">`:`<i class="fas ${i}" style="font-size: 20px; color: #888;"></i>`}\n <span class="file-name">${e.name||"file"}</span>\n <span class="file-size">${c=e.size||0,c<1024?c+" B":c<1048576?(c/1024).toFixed(1)+" KB":(c/1048576).toFixed(1)+" MB"}</span>\n <button class="remove-file" id="aiRemoveFile" aria-label="Remove file">\n <i class="fas fa-times"></i>\n </button>\n `,r.style.background=s,n.appendChild(r),document.getElementById("aiRemoveFile")?.addEventListener("click",()=>{n.innerHTML="";const e=new CustomEvent("file-removed");document.dispatchEvent(e)})}const l={isOpen:!1,isProcessing:!1,attachedFile:null,conversationHistory:[]};function d(n){Object.assign(l,n)}const p="NexusAICache",u="pageData";let m=null;function g(){return new Promise((n,e)=>{if(m)return void n(m);const t=indexedDB.open(p,1);t.onupgradeneeded=n=>{const e=n.target.result;if(!e.objectStoreNames.contains(u)){const n=e.createObjectStore(u,{keyPath:"id"});n.createIndex("url","url",{unique:!1}),n.createIndex("timestamp","timestamp",{unique:!1}),console.log("๐ฆ IndexedDB store created")}},t.onsuccess=e=>{m=e.target.result,n(m)},t.onerror=n=>{e(n.target.error)}})}async function b(n){try{const e=(await g()).transaction(u,"readwrite").objectStore(u),t=window.location.href,a={id:t,url:t,data:n,timestamp:Date.now()};return new Promise((n,t)=>{const o=e.put(a);o.onsuccess=()=>{console.log("๐พ Data cached in IndexedDB"),n()},o.onerror=n=>{t(n.target.error)}})}catch(n){console.warn("IndexedDB write failed:",n)}}let h=null,f=null;function x(n){return new Promise((e,t)=>{const a=document.createElement("script");a.src=n,a.async=!0,a.onload=e,a.onerror=t,document.head.appendChild(a)})}function w(){console.log("๐ท๏ธ Advanced scraping started...");const n={title:document.title,url:window.location.href,timestamp:(new Date).toISOString(),pageType:y(),readability:null,structuredData:[],markdown:"",summary:"",metadata:{}};try{if(h){const e=document.cloneNode(!0),t=new h(e).parse();if(t){if(n.readability={title:t.title,byline:t.byline,excerpt:t.excerpt,content:t.content,textContent:t.textContent},f){const e=new f({headingStyle:"atx",codeBlockStyle:"fenced",emDelimiter:"*",bulletListMarker:"-"});e.addRule("strikethrough",{filter:["del","s","strike"],replacement:function(n){return"~~"+n+"~~"}}),n.markdown=e.turndown(t.content)}n.summary=t.excerpt||t.textContent.substring(0,300)}}}catch(e){console.warn("Readability failed, falling back to basic scraper:",e),n.readability=null}try{document.querySelectorAll('script[type="application/ld+json"]').forEach(e=>{try{const t=JSON.parse(e.textContent);n.structuredData.push(t)}catch(n){}})}catch(n){console.warn("JSON-LD extraction failed:",n)}if(n.metadata=function(){const n={},e=document.querySelector('meta[name="description"]');e&&(n.description=e.content);const t=document.querySelector('meta[name="keywords"]');t&&(n.keywords=t.content),["title","description","image","url","type","site_name"].forEach(e=>{const t=document.querySelector(`meta[property="og:${e}"]`);t&&(n[`og_${e}`]=t.content)}),["card","title","description","image"].forEach(e=>{const t=document.querySelector(`meta[name="twitter:${e}"]`);t&&(n[`twitter_${e}`]=t.content)});const a=document.querySelector('link[rel="canonical"]');return a&&(n.canonical=a.href),n}(),!n.readability){console.log("โ ๏ธ Using fallback scraper");const e=function(){const n=document.body.cloneNode(!0);["nav","header","footer","aside",".sidebar",".navigation",".menu",".ads",".cookie-banner",".popup",".overlay","script","style","noscript","iframe"].forEach(e=>{n.querySelectorAll(e).forEach(n=>n.remove())});let e=n.textContent||"";e=e.replace(/\s+/g," ").trim();const t=e.substring(0,500)+"...";return{content:e,summary:t}}();n.markdown=e.content,n.summary=e.summary}return n.aiContext=v(n),console.log("โ
Advanced scraping complete"),n}function y(){const n=window.location.href,e=document.title.toLowerCase(),t=document.querySelector('meta[property="og:type"]');if(t){const n=t.content;if(n.includes("article"))return"article";if(n.includes("product"))return"product";if(n.includes("website"))return"website"}return n.includes("/blog/")||n.includes("/post/")||e.includes("blog")?"blog":n.includes("/product/")||n.includes("/shop/")?"product":n.includes("/portfolio/")||n.includes("/project/")?"portfolio":n===window.location.origin+"/"||n===window.location.origin?"homepage":"general"}function v(n){let e="";if(e+=`# ๐ ${n.title}\n\n`,e+=`**URL:** ${n.url}\n`,e+=`**Type:** ${n.pageType}\n`,e+=`**Scraped:** ${new Date(n.timestamp).toLocaleString()}\n\n`,Object.keys(n.metadata).length>0){e+="## ๐ Metadata\n";for(const[t,a]of Object.entries(n.metadata))a&&a.length>0&&(e+=`- **${t}:** ${a}\n`);e+="\n"}if(n.structuredData&&n.structuredData.length>0&&(e+="## ๐ Structured Data (Schema.org)\n",n.structuredData.forEach((n,t)=>{e+=`\n### Schema ${t+1}\n`,e+=`\`\`\`json\n${JSON.stringify(n,null,2)}\n\`\`\`\n`}),e+="\n"),n.markdown){e+="## ๐ Main Content\n\n";const t=4e3,a=n.markdown.length>t?n.markdown.substring(0,t)+"... (truncated)":n.markdown;e+=a,e+="\n\n"}return n.summary&&(e+="## ๐ Summary\n\n",e+=n.summary,e+="\n\n"),e+="---\n",e+="*๐ค AI Instructions: Use this context to answer user questions about this page. Be precise, reference specific details from the content, and maintain a friendly professional tone.*\n",e}async function k(e,t,a=null){if(l.isProcessing)return;d({isProcessing:!0});const o=document.getElementById("aiSendBtn"),i=document.getElementById("aiInput");o&&(o.disabled=!0),i&&(i.disabled=!0),s(e,!0);const c=[];if(t&&c.push({type:"text",text:t}),a&&c.push({type:"image_url",image_url:{url:a}}),l.conversationHistory.push({role:"user",content:t||"(image)"}),l.conversationHistory.length>11){const n=l.conversationHistory[0],e=l.conversationHistory.slice(-10);l.conversationHistory=[n,...e]}try{const t=await async function(){let n=await async function(){try{const n=(await g()).transaction(u,"readonly").objectStore(u),e=window.location.href;return new Promise((t,a)=>{const o=n.get(e);o.onsuccess=n=>{const a=n.target.result;if(!a)return void t(null);const o=Date.now();if(o-a.timestamp>864e5)return async function(n){try{(await g()).transaction(u,"readwrite").objectStore(u).delete(n)}catch(n){console.warn("IndexedDB delete failed:",n)}}(e),void t(null);console.log(`๐ฆ Cache hit! Age: ${Math.floor((o-a.timestamp)/6e4)} minutes`),t(a.data)},o.onerror=n=>{a(n.target.error)}})}catch(n){return console.warn("IndexedDB read failed:",n),null}}();if(n)return console.log("๐ฆ Using cached page data"),n;console.log("๐ Scraping page data...");const e=v(w());return await b(e),console.log("๐พ Page data cached"),e}(),a=window.__NEXUS_CONFIG||n,o=[{role:"system",content:`${a.SYSTEM_PROMPT}\n\n## Current Page Context\n${t}`}];for(let n=1;n<l.conversationHistory.length-1;n++)o.push({role:l.conversationHistory[n].role,content:l.conversationHistory[n].content});o.push({role:"user",content:c});const i=await fetch(a.API_URL,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a.API_KEY}`},body:JSON.stringify({model:a.MODEL,messages:o,temperature:.7,max_tokens:600,stream:!1})});if(!i.ok){const n=await i.json().catch(()=>({}));throw new Error(`API error ${i.status}: ${n.error?.message||i.statusText}`)}const d=await i.json(),p=d.choices?.[0]?.message?.content||"Sorry, I didn't understand that.";l.conversationHistory.push({role:"assistant",content:p}),s(e,!1),r(e,p,"bot")}catch(n){console.error("Groq API Error:",n),s(e,!1),r(e,`โ ๏ธ Oops! ${n.message||"Something went wrong."} Please try again.`,"bot")}finally{d({isProcessing:!1}),o&&(o.disabled=!1),i&&(i.disabled=!1,i.value="",i.focus())}}function E(){const n=document.getElementById("ai-widget-root");if(!n)return;const e="dark"===(n.dataset.theme||"dark")?"light":"dark";n.dataset.theme=e,localStorage.setItem("nexus-theme",e),window.__NEXUS_CONFIG&&(window.__NEXUS_CONFIG.THEME=e);const t=document.getElementById("aiThemeToggle");t&&(t.innerHTML="dark"===e?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',t.title="dark"===e?"Switch to Light":"Switch to Dark"),console.log(`๐ Theme switched to: ${e}`)}async function S(){const t=function(){const e=window.NexusConfig||{},t=function(){const n=window.NexusConfig||{};if(n.theme)return n.theme;const e=localStorage.getItem("nexus-theme");if(e)return e;const t=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-theme")||t.classList.contains("dark-mode"))return"dark";if(t.classList.contains("light")||t.classList.contains("light-theme")||t.classList.contains("light-mode"))return"light";const a=document.documentElement.getAttribute("data-theme");if("dark"===a||"light"===a)return a;const o=document.querySelector('meta[name="theme-color"]');if(o){const n=o.content;if(n.startsWith("#")&&parseInt(n.slice(1,3),16)<100)return"dark"}return window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,"dark"}();return{API_URL:n.API_URL,API_KEY:e.apiKey||n.API_KEY,MODEL:e.model||n.MODEL,BOT_NAME:e.botName||n.BOT_NAME,GREETING:e.greeting||n.GREETING,SYSTEM_PROMPT:e.systemPrompt||n.SYSTEM_PROMPT,THEME:t,COLORS:n.COLORS}}();window.__NEXUS_CONFIG=t,await async function(){const n=document.createElement("link");n.href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css",n.rel="stylesheet",document.head.appendChild(n);const e=document.createElement("link");e.href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap",e.rel="stylesheet",document.head.appendChild(e);const t=document.createElement("script");t.src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js",t.async=!0,document.head.appendChild(t);const a=document.createElement("link");a.href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css",a.rel="stylesheet",document.head.appendChild(a);const o=document.createElement("script");o.src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js",o.async=!0,document.head.appendChild(o),await async function(){await Promise.all([x("https://cdnjs.cloudflare.com/ajax/libs/readability/0.4.4/Readability.min.js"),x("https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js")]),h=window.Readability,f=window.TurndownService,console.log("๐ Scraper libraries loaded")}()}(),function(){const n=document.createElement("style");n.id="ai-chat-widget-styles",n.textContent="\n /* ===== MONOCHROME THEME VARIABLES ===== */\n #ai-widget-root {\n /* Dark theme defaults */\n --bg-panel: rgba(10, 10, 10, 0.85);\n --bg-input: rgba(255, 255, 255, 0.03);\n --border-color: rgba(255, 255, 255, 0.08);\n --text-primary: #f0f0f0;\n --text-secondary: #999;\n --user-bubble-bg: #2a2a2a;\n --bot-bubble-bg: rgba(255, 255, 255, 0.04);\n --shadow-color: rgba(0, 0, 0, 0.6);\n --fab-bg: rgba(255, 255, 255, 0.05);\n --accent: #aaa;\n --send-bg: #f0f0f0;\n --send-color: #111;\n }\n\n /* ===== LIGHT THEME ===== */\n #ai-widget-root[data-theme=\"light\"] {\n --bg-panel: rgba(245, 245, 245, 0.92);\n --bg-input: rgba(0, 0, 0, 0.02);\n --border-color: rgba(0, 0, 0, 0.08);\n --text-primary: #111;\n --text-secondary: #555;\n --user-bubble-bg: #e0e0e0;\n --bot-bubble-bg: rgba(255, 255, 255, 0.5);\n --shadow-color: rgba(0, 0, 0, 0.05);\n --fab-bg: rgba(255, 255, 255, 0.6);\n --accent: #888;\n --send-bg: #222;\n --send-color: #fff;\n }\n\n /* ===== ANIMATIONS ===== */\n @keyframes slideUpFade {\n 0% { opacity: 0; transform: translateY(30px) scale(0.95); }\n 100% { opacity: 1; transform: translateY(0) scale(1); }\n }\n @keyframes pulseDot {\n 0%, 100% { transform: scale(0.8); opacity: 0.5; }\n 50% { transform: scale(1.3); opacity: 1; }\n }\n @keyframes typing {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(2px); }\n 40% { opacity: 1; transform: translateY(-2px); }\n }\n @keyframes previewPop {\n 0% { opacity: 0; transform: scale(0.8) translateY(10px); }\n 100% { opacity: 1; transform: scale(1) translateY(0); }\n }\n @keyframes fabPulse {\n 0% { box-shadow: 0 0 0 0 rgba(255,255,255,0.1); }\n 70% { box-shadow: 0 0 0 20px rgba(255,255,255,0); }\n 100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); }\n }\n\n /* ===== ROOT ===== */\n #ai-widget-root {\n all: initial;\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 999999;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n pointer-events: none;\n color: var(--text-primary);\n }\n #ai-widget-root * { box-sizing: border-box; pointer-events: auto; }\n\n /* ===== ICONS FIX ===== */\n #ai-widget-root .fas,\n #ai-widget-root .far,\n #ai-widget-root .fab {\n font-family: 'Font Awesome 6 Free', 'Font Awesome 6 Brands' !important;\n font-weight: 900 !important;\n font-style: normal;\n font-variant: normal;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n }\n #ai-widget-root .far { font-weight: 400 !important; }\n #ai-widget-root .fab {\n font-family: 'Font Awesome 6 Brands' !important;\n font-weight: 400 !important;\n }\n\n /* ===== FLOATING ACTION BUTTON ===== */\n .ai-fab {\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: var(--fab-bg);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--border-color);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-primary);\n transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);\n user-select: none;\n box-shadow: 0 8px 32px var(--shadow-color);\n position: relative;\n z-index: 10;\n will-change: transform, opacity;\n animation: fabPulse 3s infinite;\n }\n .ai-fab:hover {\n transform: scale(1.08) rotate(-5deg);\n box-shadow: 0 12px 40px var(--shadow-color);\n border-color: var(--accent);\n }\n .ai-fab .bot-icon i {\n color: var(--text-primary) !important;\n font-size: 26px !important;\n filter: drop-shadow(0 2px 8px rgba(0,0,0,0.2));\n }\n .ai-panel.open ~ .ai-fab {\n opacity: 0;\n transform: scale(0.8);\n pointer-events: none;\n animation: none;\n }\n\n /* ===== CHAT PANEL ===== */\n .ai-panel {\n position: absolute;\n bottom: 0;\n right: 0;\n width: 420px;\n max-width: calc(100vw - 48px);\n height: 620px;\n max-height: calc(100vh - 48px);\n background: var(--bg-panel);\n backdrop-filter: blur(32px);\n -webkit-backdrop-filter: blur(32px);\n border-radius: 28px;\n box-shadow: 0 32px 80px var(--shadow-color), 0 0 0 1px var(--border-color);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n opacity: 0;\n visibility: hidden;\n transform: translateY(20px) scale(0.96);\n transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n border: 1px solid var(--border-color);\n }\n .ai-panel.open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0) scale(1);\n }\n\n /* ===== HEADER ===== */\n .ai-header {\n padding: 20px 24px;\n border-bottom: 1px solid var(--border-color);\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-shrink: 0;\n }\n .ai-header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .ai-header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n color: var(--text-primary);\n font-weight: 600;\n font-size: 16px;\n letter-spacing: -0.3px;\n }\n .status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 12px rgba(128,128,128,0.4);\n animation: pulseDot 2s infinite;\n flex-shrink: 0;\n }\n .ai-theme-toggle,\n .ai-close-btn {\n background: transparent;\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n width: 32px;\n height: 32px;\n border-radius: 50%;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ai-theme-toggle:hover,\n .ai-close-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n transform: rotate(90deg);\n }\n .ai-theme-toggle:hover {\n transform: rotate(180deg) !important;\n }\n\n /* ===== MESSAGES ===== */\n .ai-messages {\n flex: 1;\n padding: 24px 24px 16px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 20px;\n scroll-behavior: smooth;\n }\n .ai-messages::-webkit-scrollbar { width: 4px; }\n .ai-messages::-webkit-scrollbar-track { background: transparent; }\n .ai-messages::-webkit-scrollbar-thumb {\n background: var(--border-color);\n border-radius: 10px;\n }\n\n /* ===== MESSAGE BUBBLES ===== */\n .msg {\n max-width: 85%;\n font-size: 14.5px;\n line-height: 1.7;\n word-break: break-word;\n animation: slideUpFade 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n color: var(--text-primary);\n padding: 12px 18px;\n border-radius: 18px;\n position: relative;\n box-shadow: 0 2px 8px var(--shadow-color);\n }\n /* User message โ clearly distinguishable */\n .msg.user {\n align-self: flex-end;\n background: var(--user-bubble-bg);\n border: 1px solid var(--border-color);\n border-bottom-right-radius: 4px;\n }\n /* Bot message โ different shade */\n .msg.bot {\n align-self: flex-start;\n background: var(--bot-bubble-bg);\n border: 1px solid var(--border-color);\n border-bottom-left-radius: 4px;\n max-width: 95%;\n }\n\n /* Small tail effects for clarity */\n .msg.user::after {\n content: '';\n position: absolute;\n bottom: 0;\n right: -6px;\n width: 12px;\n height: 12px;\n background: var(--user-bubble-bg);\n border-radius: 0 0 12px 0;\n clip-path: polygon(0 0, 100% 0, 100% 100%);\n }\n .msg.bot::before {\n content: '';\n position: absolute;\n bottom: 0;\n left: -6px;\n width: 12px;\n height: 12px;\n background: var(--bot-bubble-bg);\n border-radius: 0 0 0 12px;\n clip-path: polygon(0 0, 100% 100%, 100% 0);\n }\n\n /* ===== MARKDOWN RENDER ===== */\n .msg.bot p { margin: 0 0 12px 0; }\n .msg.bot p:last-child { margin-bottom: 0; }\n .msg.bot code {\n background: var(--border-color);\n padding: 2px 8px;\n border-radius: 6px;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n border: 1px solid var(--border-color);\n }\n .msg.bot pre {\n background: var(--border-color);\n padding: 16px;\n border-radius: 14px;\n overflow-x: auto;\n margin: 12px 0;\n border: 1px solid var(--border-color);\n }\n .msg.bot pre code { background: transparent; padding: 0; border: none; }\n .msg.bot ul, .msg.bot ol { margin: 8px 0 12px 20px; padding-left: 0; }\n .msg.bot li { margin-bottom: 6px; }\n .msg.bot a { color: var(--accent); text-decoration: none; border-bottom: 1px dotted var(--accent); }\n .msg .image-preview {\n max-width: 200px;\n border-radius: 14px;\n margin-top: 8px;\n border: 1px solid var(--border-color);\n }\n\n /* ===== INPUT AREA ===== */\n .ai-input-area {\n padding: 12px 20px 20px;\n background: transparent;\n flex-shrink: 0;\n border-top: 1px solid var(--border-color);\n }\n .ai-input-container {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n background: var(--bg-input);\n border: 1px solid var(--border-color);\n border-radius: 20px;\n padding: 4px 4px 4px 16px;\n transition: all 0.3s ease;\n min-height: 48px;\n box-shadow: inset 0 2px 6px rgba(0,0,0,0.02);\n }\n .ai-input-container:focus-within {\n border-color: var(--accent);\n background: var(--bg-input);\n box-shadow: 0 0 0 4px rgba(128,128,128,0.08);\n }\n .ai-input-container textarea {\n flex: 1;\n background: transparent;\n border: none;\n color: var(--text-primary);\n font-size: 14px;\n outline: none;\n font-family: inherit;\n padding: 10px 0;\n resize: none;\n max-height: 150px;\n line-height: 1.6;\n min-height: 28px;\n scrollbar-width: none;\n }\n .ai-input-container textarea::placeholder {\n color: var(--text-secondary);\n font-weight: 400;\n }\n\n .icon-btn {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n width: 38px;\n height: 38px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n align-self: flex-end;\n margin-bottom: 2px;\n }\n .icon-btn:hover {\n color: var(--text-primary);\n background: var(--border-color);\n transform: scale(1.05);\n }\n .icon-btn:active { transform: scale(0.92); }\n\n .send-btn {\n background: var(--send-bg);\n color: var(--send-color);\n width: 38px;\n height: 38px;\n align-self: flex-end;\n margin-bottom: 2px;\n box-shadow: 0 4px 12px var(--shadow-color);\n transition: all 0.3s ease;\n border: none;\n }\n .send-btn:hover {\n transform: scale(1.08) rotate(-3deg);\n box-shadow: 0 6px 20px var(--shadow-color);\n }\n .send-btn:active { transform: scale(0.92); }\n .send-btn:disabled {\n background: var(--border-color);\n color: var(--text-secondary);\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n }\n\n .ai-input-footer {\n display: flex;\n justify-content: flex-end;\n padding: 4px 6px 0;\n min-height: 20px;\n }\n .ai-char-count {\n font-size: 11px;\n color: var(--text-secondary);\n font-weight: 500;\n transition: color 0.2s;\n opacity: 0.5;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n }\n\n /* ===== PREVIEW PILL ===== */\n #aiPreviewContainer {\n margin-bottom: 8px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n .image-preview-pill {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: var(--bg-input);\n border: 1px solid var(--border-color);\n border-radius: 14px;\n padding: 6px 12px 6px 6px;\n animation: previewPop 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n box-shadow: 0 4px 16px var(--shadow-color);\n transition: all 0.2s;\n max-width: 100%;\n }\n .image-preview-pill:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 24px var(--shadow-color);\n }\n .image-preview-pill .preview-thumb {\n width: 32px;\n height: 32px;\n border-radius: 10px;\n object-fit: cover;\n border: 1px solid var(--border-color);\n }\n .image-preview-pill .file-name {\n font-size: 12px;\n color: var(--text-primary);\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n }\n .image-preview-pill .file-size {\n font-size: 10px;\n color: var(--text-secondary);\n opacity: 0.6;\n }\n .image-preview-pill .remove-file {\n background: var(--border-color);\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: 50%;\n transition: all 0.2s;\n margin-left: 2px;\n width: 26px;\n height: 26px;\n }\n .image-preview-pill .remove-file:hover {\n color: #ef4444;\n background: rgba(239,68,68,0.15);\n transform: scale(1.1);\n }\n\n /* ===== TYPING INDICATOR ===== */\n .typing-indicator {\n display: flex;\n gap: 6px;\n padding: 12px 0;\n align-self: flex-start;\n background: var(--bot-bubble-bg);\n padding: 10px 16px;\n border-radius: 18px;\n border: 1px solid var(--border-color);\n }\n .typing-indicator span {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--text-secondary);\n animation: typing 1.4s infinite;\n box-shadow: 0 2px 6px rgba(0,0,0,0.02);\n }\n .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }\n .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }\n\n /* ===== RESPONSIVE ===== */\n @media (max-width: 480px) {\n .ai-panel {\n width: calc(100vw - 32px);\n right: 16px;\n height: 80vh;\n max-height: calc(100vh - 48px);\n border-radius: 20px;\n }\n .ai-fab {\n width: 52px;\n height: 52px;\n }\n .ai-fab .bot-icon { font-size: 22px; }\n .ai-input-area {\n padding: 8px 14px 14px;\n }\n .ai-input-container {\n border-radius: 16px;\n padding: 2px 2px 2px 12px;\n min-height: 42px;\n }\n .ai-input-container textarea { font-size: 13px; padding: 8px 0; }\n .icon-btn { width: 32px; height: 32px; }\n .send-btn { width: 32px; height: 32px; }\n .image-preview-pill .file-name { max-width: 100px; }\n }\n ",document.head.appendChild(n)}(),a();const i=document.getElementById("ai-widget-root");i&&(i.dataset.theme=t.THEME||"dark");const s=document.getElementById("aiThemeToggle");if(s){const n="dark"===t.THEME;s.innerHTML=n?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',s.title=n?"Switch to Light":"Switch to Dark",s.addEventListener("click",E)}const m=e("aiPanel"),y=e("aiMessages"),S=e("aiFab"),I=e("aiCloseBtn"),T=e("aiSendBtn"),L=e("aiInput"),P=e("aiAttachBtn"),O=e("aiPreviewContainer");!function(n){let e=window.location.href;new MutationObserver(()=>{window.location.href!==e&&(e=window.location.href,console.log("๐ Page navigation detected"),n())}).observe(document,{subtree:!0,childList:!0});const t=history.pushState;history.pushState=function(){t.apply(this,arguments),setTimeout(()=>{window.location.href!==e&&(e=window.location.href,n())},500)}}(()=>{console.log("๐ Page changed, refreshing context..."),async function(){console.log("๐ Forcing page context refresh...");const n=v(w());await b(n),console.log("โ
Page context refreshed")}(),r(y,"๐ Page context updated!","bot")});const A=document.createElement("input");A.type="file",A.accept="image/*",A.style.display="none",A.id="aiFileInput",document.body.appendChild(A),P?.addEventListener("click",()=>A.click()),A.addEventListener("change",n=>{const e=n.target.files?.[0];if(!e)return;if(!e.type.startsWith("image/"))return void alert("Please select an image file.");const t=new FileReader;t.onload=n=>{const t={dataUrl:n.target.result,name:e.name,type:e.type};d({attachedFile:t}),c(O,t);const a=document.getElementById("aiRemoveFile");a&&a.addEventListener("click",()=>{d({attachedFile:null}),c(O,null),L?.focus()}),L?.focus()},t.readAsDataURL(e),n.target.value=""}),S?.addEventListener("click",()=>{d({isOpen:o(m,!0)})}),I?.addEventListener("click",()=>{d({isOpen:o(m,!1)})}),document.addEventListener("click",n=>{const e=document.getElementById("ai-widget-root");e&&l.isOpen&&!e.contains(n.target)&&d({isOpen:o(m,!1)})});const M=()=>{const n=L?.value.trim();if(!n&&!l.attachedFile)return;if(l.isProcessing)return;const e=l.attachedFile?.dataUrl||null;r(y,n,"user",e),L.value="",L.style.height="auto",c(O,null);const t=l.attachedFile?.dataUrl||null;d({attachedFile:null}),k(y,n,t)};T?.addEventListener("click",M),L?.addEventListener("keydown",n=>{"Enter"!==n.key||n.shiftKey||(n.preventDefault(),M())});const C=await async function(){try{const n=(await g()).transaction(u,"readonly").objectStore(u);return new Promise(e=>{const t=n.count();t.onsuccess=()=>{e({totalEntries:t.result,database:p,version:1})},t.onerror=()=>e(null)})}catch{return null}}();console.log("๐ Cache Stats:",C||"No cache yet"),console.log("โจ Nexus AI v2.0 initialized with theme:",t.THEME)}l.conversationHistory=[{role:"system",content:n.SYSTEM_PROMPT}],"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S()})();
|
|
1
|
+
(()=>{"use strict";const n={API_URL:"https://trynexusweb.vercel.app/api/chat",API_KEY:"",MODEL:"meta-llama/llama-4-scout-17b-16e-instruct",BOT_NAME:"Nexus AI",GREETING:"๐ Hey there! I'm **Nexus AI** โ your smart assistant for this page.\n\nAsk me anything about the content you see here. I can:\nโข ๐ Summarize articles\nโข ๐ Find specific details\nโข ๐ Explain tables and data\nโข ๐ก Answer questions instantly\n\nJust type your question below and I'll do my best to help! ๐",SYSTEM_PROMPT:"\nYou are Nexus AI, a smart assistant. Use the page context provided in every response.\n\n**Rules:**\n- Always prioritize the given page context over general knowledge.\n- Quote specific details (headings, tables, code, lists, etc.) from the context.\n- Structure answers clearly with bullet points or headings where helpful.\n- Be crisp, professional, and friendly. Use Markdown.\n- If information is not in context, politely say so and offer to help with related topics.\n- Keep responses concise (under 600 tokens).\n ",MAX_FILE_SIZE:5242880,ALLOWED_FILE_TYPES:["image/*","application/pdf","text/*","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document"],COLORS:{bg:"#0a0a0a",panelBg:"rgba(12, 12, 12, 0.85)",border:"#2a2a2a",borderHover:"#444444",textPrimary:"#f5f5f5",textSecondary:"#a0a0a0",userBubble:"#1e1e1e",botBubble:"transparent"}},e=n=>document.getElementById(n);function t(n){if(!n)return"";try{if("undefined"!=typeof marked&&marked.parse)return marked.parse(n,{breaks:!0,gfm:!0})}catch(n){console.warn("Markdown parse failed:",n)}return n.replace(/\n/g,"<br>")}function a(){const a=window.__NEXUS_CONFIG?.BOT_NAME||n.BOT_NAME,o=t(window.__NEXUS_CONFIG?.GREETING||n.GREETING),r=document.createElement("div");r.id="ai-widget-root",r.innerHTML=`\n <div class="ai-panel" id="aiPanel">\n <div class="ai-header">\n <div class="ai-header-title">\n <span class="status-dot"></span>\n ${a}\n </div>\n <div class="ai-header-actions">\n <button class="ai-theme-toggle" id="aiThemeToggle" aria-label="Toggle theme">\n <i class="fas fa-moon"></i>\n </button>\n <button class="ai-close-btn" id="aiCloseBtn" aria-label="Close chat">\n <i class="fas fa-xmark"></i>\n </button>\n </div>\n </div>\n <div class="ai-messages" id="aiMessages">\n <div class="msg bot">\n ${o}\n </div>\n </div>\n <div class="ai-input-area" id="aiInputArea">\n <div id="aiPreviewContainer"></div>\n <div class="ai-input-container">\n <button class="icon-btn" id="aiAttachBtn" aria-label="Attach image or file">\n <i class="fas fa-paperclip"></i>\n </button>\n <textarea \n id="aiInput" \n placeholder="Message..." \n rows="1"\n autocomplete="off"\n spellcheck="true"\n ></textarea>\n <button class="icon-btn send-btn" id="aiSendBtn">\n <i class="fas fa-arrow-up"></i>\n </button>\n </div>\n <div class="ai-input-footer">\n <span class="ai-char-count" id="aiCharCount">0</span>\n </div>\n </div>\n </div>\n <button class="ai-fab" id="aiFab" aria-label="Open AI Chat">\n <span class="bot-icon">\n <i class="fas fa-robot" style="color: #ffffff !important; font-size: 22px;"></i>\n </span>\n </button>\n `,document.body.appendChild(r);const i=e("aiInput");return i&&i.addEventListener("input",()=>{!function(n){n.style.height="auto",n.style.height=Math.min(n.scrollHeight,150)+"px"}(i),function(n){const t=e("aiCharCount");if(t){const e=n.value.length;t.textContent=e>0?`${e}`:"0",t.style.color=e>500?"#ef4444":"#a0a0a0"}}(i)}),r}function o(n,t){if(!n)return!1;const a=void 0!==t?t:!n.classList.contains("open");return n.classList.toggle("open",a),a&&setTimeout(()=>{const n=e("aiInput");n&&n.focus()},100),a}function r(n,e,a="bot",o=null){if(!n)return null;const r=document.createElement("div");if(r.className=`msg ${a}`,"bot"===a){const n=t(e);r.innerHTML=n,"undefined"!=typeof hljs&&r.querySelectorAll("pre code").forEach(n=>{hljs.highlightElement(n)})}else{let n=e||"";o&&(n+=`<br><img src="${o}" class="image-preview" alt="attached">`),r.innerHTML=n}return n.appendChild(r),n.scrollTo({top:n.scrollHeight,behavior:"smooth"}),r}let i=null;function s(n,e){n&&(i&&(i.remove(),i=null),e&&(i=document.createElement("div"),i.className="typing-indicator",i.innerHTML="<span></span><span></span><span></span>",n.appendChild(i),n.scrollTo({top:n.scrollHeight,behavior:"smooth"})))}function c(n,e){if(!n)return;if(n.innerHTML="",!e)return;const t=e.type?.startsWith("image/")||e.dataUrl?.startsWith("data:image"),a="application/pdf"===e.type||e.name?.endsWith(".pdf"),o=e.type?.includes("document")||e.name?.endsWith(".docx")||e.name?.endsWith(".txt"),r=document.createElement("div");r.className="image-preview-pill";let i="fa-file",s="rgba(255,255,255,0.04)";var c;t?(i="fa-image",s="rgba(255,255,255,0.04)"):a?(i="fa-file-pdf",s="rgba(255,255,255,0.04)"):o&&(i="fa-file-lines",s="rgba(255,255,255,0.04)"),r.innerHTML=`\n ${t?`<img src="${e.dataUrl}" alt="preview" class="preview-thumb">`:`<i class="fas ${i}" style="font-size: 20px; color: #888;"></i>`}\n <span class="file-name">${e.name||"file"}</span>\n <span class="file-size">${c=e.size||0,c<1024?c+" B":c<1048576?(c/1024).toFixed(1)+" KB":(c/1048576).toFixed(1)+" MB"}</span>\n <button class="remove-file" id="aiRemoveFile" aria-label="Remove file">\n <i class="fas fa-times"></i>\n </button>\n `,r.style.background=s,n.appendChild(r),document.getElementById("aiRemoveFile")?.addEventListener("click",()=>{n.innerHTML="";const e=new CustomEvent("file-removed");document.dispatchEvent(e)})}const l={isOpen:!1,isProcessing:!1,attachedFile:null,conversationHistory:[]};function d(n){Object.assign(l,n)}const p="NexusAICache",u="pageData";let m=null;function g(){return new Promise((n,e)=>{if(m)return void n(m);const t=indexedDB.open(p,1);t.onupgradeneeded=n=>{const e=n.target.result;if(!e.objectStoreNames.contains(u)){const n=e.createObjectStore(u,{keyPath:"id"});n.createIndex("url","url",{unique:!1}),n.createIndex("timestamp","timestamp",{unique:!1}),console.log("๐ฆ IndexedDB store created")}},t.onsuccess=e=>{m=e.target.result,n(m)},t.onerror=n=>{e(n.target.error)}})}async function b(n){try{const e=(await g()).transaction(u,"readwrite").objectStore(u),t=window.location.href,a={id:t,url:t,data:n,timestamp:Date.now()};return new Promise((n,t)=>{const o=e.put(a);o.onsuccess=()=>{console.log("๐พ Data cached in IndexedDB"),n()},o.onerror=n=>{t(n.target.error)}})}catch(n){console.warn("IndexedDB write failed:",n)}}let h=null,f=null;function x(n){return new Promise((e,t)=>{const a=document.createElement("script");a.src=n,a.async=!0,a.onload=e,a.onerror=t,document.head.appendChild(a)})}function w(){console.log("๐ท๏ธ Advanced scraping started...");const n={title:document.title,url:window.location.href,timestamp:(new Date).toISOString(),pageType:y(),readability:null,structuredData:[],markdown:"",summary:"",metadata:{}};try{if(h){const e=document.cloneNode(!0),t=new h(e).parse();if(t){if(n.readability={title:t.title,byline:t.byline,excerpt:t.excerpt,content:t.content,textContent:t.textContent},f){const e=new f({headingStyle:"atx",codeBlockStyle:"fenced",emDelimiter:"*",bulletListMarker:"-"});e.addRule("strikethrough",{filter:["del","s","strike"],replacement:function(n){return"~~"+n+"~~"}}),n.markdown=e.turndown(t.content)}n.summary=t.excerpt||t.textContent.substring(0,300)}}}catch(e){console.warn("Readability failed, falling back to basic scraper:",e),n.readability=null}try{document.querySelectorAll('script[type="application/ld+json"]').forEach(e=>{try{const t=JSON.parse(e.textContent);n.structuredData.push(t)}catch(n){}})}catch(n){console.warn("JSON-LD extraction failed:",n)}if(n.metadata=function(){const n={},e=document.querySelector('meta[name="description"]');e&&(n.description=e.content);const t=document.querySelector('meta[name="keywords"]');t&&(n.keywords=t.content),["title","description","image","url","type","site_name"].forEach(e=>{const t=document.querySelector(`meta[property="og:${e}"]`);t&&(n[`og_${e}`]=t.content)}),["card","title","description","image"].forEach(e=>{const t=document.querySelector(`meta[name="twitter:${e}"]`);t&&(n[`twitter_${e}`]=t.content)});const a=document.querySelector('link[rel="canonical"]');return a&&(n.canonical=a.href),n}(),!n.readability){console.log("โ ๏ธ Using fallback scraper");const e=function(){const n=document.body.cloneNode(!0);["nav","header","footer","aside",".sidebar",".navigation",".menu",".ads",".cookie-banner",".popup",".overlay","script","style","noscript","iframe"].forEach(e=>{n.querySelectorAll(e).forEach(n=>n.remove())});let e=n.textContent||"";e=e.replace(/\s+/g," ").trim();const t=e.substring(0,500)+"...";return{content:e,summary:t}}();n.markdown=e.content,n.summary=e.summary}return n.aiContext=v(n),console.log("โ
Advanced scraping complete"),n}function y(){const n=window.location.href,e=document.title.toLowerCase(),t=document.querySelector('meta[property="og:type"]');if(t){const n=t.content;if(n.includes("article"))return"article";if(n.includes("product"))return"product";if(n.includes("website"))return"website"}return n.includes("/blog/")||n.includes("/post/")||e.includes("blog")?"blog":n.includes("/product/")||n.includes("/shop/")?"product":n.includes("/portfolio/")||n.includes("/project/")?"portfolio":n===window.location.origin+"/"||n===window.location.origin?"homepage":"general"}function v(n){let e="";if(e+=`# ๐ ${n.title}\n\n`,e+=`**URL:** ${n.url}\n`,e+=`**Type:** ${n.pageType}\n`,e+=`**Scraped:** ${new Date(n.timestamp).toLocaleString()}\n\n`,Object.keys(n.metadata).length>0){e+="## ๐ Metadata\n";for(const[t,a]of Object.entries(n.metadata))a&&a.length>0&&(e+=`- **${t}:** ${a}\n`);e+="\n"}if(n.structuredData&&n.structuredData.length>0&&(e+="## ๐ Structured Data (Schema.org)\n",n.structuredData.forEach((n,t)=>{e+=`\n### Schema ${t+1}\n`,e+=`\`\`\`json\n${JSON.stringify(n,null,2)}\n\`\`\`\n`}),e+="\n"),n.markdown){e+="## ๐ Main Content\n\n";const t=4e3,a=n.markdown.length>t?n.markdown.substring(0,t)+"... (truncated)":n.markdown;e+=a,e+="\n\n"}return n.summary&&(e+="## ๐ Summary\n\n",e+=n.summary,e+="\n\n"),e+="---\n",e+="*๐ค AI Instructions: Use this context to answer user questions about this page. Be precise, reference specific details from the content, and maintain a friendly professional tone.*\n",e}async function E(e,t,a=null){if(l.isProcessing)return;d({isProcessing:!0});const o=document.getElementById("aiSendBtn"),i=document.getElementById("aiInput");o&&(o.disabled=!0),i&&(i.disabled=!0),s(e,!0);const c=[];if(t&&c.push({type:"text",text:t}),a&&c.push({type:"image_url",image_url:{url:a}}),l.conversationHistory.push({role:"user",content:t||"(image)"}),l.conversationHistory.length>11){const n=l.conversationHistory[0],e=l.conversationHistory.slice(-10);l.conversationHistory=[n,...e]}try{const t=await async function(){let n=await async function(){try{const n=(await g()).transaction(u,"readonly").objectStore(u),e=window.location.href;return new Promise((t,a)=>{const o=n.get(e);o.onsuccess=n=>{const a=n.target.result;if(!a)return void t(null);const o=Date.now();if(o-a.timestamp>864e5)return async function(n){try{(await g()).transaction(u,"readwrite").objectStore(u).delete(n)}catch(n){console.warn("IndexedDB delete failed:",n)}}(e),void t(null);console.log(`๐ฆ Cache hit! Age: ${Math.floor((o-a.timestamp)/6e4)} minutes`),t(a.data)},o.onerror=n=>{a(n.target.error)}})}catch(n){return console.warn("IndexedDB read failed:",n),null}}();if(n)return console.log("๐ฆ Using cached page data"),n;console.log("๐ Scraping page data...");const e=v(w());return await b(e),console.log("๐พ Page data cached"),e}(),a=window.__NEXUS_CONFIG||n,o=[{role:"system",content:`${a.SYSTEM_PROMPT}\n\n## Current Page Context\n${t}`}];for(let n=1;n<l.conversationHistory.length-1;n++)o.push({role:l.conversationHistory[n].role,content:l.conversationHistory[n].content});o.push({role:"user",content:c});const i=await fetch(a.API_URL,{method:"POST",headers:{"Content-Type":"application/json",Origin:window.location.origin},body:JSON.stringify({nexusKey:a.API_KEY,messages:o,model:a.MODEL})});if(!i.ok){const n=await i.json().catch(()=>({}));throw new Error(`Proxy error ${i.status}: ${n.error||i.statusText}`)}const d=await i.json(),p=d.choices?.[0]?.message?.content||"Sorry, I didn't understand that.";l.conversationHistory.push({role:"assistant",content:p}),s(e,!1),r(e,p,"bot")}catch(n){console.error("Nexus Proxy Error:",n),s(e,!1),r(e,`โ ๏ธ Oops! ${n.message||"Something went wrong."} Please try again.`,"bot")}finally{d({isProcessing:!1}),o&&(o.disabled=!1),i&&(i.disabled=!1,i.value="",i.focus())}}function k(){const n=document.getElementById("ai-widget-root");if(!n)return;const e="dark"===(n.dataset.theme||"dark")?"light":"dark";n.dataset.theme=e,localStorage.setItem("nexus-theme",e),window.__NEXUS_CONFIG&&(window.__NEXUS_CONFIG.THEME=e);const t=document.getElementById("aiThemeToggle");t&&(t.innerHTML="dark"===e?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',t.title="dark"===e?"Switch to Light":"Switch to Dark"),console.log(`๐ Theme switched to: ${e}`)}async function S(){const t=function(){const e=window.NexusConfig||{},t=function(){const n=window.NexusConfig||{};if(n.theme)return n.theme;const e=localStorage.getItem("nexus-theme");if(e)return e;const t=document.body;if(t.classList.contains("dark")||t.classList.contains("dark-theme")||t.classList.contains("dark-mode"))return"dark";if(t.classList.contains("light")||t.classList.contains("light-theme")||t.classList.contains("light-mode"))return"light";const a=document.documentElement.getAttribute("data-theme");if("dark"===a||"light"===a)return a;const o=document.querySelector('meta[name="theme-color"]');if(o){const n=o.content;if(n.startsWith("#")&&parseInt(n.slice(1,3),16)<100)return"dark"}return window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches,"dark"}();return{API_URL:n.API_URL,API_KEY:e.apiKey||n.API_KEY,MODEL:e.model||n.MODEL,BOT_NAME:e.botName||n.BOT_NAME,GREETING:e.greeting||n.GREETING,SYSTEM_PROMPT:e.systemPrompt||n.SYSTEM_PROMPT,THEME:t,COLORS:n.COLORS}}();window.__NEXUS_CONFIG=t,await async function(){const n=document.createElement("link");n.href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css",n.rel="stylesheet",document.head.appendChild(n);const e=document.createElement("link");e.href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap",e.rel="stylesheet",document.head.appendChild(e);const t=document.createElement("script");t.src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.0/marked.min.js",t.async=!0,document.head.appendChild(t);const a=document.createElement("link");a.href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css",a.rel="stylesheet",document.head.appendChild(a);const o=document.createElement("script");o.src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js",o.async=!0,document.head.appendChild(o),await async function(){await Promise.all([x("https://cdnjs.cloudflare.com/ajax/libs/readability/0.4.4/Readability.min.js"),x("https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js")]),h=window.Readability,f=window.TurndownService,console.log("๐ Scraper libraries loaded")}()}(),function(){const n=document.createElement("style");n.id="ai-chat-widget-styles",n.textContent="\n /* ===== MONOCHROME THEME VARIABLES ===== */\n #ai-widget-root {\n /* Dark theme defaults */\n --bg-panel: rgba(10, 10, 10, 0.85);\n --bg-input: rgba(255, 255, 255, 0.03);\n --border-color: rgba(255, 255, 255, 0.08);\n --text-primary: #f0f0f0;\n --text-secondary: #999;\n --user-bubble-bg: #2a2a2a;\n --bot-bubble-bg: rgba(255, 255, 255, 0.04);\n --shadow-color: rgba(0, 0, 0, 0.6);\n --fab-bg: rgba(255, 255, 255, 0.05);\n --accent: #aaa;\n --send-bg: #f0f0f0;\n --send-color: #111;\n }\n\n /* ===== LIGHT THEME ===== */\n #ai-widget-root[data-theme=\"light\"] {\n --bg-panel: rgba(245, 245, 245, 0.92);\n --bg-input: rgba(0, 0, 0, 0.02);\n --border-color: rgba(0, 0, 0, 0.08);\n --text-primary: #111;\n --text-secondary: #555;\n --user-bubble-bg: #e0e0e0;\n --bot-bubble-bg: rgba(255, 255, 255, 0.5);\n --shadow-color: rgba(0, 0, 0, 0.05);\n --fab-bg: rgba(255, 255, 255, 0.6);\n --accent: #888;\n --send-bg: #222;\n --send-color: #fff;\n }\n\n /* ===== ANIMATIONS ===== */\n @keyframes slideUpFade {\n 0% { opacity: 0; transform: translateY(30px) scale(0.95); }\n 100% { opacity: 1; transform: translateY(0) scale(1); }\n }\n @keyframes pulseDot {\n 0%, 100% { transform: scale(0.8); opacity: 0.5; }\n 50% { transform: scale(1.3); opacity: 1; }\n }\n @keyframes typing {\n 0%, 80%, 100% { opacity: 0.3; transform: translateY(2px); }\n 40% { opacity: 1; transform: translateY(-2px); }\n }\n @keyframes previewPop {\n 0% { opacity: 0; transform: scale(0.8) translateY(10px); }\n 100% { opacity: 1; transform: scale(1) translateY(0); }\n }\n @keyframes fabPulse {\n 0% { box-shadow: 0 0 0 0 rgba(255,255,255,0.1); }\n 70% { box-shadow: 0 0 0 20px rgba(255,255,255,0); }\n 100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); }\n }\n\n /* ===== ROOT ===== */\n #ai-widget-root {\n all: initial;\n font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 999999;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n pointer-events: none;\n color: var(--text-primary);\n }\n #ai-widget-root * { box-sizing: border-box; pointer-events: auto; }\n\n /* ===== ICONS FIX ===== */\n #ai-widget-root .fas,\n #ai-widget-root .far,\n #ai-widget-root .fab {\n font-family: 'Font Awesome 6 Free', 'Font Awesome 6 Brands' !important;\n font-weight: 900 !important;\n font-style: normal;\n font-variant: normal;\n text-rendering: auto;\n -webkit-font-smoothing: antialiased;\n }\n #ai-widget-root .far { font-weight: 400 !important; }\n #ai-widget-root .fab {\n font-family: 'Font Awesome 6 Brands' !important;\n font-weight: 400 !important;\n }\n\n /* ===== FLOATING ACTION BUTTON ===== */\n .ai-fab {\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: var(--fab-bg);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--border-color);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-primary);\n transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);\n user-select: none;\n box-shadow: 0 8px 32px var(--shadow-color);\n position: relative;\n z-index: 10;\n will-change: transform, opacity;\n animation: fabPulse 3s infinite;\n }\n .ai-fab:hover {\n transform: scale(1.08) rotate(-5deg);\n box-shadow: 0 12px 40px var(--shadow-color);\n border-color: var(--accent);\n }\n .ai-fab .bot-icon i {\n color: var(--text-primary) !important;\n font-size: 26px !important;\n filter: drop-shadow(0 2px 8px rgba(0,0,0,0.2));\n }\n .ai-panel.open ~ .ai-fab {\n opacity: 0;\n transform: scale(0.8);\n pointer-events: none;\n animation: none;\n }\n\n /* ===== CHAT PANEL ===== */\n .ai-panel {\n position: absolute;\n bottom: 0;\n right: 0;\n width: 420px;\n max-width: calc(100vw - 48px);\n height: 620px;\n max-height: calc(100vh - 48px);\n background: var(--bg-panel);\n backdrop-filter: blur(32px);\n -webkit-backdrop-filter: blur(32px);\n border-radius: 28px;\n box-shadow: 0 32px 80px var(--shadow-color), 0 0 0 1px var(--border-color);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n opacity: 0;\n visibility: hidden;\n transform: translateY(20px) scale(0.96);\n transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n border: 1px solid var(--border-color);\n }\n .ai-panel.open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0) scale(1);\n }\n\n /* ===== HEADER ===== */\n .ai-header {\n padding: 20px 24px;\n border-bottom: 1px solid var(--border-color);\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-shrink: 0;\n }\n .ai-header-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .ai-header-title {\n display: flex;\n align-items: center;\n gap: 12px;\n color: var(--text-primary);\n font-weight: 600;\n font-size: 16px;\n letter-spacing: -0.3px;\n }\n .status-dot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 12px rgba(128,128,128,0.4);\n animation: pulseDot 2s infinite;\n flex-shrink: 0;\n }\n .ai-theme-toggle,\n .ai-close-btn {\n background: transparent;\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n width: 32px;\n height: 32px;\n border-radius: 50%;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ai-theme-toggle:hover,\n .ai-close-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n transform: rotate(90deg);\n }\n .ai-theme-toggle:hover {\n transform: rotate(180deg) !important;\n }\n\n /* ===== MESSAGES ===== */\n .ai-messages {\n flex: 1;\n padding: 24px 24px 16px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 20px;\n scroll-behavior: smooth;\n }\n .ai-messages::-webkit-scrollbar { width: 4px; }\n .ai-messages::-webkit-scrollbar-track { background: transparent; }\n .ai-messages::-webkit-scrollbar-thumb {\n background: var(--border-color);\n border-radius: 10px;\n }\n\n /* ===== MESSAGE BUBBLES ===== */\n .msg {\n max-width: 85%;\n font-size: 14.5px;\n line-height: 1.7;\n word-break: break-word;\n animation: slideUpFade 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n color: var(--text-primary);\n padding: 12px 18px;\n border-radius: 18px;\n position: relative;\n box-shadow: 0 2px 8px var(--shadow-color);\n }\n /* User message โ clearly distinguishable */\n .msg.user {\n align-self: flex-end;\n background: var(--user-bubble-bg);\n border: 1px solid var(--border-color);\n border-bottom-right-radius: 4px;\n }\n /* Bot message โ different shade */\n .msg.bot {\n align-self: flex-start;\n background: var(--bot-bubble-bg);\n border: 1px solid var(--border-color);\n border-bottom-left-radius: 4px;\n max-width: 95%;\n }\n\n /* Small tail effects for clarity */\n .msg.user::after {\n content: '';\n position: absolute;\n bottom: 0;\n right: -6px;\n width: 12px;\n height: 12px;\n background: var(--user-bubble-bg);\n border-radius: 0 0 12px 0;\n clip-path: polygon(0 0, 100% 0, 100% 100%);\n }\n .msg.bot::before {\n content: '';\n position: absolute;\n bottom: 0;\n left: -6px;\n width: 12px;\n height: 12px;\n background: var(--bot-bubble-bg);\n border-radius: 0 0 0 12px;\n clip-path: polygon(0 0, 100% 100%, 100% 0);\n }\n\n /* ===== MARKDOWN RENDER ===== */\n .msg.bot p { margin: 0 0 12px 0; }\n .msg.bot p:last-child { margin-bottom: 0; }\n .msg.bot code {\n background: var(--border-color);\n padding: 2px 8px;\n border-radius: 6px;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n border: 1px solid var(--border-color);\n }\n .msg.bot pre {\n background: var(--border-color);\n padding: 16px;\n border-radius: 14px;\n overflow-x: auto;\n margin: 12px 0;\n border: 1px solid var(--border-color);\n }\n .msg.bot pre code { background: transparent; padding: 0; border: none; }\n .msg.bot ul, .msg.bot ol { margin: 8px 0 12px 20px; padding-left: 0; }\n .msg.bot li { margin-bottom: 6px; }\n .msg.bot a { color: var(--accent); text-decoration: none; border-bottom: 1px dotted var(--accent); }\n .msg .image-preview {\n max-width: 200px;\n border-radius: 14px;\n margin-top: 8px;\n border: 1px solid var(--border-color);\n }\n\n /* ===== INPUT AREA ===== */\n .ai-input-area {\n padding: 12px 20px 20px;\n background: transparent;\n flex-shrink: 0;\n border-top: 1px solid var(--border-color);\n }\n .ai-input-container {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n background: var(--bg-input);\n border: 1px solid var(--border-color);\n border-radius: 20px;\n padding: 4px 4px 4px 16px;\n transition: all 0.3s ease;\n min-height: 48px;\n box-shadow: inset 0 2px 6px rgba(0,0,0,0.02);\n }\n .ai-input-container:focus-within {\n border-color: var(--accent);\n background: var(--bg-input);\n box-shadow: 0 0 0 4px rgba(128,128,128,0.08);\n }\n .ai-input-container textarea {\n flex: 1;\n background: transparent;\n border: none;\n color: var(--text-primary);\n font-size: 14px;\n outline: none;\n font-family: inherit;\n padding: 10px 0;\n resize: none;\n max-height: 150px;\n line-height: 1.6;\n min-height: 28px;\n scrollbar-width: none;\n }\n .ai-input-container textarea::placeholder {\n color: var(--text-secondary);\n font-weight: 400;\n }\n\n .icon-btn {\n background: transparent;\n border: none;\n color: var(--text-secondary);\n width: 38px;\n height: 38px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n align-self: flex-end;\n margin-bottom: 2px;\n }\n .icon-btn:hover {\n color: var(--text-primary);\n background: var(--border-color);\n transform: scale(1.05);\n }\n .icon-btn:active { transform: scale(0.92); }\n\n .send-btn {\n background: var(--send-bg);\n color: var(--send-color);\n width: 38px;\n height: 38px;\n align-self: flex-end;\n margin-bottom: 2px;\n box-shadow: 0 4px 12px var(--shadow-color);\n transition: all 0.3s ease;\n border: none;\n }\n .send-btn:hover {\n transform: scale(1.08) rotate(-3deg);\n box-shadow: 0 6px 20px var(--shadow-color);\n }\n .send-btn:active { transform: scale(0.92); }\n .send-btn:disabled {\n background: var(--border-color);\n color: var(--text-secondary);\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n }\n\n .ai-input-footer {\n display: flex;\n justify-content: flex-end;\n padding: 4px 6px 0;\n min-height: 20px;\n }\n .ai-char-count {\n font-size: 11px;\n color: var(--text-secondary);\n font-weight: 500;\n transition: color 0.2s;\n opacity: 0.5;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n }\n\n /* ===== PREVIEW PILL ===== */\n #aiPreviewContainer {\n margin-bottom: 8px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n .image-preview-pill {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n background: var(--bg-input);\n border: 1px solid var(--border-color);\n border-radius: 14px;\n padding: 6px 12px 6px 6px;\n animation: previewPop 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n box-shadow: 0 4px 16px var(--shadow-color);\n transition: all 0.2s;\n max-width: 100%;\n }\n .image-preview-pill:hover {\n transform: translateY(-2px);\n box-shadow: 0 8px 24px var(--shadow-color);\n }\n .image-preview-pill .preview-thumb {\n width: 32px;\n height: 32px;\n border-radius: 10px;\n object-fit: cover;\n border: 1px solid var(--border-color);\n }\n .image-preview-pill .file-name {\n font-size: 12px;\n color: var(--text-primary);\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n }\n .image-preview-pill .file-size {\n font-size: 10px;\n color: var(--text-secondary);\n opacity: 0.6;\n }\n .image-preview-pill .remove-file {\n background: var(--border-color);\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: 50%;\n transition: all 0.2s;\n margin-left: 2px;\n width: 26px;\n height: 26px;\n }\n .image-preview-pill .remove-file:hover {\n color: #ef4444;\n background: rgba(239,68,68,0.15);\n transform: scale(1.1);\n }\n\n /* ===== TYPING INDICATOR ===== */\n .typing-indicator {\n display: flex;\n gap: 6px;\n padding: 12px 0;\n align-self: flex-start;\n background: var(--bot-bubble-bg);\n padding: 10px 16px;\n border-radius: 18px;\n border: 1px solid var(--border-color);\n }\n .typing-indicator span {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--text-secondary);\n animation: typing 1.4s infinite;\n box-shadow: 0 2px 6px rgba(0,0,0,0.02);\n }\n .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }\n .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }\n\n /* ===== RESPONSIVE ===== */\n @media (max-width: 480px) {\n .ai-panel {\n width: calc(100vw - 32px);\n right: 16px;\n height: 80vh;\n max-height: calc(100vh - 48px);\n border-radius: 20px;\n }\n .ai-fab {\n width: 52px;\n height: 52px;\n }\n .ai-fab .bot-icon { font-size: 22px; }\n .ai-input-area {\n padding: 8px 14px 14px;\n }\n .ai-input-container {\n border-radius: 16px;\n padding: 2px 2px 2px 12px;\n min-height: 42px;\n }\n .ai-input-container textarea { font-size: 13px; padding: 8px 0; }\n .icon-btn { width: 32px; height: 32px; }\n .send-btn { width: 32px; height: 32px; }\n .image-preview-pill .file-name { max-width: 100px; }\n }\n ",document.head.appendChild(n)}(),a();const i=document.getElementById("ai-widget-root");i&&(i.dataset.theme=t.THEME||"dark");const s=document.getElementById("aiThemeToggle");if(s){const n="dark"===t.THEME;s.innerHTML=n?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',s.title=n?"Switch to Light":"Switch to Dark",s.addEventListener("click",k)}const m=e("aiPanel"),y=e("aiMessages"),S=e("aiFab"),I=e("aiCloseBtn"),T=e("aiSendBtn"),L=e("aiInput"),P=e("aiAttachBtn"),O=e("aiPreviewContainer");!function(n){let e=window.location.href;new MutationObserver(()=>{window.location.href!==e&&(e=window.location.href,console.log("๐ Page navigation detected"),n())}).observe(document,{subtree:!0,childList:!0});const t=history.pushState;history.pushState=function(){t.apply(this,arguments),setTimeout(()=>{window.location.href!==e&&(e=window.location.href,n())},500)}}(()=>{console.log("๐ Page changed, refreshing context..."),async function(){console.log("๐ Forcing page context refresh...");const n=v(w());await b(n),console.log("โ
Page context refreshed")}(),r(y,"๐ Page context updated!","bot")});const M=document.createElement("input");M.type="file",M.accept="image/*",M.style.display="none",M.id="aiFileInput",document.body.appendChild(M),P?.addEventListener("click",()=>M.click()),M.addEventListener("change",n=>{const e=n.target.files?.[0];if(!e)return;if(!e.type.startsWith("image/"))return void alert("Please select an image file.");const t=new FileReader;t.onload=n=>{const t={dataUrl:n.target.result,name:e.name,type:e.type};d({attachedFile:t}),c(O,t);const a=document.getElementById("aiRemoveFile");a&&a.addEventListener("click",()=>{d({attachedFile:null}),c(O,null),L?.focus()}),L?.focus()},t.readAsDataURL(e),n.target.value=""}),S?.addEventListener("click",()=>{d({isOpen:o(m,!0)})}),I?.addEventListener("click",()=>{d({isOpen:o(m,!1)})}),document.addEventListener("click",n=>{const e=document.getElementById("ai-widget-root");e&&l.isOpen&&!e.contains(n.target)&&d({isOpen:o(m,!1)})});const C=()=>{const n=L?.value.trim();if(!n&&!l.attachedFile)return;if(l.isProcessing)return;const e=l.attachedFile?.dataUrl||null;r(y,n,"user",e),L.value="",L.style.height="auto",c(O,null);const t=l.attachedFile?.dataUrl||null;d({attachedFile:null}),E(y,n,t)};T?.addEventListener("click",C),L?.addEventListener("keydown",n=>{"Enter"!==n.key||n.shiftKey||(n.preventDefault(),C())});const N=await async function(){try{const n=(await g()).transaction(u,"readonly").objectStore(u);return new Promise(e=>{const t=n.count();t.onsuccess=()=>{e({totalEntries:t.result,database:p,version:1})},t.onerror=()=>e(null)})}catch{return null}}();console.log("๐ Cache Stats:",N||"No cache yet"),console.log("โจ Nexus AI v2.0 initialized with theme:",t.THEME)}l.conversationHistory=[{role:"system",content:n.SYSTEM_PROMPT}],"loading"===document.readyState?document.addEventListener("DOMContentLoaded",S):S()})();
|