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 CHANGED
@@ -1,265 +1,185 @@
1
- # Nexus Web Assistant ๐Ÿš€
1
+ # Nexus Web Assistant
2
2
 
3
- **Your Intelligent AI-Powered Web Companion**
3
+ [![Version](https://img.shields.io/npm/v/nexus-web-assistant)](https://www.npmjs.com/package/nexus-web-assistant)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![CDN](https://img.shields.io/badge/CDN-jsDelivr-blue)](https://cdn.jsdelivr.net/npm/nexus-web-assistant@1.2.0/dist/nexus-assistant.min.js)
4
6
 
5
- [![GitHub stars](https://img.shields.io/github/stars/thekaifansari01/NexusWebAssistant?style=for-the-badge&color=gold)](https://github.com/thekaifansari01/NexusWebAssistant/stargazers)
6
- [![GitHub forks](https://img.shields.io/github/forks/thekaifansari01/NexusWebAssistant?style=for-the-badge&color=blue)](https://github.com/thekaifansari01/NexusWebAssistant/network)
7
- [![GitHub issues](https://img.shields.io/github/issues/thekaifansari01/NexusWebAssistant?style=for-the-badge&color=red)](https://github.com/thekaifansari01/NexusWebAssistant/issues)
8
- [![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge)](LICENSE)
9
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=for-the-badge)](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
- ## ๐Ÿ“– Overview
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
- > โœจ **Live Demo:** [https://nexus-web-assistant.vercel.app](https://nexus-web-assistant.vercel.app)
14
+ โžก๏ธ [Live Demo](https://your-demo-link.com) ยท [GitHub](https://github.com/thekaifansari01/nexusWebAssistant/)
18
15
 
19
16
  ---
20
17
 
21
- ## โœจ Key Features
22
-
23
- ### ๐Ÿค– **Intelligent AI Chat**
24
- - **Powered by Groq API** - Lightning-fast inference with LLaMA models
25
- - **Context-Aware Responses** - Understands and references page content
26
- - **Multimodal Support** - Text + Image analysis capabilities
27
- - **Markdown Rendering** - Beautifully formatted responses with code highlighting
28
-
29
- ### ๐Ÿ“„ **Advanced Web Scraping**
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
- ## ๐Ÿš€ Quick Start
30
+ ## Installation
52
31
 
53
- ### **Option 1: Direct Integration (Recommended)**
32
+ ### CDN (recommended)
54
33
 
55
- Add this single script tag to your website:
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
- ### **Option 2: Self-Hosted**
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
- # Navigate to project
68
- cd NexusWebAssistant
42
+ ---
69
43
 
70
- # Install dependencies
71
- npm install
44
+ ## Quick Start
72
45
 
73
- # Build for production
74
- npm run build
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
- # Include in your project
77
- <script defer src="/path/to/dist/nexus-assistant.min.js"></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
- ### **Option 3: Development**
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
- ## โš™๏ธ Configuration
66
+ ## Configuration Options
96
67
 
97
- Customize the assistant by modifying `config.js`:
68
+ All settings are optional except `apiKey`. Set them in `window.NexusConfig` before the widget script loads.
98
69
 
99
- ```javascript
100
- export const CONFIG = {
101
- // API Configuration
102
- API_URL: 'https://api.groq.com/openai/v1/chat/completions',
103
- API_KEY: 'your-api-key-here', // ๐Ÿ”‘ Get from console.groq.com
104
- MODEL: 'meta-llama/llama-4-scout-17b-16e-instruct',
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
- // Branding
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
- // System Prompt
111
- SYSTEM_PROMPT: `...`, // Customize AI behavior
81
+ ## Customisation
112
82
 
113
- // UI Colors
114
- COLORS: {
115
- bg: '#0a0a0a',
116
- panelBg: 'rgba(12, 12, 12, 0.85)',
117
- // ... customize theme
118
- }
119
- };
120
- ```
83
+ ### Theming
121
84
 
122
- ### **Required Configuration:**
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
- 1. **Get your API Key:** [Groq Console](https://console.groq.com)
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
- ## ๐Ÿ“ Project Structure
95
+ ### CSS Variables
131
96
 
132
- ```
133
- NexusWebAssistant/
134
- โ”œโ”€โ”€ src/
135
- โ”‚ โ”œโ”€โ”€ api.js # AI API integration
136
- โ”‚ โ”œโ”€โ”€ cache.js # IndexedDB caching engine
137
- โ”‚ โ”œโ”€โ”€ config.js # Configuration & settings
138
- โ”‚ โ”œโ”€โ”€ dom.js # DOM manipulation & rendering
139
- โ”‚ โ”œโ”€โ”€ index.js # Main entry point
140
- โ”‚ โ”œโ”€โ”€ scraper.js # Advanced web scraping engine
141
- โ”‚ โ”œโ”€โ”€ state.js # Application state management
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
- ## ๐Ÿ› ๏ธ Technology Stack
111
+ ## How It Works
153
112
 
154
- | Technology | Purpose |
155
- |------------|---------|
156
- | **Groq API** | AI inference engine |
157
- | **IndexedDB** | Client-side caching |
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
- ## ๐Ÿ”Œ Features in Detail
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
- ### ๐Ÿ’ฌ **Conversation History**
182
- Maintains context across multiple messages for natural conversations.
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
- ## ๐Ÿค Contributing
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
- ### ๐Ÿ’ก **Feature Requests**
204
- [Submit a Feature Request](https://github.com/thekaifansari01/NexusWebAssistant/issues/new?labels=enhancement)
133
+ ### Build from Source
205
134
 
206
- ### ๐Ÿ”ง **Development**
135
+ Clone the repository and install dependencies:
207
136
 
208
137
  ```bash
209
- # Fork the repository
210
- # Clone your fork
211
- git clone https://github.com/YOUR_USERNAME/NexusWebAssistant.git
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
- ### ๐Ÿ“ **Guidelines**
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
- ## ๐Ÿ“„ License
149
+ ### File Structure
232
150
 
233
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
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
- ## ๐Ÿ† Acknowledgments
162
+ ## Contributing
238
163
 
239
- - **Groq** for their incredible AI inference engine
240
- - **Mozilla** for Readability library
241
- - **Open Source Community** for all the amazing tools
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
- ## ๐Ÿ“ž Contact & Support
171
+ ## License
246
172
 
247
- - **Author:** [Kaif Ansari](https://github.com/thekaifansari01)
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
- ## โญ Show Your Support
177
+ ## Acknowledgements
254
178
 
255
- If you find this project useful, please consider:
256
- - โญ Starring the repository
257
- - ๐Ÿด Forking the project
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
- **Built with โค๏ธ by [Kaif Ansari](https://github.com/thekaifansari01)**
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()})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-web-assistant",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "AI-powered chatbot widget for any website",
5
5
  "main": "dist/nexus-assistant.min.js",
6
6
  "files": [