nexus-web-assistant 2.1.0 → 2.3.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 +35 -18
- package/dist/nexus-assistant.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,19 @@
|
|
|
11
11
|
- **Beautiful dark/light themes** – auto‑detects your site’s theme and adapts.
|
|
12
12
|
- **CDN‑ready** – drop in a single `<script>` and you're live.
|
|
13
13
|
|
|
14
|
-
➡️ [Live Demo](https://trynexusweb.vercel.app/demo) · [GitHub](https://github.com/thekaifansari01/nexusWebAssistant/)
|
|
14
|
+
➡️ [Live Demo](https://trynexusweb.vercel.app/demo) · [Dashboard](https://trynexusweb.vercel.app/dashboard) · [GitHub](https://github.com/thekaifansari01/nexusWebAssistant/)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🔐 Security First – How It Works
|
|
19
|
+
|
|
20
|
+
To keep your Groq API key **completely secure**:
|
|
21
|
+
|
|
22
|
+
1. **You store your Groq API key** in the Nexus Dashboard (encrypted at rest).
|
|
23
|
+
2. **You generate a Nexus API key** from the dashboard – this key is safe to use in your frontend.
|
|
24
|
+
3. The widget sends only the **Nexus key** to our proxy; the proxy validates your domain, decrypts your Groq key, and forwards the request to Groq.
|
|
25
|
+
|
|
26
|
+
**Your Groq key is never exposed to the browser** – only you (via the dashboard) can manage it.
|
|
15
27
|
|
|
16
28
|
---
|
|
17
29
|
|
|
@@ -23,7 +35,7 @@
|
|
|
23
35
|
- **Markdown & Syntax Highlighting** – responses are rendered in clean Markdown with code highlighting.
|
|
24
36
|
- **Theme Switching** – automatically follows your website’s theme; users can toggle manually.
|
|
25
37
|
- **SPA‑ready** – detects navigation changes (pushState/popState) and refreshes context.
|
|
26
|
-
- **Fully Customizable** – override
|
|
38
|
+
- **Fully Customizable** – override bot name, greeting, system prompt, and UI colours.
|
|
27
39
|
|
|
28
40
|
---
|
|
29
41
|
|
|
@@ -43,23 +55,29 @@ Add the following script to your HTML – it loads the widget and all its depend
|
|
|
43
55
|
|
|
44
56
|
## Quick Start
|
|
45
57
|
|
|
46
|
-
1. **
|
|
47
|
-
|
|
58
|
+
1. **Get your Nexus API key**
|
|
59
|
+
- Go to the [Nexus Dashboard](https://trynexusweb.vercel.app/dashboard) and sign in with Google.
|
|
60
|
+
- In the **Nexus Keys** tab, create a new key and copy it.
|
|
61
|
+
- In the **Settings** tab, store your Groq API key – it will be encrypted and never exposed.
|
|
62
|
+
|
|
63
|
+
2. **Include the widget script** on your page (see above).
|
|
64
|
+
|
|
65
|
+
3. **Configure the widget** with your Nexus API key:
|
|
48
66
|
|
|
49
67
|
```html
|
|
50
68
|
<script>
|
|
51
69
|
window.NexusConfig = {
|
|
52
|
-
apiKey: '
|
|
53
|
-
model: '
|
|
70
|
+
apiKey: 'YOUR_NEXUS_API_KEY', // Required – get from dashboard
|
|
71
|
+
model: 'llama3-8b-8192', // optional (default)
|
|
54
72
|
botName: 'My Assistant', // optional
|
|
55
73
|
greeting: 'Hello! How can I help?', // optional
|
|
56
74
|
systemPrompt: 'You are a helpful assistant...', // optional
|
|
57
|
-
theme: '
|
|
75
|
+
theme: 'auto' // 'dark' | 'light' | auto-detect
|
|
58
76
|
};
|
|
59
77
|
</script>
|
|
60
78
|
```
|
|
61
79
|
|
|
62
|
-
|
|
80
|
+
4. **That's it!** A floating chat button will appear in the bottom‑right corner of your page.
|
|
63
81
|
|
|
64
82
|
---
|
|
65
83
|
|
|
@@ -69,8 +87,8 @@ All settings are optional except `apiKey`. Set them in `window.NexusConfig` befo
|
|
|
69
87
|
|
|
70
88
|
| Option | Type | Default | Description |
|
|
71
89
|
|--------|------|---------|-------------|
|
|
72
|
-
| `apiKey` | `string` | `''` | **Required** – Your
|
|
73
|
-
| `model` | `string` | `'
|
|
90
|
+
| `apiKey` | `string` | `''` | **Required** – Your Nexus API key (from the dashboard). |
|
|
91
|
+
| `model` | `string` | `'llama3-8b-8192'` | Groq model to use. Supported: `llama3-8b-8192`, `llama3-70b-8192`, `mixtral-8x7b-32768`. |
|
|
74
92
|
| `botName` | `string` | `'Nexus AI'` | Display name in the header. |
|
|
75
93
|
| `greeting` | `string` | (default message) | Initial bot message shown when panel opens. |
|
|
76
94
|
| `systemPrompt` | `string` | (default prompt) | System instructions for the AI (contextualises page data). |
|
|
@@ -112,19 +130,18 @@ Example (dark theme override):
|
|
|
112
130
|
|
|
113
131
|
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
132
|
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
|
|
133
|
+
3. **Conversation** – user messages, together with the page context, are sent to our proxy using your Nexus key. The proxy validates the domain, decrypts your Groq key (if set), and forwards the request to Groq. The response is streamed (or not) and rendered with Markdown.
|
|
116
134
|
4. **SPA Support** – a `MutationObserver` watches for URL changes and re‑scrapes when navigation occurs.
|
|
117
135
|
|
|
118
136
|
---
|
|
119
137
|
|
|
120
138
|
## API Key & Security
|
|
121
139
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
- Treating the key as a user‑supplied value (e.g., from a backend proxy).
|
|
140
|
+
- **Nexus API key** – obtain from the [Dashboard](https://trynexusweb.vercel.app/dashboard). This key is safe to embed in your frontend; it only works for domains you have whitelisted.
|
|
141
|
+
- **Groq API key** – set it **once** in the Dashboard **Settings** tab. It is encrypted with AES‑256 and stored securely. It is never sent to the browser; only our backend uses it when proxying requests.
|
|
142
|
+
- **Domain Whitelisting** – you can add up to 10 domains in the Dashboard. Requests from unlisted domains are rejected.
|
|
126
143
|
|
|
127
|
-
|
|
144
|
+
**Never share your Groq API key publicly.** With Nexus, you don't have to.
|
|
128
145
|
|
|
129
146
|
---
|
|
130
147
|
|
|
@@ -145,13 +162,13 @@ Build the minified bundle:
|
|
|
145
162
|
```bash
|
|
146
163
|
npm run build
|
|
147
164
|
```
|
|
148
|
-
|
|
165
|
+
|
|
149
166
|
### File Structure
|
|
150
167
|
|
|
151
168
|
- `src/` – source code
|
|
152
169
|
- `core/` – config and state
|
|
153
170
|
- `ui/` – DOM manipulation and styles
|
|
154
|
-
- `api/` –
|
|
171
|
+
- `api/` – proxy communication
|
|
155
172
|
- `scraper/` – advanced scraping engine
|
|
156
173
|
- `utils/` – caching helpers
|
|
157
174
|
- `dist/` – output bundle
|
|
@@ -1 +1 @@
|
|
|
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\nI can help you understand and find information from the content you see here. Just ask me anything about this page and I'll assist you instantly! 🚀",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(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 o(n,e,a="bot",o=null,r={}){if(!n)return null;const{withRegenerate:i=!1,isError:s=!1,errorCallback:l=null}=r,c=document.createElement("div");if(c.className=`msg ${a}`,"bot"===a){const n=t(e);if(c.innerHTML=n,"undefined"!=typeof hljs&&c.querySelectorAll("pre code").forEach(n=>{hljs.highlightElement(n)}),i){const n=document.createElement("button");n.className="msg-regen-btn",n.innerHTML='<i class="fas fa-rotate-right"></i>',n.title="Regenerate response",n.setAttribute("aria-label","Regenerate"),n.addEventListener("click",n=>{n.stopPropagation();const e=new CustomEvent("regenerate-request",{detail:{messageEl:c}});document.dispatchEvent(e)}),c.appendChild(n)}}else{const n=function(n){return n?n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}(e);let t=n;o&&(t+=`<br><img src="${o}" class="image-preview" alt="attached">`),c.innerHTML=t}if(s&&l){const n=document.createElement("button");n.className="msg-retry-btn",n.innerHTML='<i class="fas fa-redo"></i> Retry',n.addEventListener("click",n=>{n.stopPropagation(),l()}),c.appendChild(n)}return n.appendChild(c),n.scrollTo({top:n.scrollHeight,behavior:"smooth"}),c}let r=null;function i(n,e){n&&(r&&(r.remove(),r=null),e&&(r=document.createElement("div"),r.className="typing-indicator",r.innerHTML="<span></span><span></span><span></span>",n.appendChild(r),n.scrollTo({top:n.scrollHeight,behavior:"smooth"})))}function s(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 l;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">${l=e.size||0,l<1024?l+" B":l<1048576?(l/1024).toFixed(1)+" KB":(l/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:[],abortController:null,lastUserMessage:null,lastBotMessageEl:null,lastErrorMessageEl:null};function c(n){Object.assign(l,n)}const d="NexusAICache",p="pageData";let u=null;function g(){return new Promise((n,e)=>{if(u)return void n(u);const t=indexedDB.open(d,1);t.onupgradeneeded=n=>{const e=n.target.result;if(!e.objectStoreNames.contains(p)){const n=e.createObjectStore(p,{keyPath:"id"});n.createIndex("url","url",{unique:!1}),n.createIndex("timestamp","timestamp",{unique:!1}),console.log("📦 IndexedDB store created")}},t.onsuccess=e=>{u=e.target.result,n(u)},t.onerror=n=>{e(n.target.error)}})}async function m(n){try{const e=(await g()).transaction(p,"readwrite").objectStore(p),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 b=null,f=null;function h(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 x(){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(b){const e=document.cloneNode(!0),t=new b(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=w(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 w(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 v(e,t,a=null,r={}){if(l.isProcessing)return;const{skipUserAdd:s=!1,onError:d=null}=r;c({isProcessing:!0});const u=document.getElementById("aiSendBtn"),b=document.getElementById("aiInput");u&&(u.disabled=!0),b&&(b.disabled=!0),s||(l.lastUserMessage={text:t,image:a},o(e,t,"user",a)),i(e,!0);const f=[];if(t&&f.push({type:"text",text:t}),a&&f.push({type:"image_url",image_url:{url:a}}),!s){if(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]}E()}const h=new AbortController;c({abortController:h});let y=!1,k="";try{const t=await async function(){let n=await async function(){try{const n=(await g()).transaction(p,"readonly").objectStore(p),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(p,"readwrite").objectStore(p).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=w(x());return await m(e),console.log("💾 Page data cached"),e}(),a=window.__NEXUS_CONFIG||n,r=[{role:"system",content:`${a.SYSTEM_PROMPT}\n\n## Current Page Context\n${t}`}];for(let n=1;n<l.conversationHistory.length-1;n++)r.push({role:l.conversationHistory[n].role,content:l.conversationHistory[n].content});r.push({role:"user",content:f});const s=await fetch(a.API_URL,{method:"POST",headers:{"Content-Type":"application/json",Origin:window.location.origin},signal:h.signal,body:JSON.stringify({nexusKey:a.API_KEY,messages:r,model:a.MODEL})});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(`Proxy error ${s.status}: ${n.error||s.statusText}`)}const d=await s.json(),u=d.choices?.[0]?.message?.content||"Sorry, I didn't understand that.";l.conversationHistory.push({role:"assistant",content:u}),E(),i(e,!1);const b=o(e,u,"bot",null,{withRegenerate:!0});b&&c({lastBotMessageEl:b}),l.lastErrorMessageEl=null}catch(n){if("AbortError"===n.name)return void console.log("Request aborted by user");console.error("Nexus Proxy Error:",n),y=!0,k=n.message||"Something went wrong.",i(e,!1);const t=o(e,`⚠️ Oops! ${k} Please try again.`,"bot",null,{isError:!0,errorCallback:()=>{l.lastErrorMessageEl&&(l.lastErrorMessageEl.remove(),l.lastErrorMessageEl=null);const n=l.lastUserMessage;n&&v(e,n.text,n.image,{skipUserAdd:!0})}});t&&c({lastErrorMessageEl:t}),d&&d(n)}finally{if(c({isProcessing:!1,abortController:null}),u&&(u.disabled=!1),b){if(b.disabled=!1,!y){b.value="",b.style.height="auto";const n=document.getElementById("aiCharCount");n&&(n.textContent="0",n.style.color="#a0a0a0")}b.focus()}}}function E(){try{const n=l.conversationHistory.slice(1);localStorage.setItem("nexus-chat-history",JSON.stringify(n))}catch(n){}}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 r=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=r,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([h("https://cdnjs.cloudflare.com/ajax/libs/readability/0.4.4/Readability.min.js"),h("https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js")]),b=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 #ai-widget-root {\n --bg-panel: rgba(9, 9, 11, 0.92);\n --bg-input: rgba(24, 24, 27, 0.6);\n --border-color: rgba(39, 39, 42, 0.6);\n --text-primary: #fafafa;\n --text-secondary: #a1a1aa;\n --user-bubble-bg: #27272a;\n --bot-bubble-bg: rgba(24, 24, 27, 0.6);\n --shadow-color: rgba(0, 0, 0, 0.4);\n --fab-bg: rgba(24, 24, 27, 0.8);\n --accent: #a855f7;\n --send-bg: #a855f7;\n --send-color: #fff;\n --border-hover: rgba(168, 85, 247, 0.3);\n }\n\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 --border-hover: rgba(0, 0, 0, 0.15);\n }\n\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.4; }\n 50% { transform: scale(1.2); 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(168, 85, 247, 0.1); }\n 70% { box-shadow: 0 0 0 16px rgba(168, 85, 247, 0); }\n 100% { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0); }\n }\n\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 #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 .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.3s cubic-bezier(0.16, 1, 0.3, 1);\n user-select: none;\n box-shadow: 0 4px 24px 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.06);\n box-shadow: 0 6px 32px var(--shadow-color);\n border-color: var(--accent);\n }\n .ai-fab .bot-icon i {\n color: #fafafa !important;\n font-size: 26px !important;\n filter: none;\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 .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(24px);\n -webkit-backdrop-filter: blur(24px);\n border-radius: 28px;\n box-shadow: 0 16px 64px 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 .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: #a855f7;\n animation: pulseDot 2s infinite;\n flex-shrink: 0;\n }\n .ai-theme-toggle,\n .ai-close-btn,\n .ai-clear-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 ease;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ai-theme-toggle:hover,\n .ai-close-btn:hover,\n .ai-clear-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n }\n .ai-theme-toggle:hover {\n transform: rotate(180deg) !important;\n }\n .ai-close-btn:hover {\n transform: rotate(90deg);\n }\n\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 .msg {\n max-width: 85%;\n font-size: 14.5px;\n line-height: 1.7;\n word-break: break-word;\n animation: slideUpFade 0.4s 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 1px 4px var(--shadow-color);\n }\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 .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 padding-bottom: 32px;\n }\n\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 .msg.bot p { margin: 0 0 12px 0; }\n .msg.bot p:last-child { margin-bottom: 0; }\n .msg.bot code {\n background: rgba(39, 39, 42, 0.6);\n padding: 2px 8px;\n border-radius: 6px;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n border: 1px solid rgba(39, 39, 42, 0.4);\n color: #e4e4e7;\n }\n .msg.bot pre {\n background: rgba(9, 9, 11, 0.4);\n padding: 16px;\n border-radius: 14px;\n overflow-x: auto;\n margin: 12px 0;\n border: 1px solid rgba(39, 39, 42, 0.4);\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: #a855f7; text-decoration: none; border-bottom: 1px dotted rgba(168, 85, 247, 0.2); }\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 .msg-regen-btn,\n .msg-retry-btn {\n background: transparent;\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n padding: 4px 10px;\n border-radius: 20px;\n font-size: 12px;\n cursor: pointer;\n margin-top: 8px;\n transition: all 0.2s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .msg-regen-btn:hover,\n .msg-retry-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n border-color: var(--accent);\n }\n .msg.bot .msg-regen-btn {\n position: absolute;\n bottom: 8px;\n right: 8px;\n opacity: 0.6;\n font-size: 14px;\n padding: 4px 6px;\n border-radius: 50%;\n width: 30px;\n height: 30px;\n justify-content: center;\n background: var(--bg-panel);\n margin-top: 0;\n border: 1px solid var(--border-color);\n }\n .msg.bot .msg-regen-btn:hover {\n opacity: 1;\n }\n .msg-retry-btn {\n background: rgba(239, 68, 68, 0.1);\n border-color: rgba(239, 68, 68, 0.3);\n color: #ef4444;\n margin-left: 4px;\n }\n .msg-retry-btn:hover {\n background: rgba(239, 68, 68, 0.2);\n border-color: #ef4444;\n color: #ef4444;\n }\n\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.25s ease;\n min-height: 48px;\n }\n .ai-input-container:focus-within {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(168, 85, 247, 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 2px 8px var(--shadow-color);\n transition: all 0.25s ease;\n border: none;\n }\n .send-btn:hover {\n transform: scale(1.06);\n box-shadow: 0 4px 12px var(--shadow-color);\n }\n .send-btn:active { transform: scale(0.92); }\n .send-btn:disabled {\n background: rgba(39, 39, 42, 0.6);\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.4;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n }\n\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: rgba(24, 24, 27, 0.8);\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 2px 12px 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 4px 16px 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: rgba(39, 39, 42, 0.6);\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.12);\n transform: scale(1.1);\n }\n\n .typing-indicator {\n display: flex;\n gap: 6px;\n padding: 12px 0;\n align-self: flex-start;\n background: rgba(24, 24, 27, 0.6);\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: #a855f7;\n animation: typing 1.4s infinite;\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 @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 .msg.bot {\n padding-bottom: 28px;\n }\n }\n ",document.head.appendChild(n)}(),function(){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-clear-btn" id="aiClearBtn" aria-label="Clear chat">\n <i class="fas fa-trash"></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");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)})}();const i=document.getElementById("ai-widget-root");i&&(i.dataset.theme=r.THEME||"dark");const u=document.getElementById("aiThemeToggle");if(u){const n="dark"===r.THEME;u.innerHTML=n?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',u.title=n?"Switch to Light":"Switch to Dark",u.addEventListener("click",k)}const y=e("aiPanel"),E=e("aiMessages"),S=e("aiFab"),M=e("aiCloseBtn"),C=e("aiSendBtn"),I=e("aiInput"),L=e("aiAttachBtn"),T=e("aiPreviewContainer"),P=e("aiClearBtn");P&&P.addEventListener("click",()=>{if(confirm("Clear all chat messages?")){E.innerHTML="";const e=function(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>")}(window.__NEXUS_CONFIG?.GREETING||n.GREETING),t=document.createElement("div");t.className="msg bot",t.innerHTML=e,E.appendChild(t),l.conversationHistory=[{role:"system",content:r.SYSTEM_PROMPT}],function(){try{localStorage.removeItem("nexus-chat-history")}catch{}}(),c({lastUserMessage:null,lastBotMessageEl:null,lastErrorMessageEl:null})}}),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.body,{childList:!0,subtree:!1}),window.addEventListener("popstate",()=>{setTimeout(()=>{window.location.href!==e&&(e=window.location.href,n())},100)})}(()=>{console.log("🔄 Page changed, refreshing context..."),async function(){console.log("🔄 Forcing page context refresh...");const n=w(x());await m(n),console.log("✅ Page context refreshed")}(),o(E,"🔄 Page context updated!","bot")});const O=document.createElement("input");O.type="file";const _=n.ALLOWED_FILE_TYPES||["image/*"];function N(){l.abortController&&(l.abortController.abort(),c({abortController:null})),c({isOpen:a(y,!1)})}O.accept=_.join(","),O.style.display="none",O.id="aiFileInput",document.body.appendChild(O),L?.addEventListener("click",()=>O.click()),O.addEventListener("change",n=>{const e=n.target.files?.[0];if(!e)return;if(!_.some(n=>{if(n.endsWith("/*")){const t=n.slice(0,-2);return e.type.startsWith(t)}return e.type===n||e.name.endsWith(n.slice(1))}))return void alert("Please select a valid file type.");const t=new FileReader;t.onload=n=>{const t={dataUrl:n.target.result,name:e.name,type:e.type,size:e.size};c({attachedFile:t}),s(T,t);const a=document.getElementById("aiRemoveFile");a&&a.addEventListener("click",()=>{c({attachedFile:null}),s(T,null),I?.focus()}),I?.focus()},t.readAsDataURL(e),n.target.value=""}),S?.addEventListener("click",()=>{c({isOpen:a(y,!0)})}),M?.addEventListener("click",N),document.addEventListener("click",n=>{const e=document.getElementById("ai-widget-root");e&&l.isOpen&&!e.contains(n.target)&&N()});const B=()=>{const n=I?.value.trim();if(!n&&!l.attachedFile)return;if(l.isProcessing)return;l.attachedFile;const e=l.attachedFile?.dataUrl||null;c({attachedFile:null,lastUserMessage:{text:n,image:e}}),I.value="",I.style.height="auto",s(T,null),v(E,n,e)};C?.addEventListener("click",B),I?.addEventListener("keydown",n=>{"Enter"!==n.key||n.shiftKey||(n.preventDefault(),B())}),document.addEventListener("regenerate-request",n=>{const{messageEl:e}=n.detail;if(!e)return;if(l.isProcessing)return;const t=l.conversationHistory,a=t.length-1;if(a<0||"assistant"!==t[a].role)return;const o=a-1;if(o<0||"user"!==t[o].role)return;const r=t[o].content;t.pop(),e.remove(),l.lastBotMessageEl===e&&c({lastBotMessageEl:null}),v(E,r,null,{skipUserAdd:!0})});const A=function(){try{const n=localStorage.getItem("nexus-chat-history");return n?JSON.parse(n):null}catch{return null}}();A&&A.length>0&&(l.conversationHistory=[{role:"system",content:r.SYSTEM_PROMPT}],A.forEach(n=>{if("user"===n.role)o(E,n.content,"user");else if("assistant"===n.role){const e=o(E,n.content,"bot",null,{withRegenerate:!0});e&&n===A[A.length-1]&&c({lastBotMessageEl:e})}l.conversationHistory.push(n)}));const j=await async function(){try{const n=(await g()).transaction(p,"readonly").objectStore(p);return new Promise(e=>{const t=n.count();t.onsuccess=()=>{e({totalEntries:t.result,database:d,version:1})},t.onerror=()=>e(null)})}catch{return null}}();console.log("📊 Cache Stats:",j||"No cache yet"),console.log("✨ Nexus AI v2.0 initialized with theme:",r.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\nI can help you understand and find information from the content you see here. Just ask me anything about this page and I'll assist you instantly! 🚀",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"},EMAIL_REGEX:/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,PHONE_REGEX:/(?:\+?[\d\s\-()]{8,15})/g,BUTTON_SELECTORS:["button",'[role="button"]',".btn",".button",'[class*="btn"]','[class*="button"]','input[type="button"]','input[type="submit"]'],STAT_SELECTORS:["[data-target]",".stat-number",".count",".counter","[data-count]","[data-value]",".stats-number"],CONTACT_SELECTORS:['a[href^="mailto:"]','a[href^="tel:"]','[class*="email"]','[class*="phone"]','[class*="contact"]','[id*="email"]','[id*="phone"]','[id*="contact"]']},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(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 o(n,e,a="bot",o=null,r={}){if(!n)return null;const{withRegenerate:i=!1,isError:s=!1,errorCallback:l=null}=r,c=document.createElement("div");if(c.className=`msg ${a}`,"bot"===a){const n=t(e);if(c.innerHTML=n,"undefined"!=typeof hljs&&c.querySelectorAll("pre code").forEach(n=>{hljs.highlightElement(n)}),i){const n=document.createElement("button");n.className="msg-regen-btn",n.innerHTML='<i class="fas fa-rotate-right"></i>',n.title="Regenerate response",n.setAttribute("aria-label","Regenerate"),n.addEventListener("click",n=>{n.stopPropagation();const e=new CustomEvent("regenerate-request",{detail:{messageEl:c}});document.dispatchEvent(e)}),c.appendChild(n)}}else{const n=function(n){return n?n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}(e);let t=n;o&&(t+=`<br><img src="${o}" class="image-preview" alt="attached">`),c.innerHTML=t}if(s&&l){const n=document.createElement("button");n.className="msg-retry-btn",n.innerHTML='<i class="fas fa-redo"></i> Retry',n.addEventListener("click",n=>{n.stopPropagation(),l()}),c.appendChild(n)}return n.appendChild(c),n.scrollTo({top:n.scrollHeight,behavior:"smooth"}),c}let r=null;function i(n,e){n&&(r&&(r.remove(),r=null),e&&(r=document.createElement("div"),r.className="typing-indicator",r.innerHTML="<span></span><span></span><span></span>",n.appendChild(r),n.scrollTo({top:n.scrollHeight,behavior:"smooth"})))}function s(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 l;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">${l=e.size||0,l<1024?l+" B":l<1048576?(l/1024).toFixed(1)+" KB":(l/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:[],abortController:null,lastUserMessage:null,lastBotMessageEl:null,lastErrorMessageEl:null};function c(n){Object.assign(l,n)}const d="NexusAICache",p="pageData";let u=null;function g(){return new Promise((n,e)=>{if(u)return void n(u);const t=indexedDB.open(d,1);t.onupgradeneeded=n=>{const e=n.target.result;if(!e.objectStoreNames.contains(p)){const n=e.createObjectStore(p,{keyPath:"id"});n.createIndex("url","url",{unique:!1}),n.createIndex("timestamp","timestamp",{unique:!1}),console.log("📦 IndexedDB store created")}},t.onsuccess=e=>{u=e.target.result,n(u)},t.onerror=n=>{e(n.target.error)}})}async function m(n){try{const e=(await g()).transaction(p,"readwrite").objectStore(p),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 b=null,f=null;function h(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 x(){const e=window.__NEXUS_CONFIG||{};return{...n,...e,CONTACT_SELECTORS:e.CONTACT_SELECTORS||n.CONTACT_SELECTORS,BUTTON_SELECTORS:e.BUTTON_SELECTORS||n.BUTTON_SELECTORS,STAT_SELECTORS:e.STAT_SELECTORS||n.STAT_SELECTORS,EMAIL_REGEX:e.EMAIL_REGEX||n.EMAIL_REGEX,PHONE_REGEX:e.PHONE_REGEX||n.PHONE_REGEX}}function y(){console.log("🕷️ Advanced scraping started...");const n={title:document.title,url:window.location.href,timestamp:(new Date).toISOString(),pageType:w(),readability:null,structuredData:[],markdown:"",summary:"",metadata:{},contactInfo:{emails:[],phones:[]},buttonsInfo:[],statsInfo:[]};try{if(b){const e=document.cloneNode(!0),t=new b(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.contactInfo=function(){const n=x(),e=new Set,t=new Set,a=document.body.innerText||"";return(a.match(n.EMAIL_REGEX)||[]).forEach(n=>e.add(n)),(a.match(n.PHONE_REGEX)||[]).forEach(n=>{const e=n.trim();e.length>=8&&t.add(e)}),n.CONTACT_SELECTORS.forEach(a=>{document.querySelectorAll(a).forEach(o=>{if(a.includes("mailto")){const n=o.getAttribute("href");if(n&&n.startsWith("mailto:")){const t=n.replace("mailto:","").split("?")[0];t&&e.add(t)}}else if(a.includes("tel")){const n=o.getAttribute("href");if(n&&n.startsWith("tel:")){const e=n.replace("tel:","");e&&t.add(e)}}else{const a=o.textContent.trim();a&&((a.match(n.EMAIL_REGEX)||[]).forEach(n=>e.add(n)),(a.match(n.PHONE_REGEX)||[]).forEach(n=>{n.trim().length>=8&&t.add(n.trim())}))}})}),{emails:Array.from(e),phones:Array.from(t)}}(),n.buttonsInfo=function(){const n=x(),e=[],t=new Set;return n.BUTTON_SELECTORS.forEach(n=>{document.querySelectorAll(n).forEach(n=>{let a=n.textContent.trim()||n.value||n.getAttribute("aria-label")||"";if(!a)return;const o=n.getAttribute("data-action")||n.getAttribute("onclick")||"",r=a+o;t.has(r)||(t.add(r),e.push({name:a,action:o?o.trim():"click"}))})}),e.slice(0,20)}(),n.statsInfo=function(){const n=x(),e=[],t=new Set;return n.STAT_SELECTORS.forEach(n=>{document.querySelectorAll(n).forEach(n=>{let a=n.getAttribute("data-target")||n.getAttribute("data-count")||n.getAttribute("data-value")||n.textContent.trim();if(!a)return;const o=n.closest('[class*="stat"]')||n.parentElement;let r="";if(o){const n=o.querySelector('.stat-label, .label, [class*="label"]');if(n)r=n.textContent.trim();else{const n=o.textContent.trim(),e=n.indexOf(a);if(e>-1){const t=n.substring(0,e).trim(),o=n.substring(e+a.length).trim();r=t||o}}}r||(r="Stat");const i=r+a;t.has(i)||(t.add(i),e.push({label:r,value:a}))})}),e.slice(0,20)}(),n.aiContext=v(n),console.log("✅ Advanced scraping complete"),n}function w(){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.contactInfo&&(n.contactInfo.emails.length>0||n.contactInfo.phones.length>0)&&(e+="## 📞 Contact Information\n",n.contactInfo.emails.length>0&&(e+=`- **Emails:** ${n.contactInfo.emails.join(", ")}\n`),n.contactInfo.phones.length>0&&(e+=`- **Phones:** ${n.contactInfo.phones.join(", ")}\n`),e+="\n"),n.buttonsInfo&&n.buttonsInfo.length>0&&(e+="## 🔘 Interactive Buttons\n",n.buttonsInfo.forEach(n=>{e+=`- **${n.name}** → ${n.action}\n`}),e+="\n"),n.statsInfo&&n.statsInfo.length>0&&(e+="## 📊 Key Statistics\n",n.statsInfo.forEach(n=>{e+=`- **${n.label}:** ${n.value}\n`}),e+="\n"),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,r={}){if(l.isProcessing)return;const{skipUserAdd:s=!1,onError:d=null}=r;c({isProcessing:!0});const u=document.getElementById("aiSendBtn"),b=document.getElementById("aiInput");u&&(u.disabled=!0),b&&(b.disabled=!0),s||(l.lastUserMessage={text:t,image:a},o(e,t,"user",a)),i(e,!0);const f=[];if(t&&f.push({type:"text",text:t}),a&&f.push({type:"image_url",image_url:{url:a}}),!s){if(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]}k()}const h=new AbortController;c({abortController:h});let x=!1,w="";try{const t=await async function(){let n=await async function(){try{const n=(await g()).transaction(p,"readonly").objectStore(p),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(p,"readwrite").objectStore(p).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(y());return await m(e),console.log("💾 Page data cached"),e}(),a=window.__NEXUS_CONFIG||n,r=[{role:"system",content:`${a.SYSTEM_PROMPT}\n\n## Current Page Context\n${t}`}];for(let n=1;n<l.conversationHistory.length-1;n++)r.push({role:l.conversationHistory[n].role,content:l.conversationHistory[n].content});r.push({role:"user",content:f});const s=await fetch(a.API_URL,{method:"POST",headers:{"Content-Type":"application/json",Origin:window.location.origin},signal:h.signal,body:JSON.stringify({nexusKey:a.API_KEY,messages:r,model:a.MODEL})});if(!s.ok){const n=await s.json().catch(()=>({}));throw new Error(`Proxy error ${s.status}: ${n.error||s.statusText}`)}const d=await s.json(),u=d.choices?.[0]?.message?.content||"Sorry, I didn't understand that.";l.conversationHistory.push({role:"assistant",content:u}),k(),i(e,!1);const b=o(e,u,"bot",null,{withRegenerate:!0});b&&c({lastBotMessageEl:b}),l.lastErrorMessageEl=null}catch(n){if("AbortError"===n.name)return void console.log("Request aborted by user");console.error("Nexus Proxy Error:",n),x=!0,w=n.message||"Something went wrong.",i(e,!1);const t=o(e,`⚠️ Oops! ${w} Please try again.`,"bot",null,{isError:!0,errorCallback:()=>{l.lastErrorMessageEl&&(l.lastErrorMessageEl.remove(),l.lastErrorMessageEl=null);const n=l.lastUserMessage;n&&E(e,n.text,n.image,{skipUserAdd:!0})}});t&&c({lastErrorMessageEl:t}),d&&d(n)}finally{if(c({isProcessing:!1,abortController:null}),u&&(u.disabled=!1),b){if(b.disabled=!1,!x){b.value="",b.style.height="auto";const n=document.getElementById("aiCharCount");n&&(n.textContent="0",n.style.color="#a0a0a0")}b.focus()}}}function k(){try{const n=l.conversationHistory.slice(1);localStorage.setItem("nexus-chat-history",JSON.stringify(n))}catch(n){}}function S(){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 T(){const r=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=r,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([h("https://cdnjs.cloudflare.com/ajax/libs/readability/0.4.4/Readability.min.js"),h("https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js")]),b=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 #ai-widget-root {\n --bg-panel: rgba(9, 9, 11, 0.92);\n --bg-input: rgba(24, 24, 27, 0.6);\n --border-color: rgba(39, 39, 42, 0.6);\n --text-primary: #fafafa;\n --text-secondary: #a1a1aa;\n --user-bubble-bg: #27272a;\n --bot-bubble-bg: rgba(24, 24, 27, 0.6);\n --shadow-color: rgba(0, 0, 0, 0.4);\n --fab-bg: rgba(24, 24, 27, 0.8);\n --accent: #a855f7;\n --send-bg: #a855f7;\n --send-color: #fff;\n --border-hover: rgba(168, 85, 247, 0.3);\n }\n\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 --border-hover: rgba(0, 0, 0, 0.15);\n }\n\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.4; }\n 50% { transform: scale(1.2); 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(168, 85, 247, 0.1); }\n 70% { box-shadow: 0 0 0 16px rgba(168, 85, 247, 0); }\n 100% { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0); }\n }\n\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 #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 .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.3s cubic-bezier(0.16, 1, 0.3, 1);\n user-select: none;\n box-shadow: 0 4px 24px 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.06);\n box-shadow: 0 6px 32px var(--shadow-color);\n border-color: var(--accent);\n }\n .ai-fab .bot-icon i {\n color: #fafafa !important;\n font-size: 26px !important;\n filter: none;\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 .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(24px);\n -webkit-backdrop-filter: blur(24px);\n border-radius: 28px;\n box-shadow: 0 16px 64px 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 .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: #a855f7;\n animation: pulseDot 2s infinite;\n flex-shrink: 0;\n }\n .ai-theme-toggle,\n .ai-close-btn,\n .ai-clear-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 ease;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .ai-theme-toggle:hover,\n .ai-close-btn:hover,\n .ai-clear-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n }\n .ai-theme-toggle:hover {\n transform: rotate(180deg) !important;\n }\n .ai-close-btn:hover {\n transform: rotate(90deg);\n }\n\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 .msg {\n max-width: 85%;\n font-size: 14.5px;\n line-height: 1.7;\n word-break: break-word;\n animation: slideUpFade 0.4s 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 1px 4px var(--shadow-color);\n }\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 .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 padding-bottom: 32px;\n }\n\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 .msg.bot p { margin: 0 0 12px 0; }\n .msg.bot p:last-child { margin-bottom: 0; }\n .msg.bot code {\n background: rgba(39, 39, 42, 0.6);\n padding: 2px 8px;\n border-radius: 6px;\n font-family: ui-monospace, monospace;\n font-size: 0.9em;\n border: 1px solid rgba(39, 39, 42, 0.4);\n color: #e4e4e7;\n }\n .msg.bot pre {\n background: rgba(9, 9, 11, 0.4);\n padding: 16px;\n border-radius: 14px;\n overflow-x: auto;\n margin: 12px 0;\n border: 1px solid rgba(39, 39, 42, 0.4);\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: #a855f7; text-decoration: none; border-bottom: 1px dotted rgba(168, 85, 247, 0.2); }\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 .msg-regen-btn,\n .msg-retry-btn {\n background: transparent;\n border: 1px solid var(--border-color);\n color: var(--text-secondary);\n padding: 4px 10px;\n border-radius: 20px;\n font-size: 12px;\n cursor: pointer;\n margin-top: 8px;\n transition: all 0.2s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n }\n .msg-regen-btn:hover,\n .msg-retry-btn:hover {\n background: var(--border-color);\n color: var(--text-primary);\n border-color: var(--accent);\n }\n .msg.bot .msg-regen-btn {\n position: absolute;\n bottom: 8px;\n right: 8px;\n opacity: 0.6;\n font-size: 14px;\n padding: 4px 6px;\n border-radius: 50%;\n width: 30px;\n height: 30px;\n justify-content: center;\n background: var(--bg-panel);\n margin-top: 0;\n border: 1px solid var(--border-color);\n }\n .msg.bot .msg-regen-btn:hover {\n opacity: 1;\n }\n .msg-retry-btn {\n background: rgba(239, 68, 68, 0.1);\n border-color: rgba(239, 68, 68, 0.3);\n color: #ef4444;\n margin-left: 4px;\n }\n .msg-retry-btn:hover {\n background: rgba(239, 68, 68, 0.2);\n border-color: #ef4444;\n color: #ef4444;\n }\n\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.25s ease;\n min-height: 48px;\n }\n .ai-input-container:focus-within {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(168, 85, 247, 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 2px 8px var(--shadow-color);\n transition: all 0.25s ease;\n border: none;\n }\n .send-btn:hover {\n transform: scale(1.06);\n box-shadow: 0 4px 12px var(--shadow-color);\n }\n .send-btn:active { transform: scale(0.92); }\n .send-btn:disabled {\n background: rgba(39, 39, 42, 0.6);\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.4;\n font-variant-numeric: tabular-nums;\n letter-spacing: 0.3px;\n }\n\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: rgba(24, 24, 27, 0.8);\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 2px 12px 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 4px 16px 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: rgba(39, 39, 42, 0.6);\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.12);\n transform: scale(1.1);\n }\n\n .typing-indicator {\n display: flex;\n gap: 6px;\n padding: 12px 0;\n align-self: flex-start;\n background: rgba(24, 24, 27, 0.6);\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: #a855f7;\n animation: typing 1.4s infinite;\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 @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 .msg.bot {\n padding-bottom: 28px;\n }\n }\n ",document.head.appendChild(n)}(),function(){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-clear-btn" id="aiClearBtn" aria-label="Clear chat">\n <i class="fas fa-trash"></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");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)})}();const i=document.getElementById("ai-widget-root");i&&(i.dataset.theme=r.THEME||"dark");const u=document.getElementById("aiThemeToggle");if(u){const n="dark"===r.THEME;u.innerHTML=n?'<i class="fas fa-moon"></i>':'<i class="fas fa-sun"></i>',u.title=n?"Switch to Light":"Switch to Dark",u.addEventListener("click",S)}const x=e("aiPanel"),w=e("aiMessages"),k=e("aiFab"),T=e("aiCloseBtn"),C=e("aiSendBtn"),I=e("aiInput"),L=e("aiAttachBtn"),O=e("aiPreviewContainer"),M=e("aiClearBtn");M&&M.addEventListener("click",()=>{if(confirm("Clear all chat messages?")){w.innerHTML="";const e=function(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>")}(window.__NEXUS_CONFIG?.GREETING||n.GREETING),t=document.createElement("div");t.className="msg bot",t.innerHTML=e,w.appendChild(t),l.conversationHistory=[{role:"system",content:r.SYSTEM_PROMPT}],function(){try{localStorage.removeItem("nexus-chat-history")}catch{}}(),c({lastUserMessage:null,lastBotMessageEl:null,lastErrorMessageEl:null})}}),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.body,{childList:!0,subtree:!1}),window.addEventListener("popstate",()=>{setTimeout(()=>{window.location.href!==e&&(e=window.location.href,n())},100)})}(()=>{console.log("🔄 Page changed, refreshing context..."),async function(){console.log("🔄 Forcing page context refresh...");const n=v(y());await m(n),console.log("✅ Page context refreshed")}(),o(w,"🔄 Page context updated!","bot")});const _=document.createElement("input");_.type="file";const A=n.ALLOWED_FILE_TYPES||["image/*"];function P(){l.abortController&&(l.abortController.abort(),c({abortController:null})),c({isOpen:a(x,!1)})}_.accept=A.join(","),_.style.display="none",_.id="aiFileInput",document.body.appendChild(_),L?.addEventListener("click",()=>_.click()),_.addEventListener("change",n=>{const e=n.target.files?.[0];if(!e)return;if(!A.some(n=>{if(n.endsWith("/*")){const t=n.slice(0,-2);return e.type.startsWith(t)}return e.type===n||e.name.endsWith(n.slice(1))}))return void alert("Please select a valid file type.");const t=new FileReader;t.onload=n=>{const t={dataUrl:n.target.result,name:e.name,type:e.type,size:e.size};c({attachedFile:t}),s(O,t);const a=document.getElementById("aiRemoveFile");a&&a.addEventListener("click",()=>{c({attachedFile:null}),s(O,null),I?.focus()}),I?.focus()},t.readAsDataURL(e),n.target.value=""}),k?.addEventListener("click",()=>{c({isOpen:a(x,!0)})}),T?.addEventListener("click",P),document.addEventListener("click",n=>{const e=document.getElementById("ai-widget-root");e&&l.isOpen&&!e.contains(n.target)&&P()});const N=()=>{const n=I?.value.trim();if(!n&&!l.attachedFile)return;if(l.isProcessing)return;l.attachedFile;const e=l.attachedFile?.dataUrl||null;c({attachedFile:null,lastUserMessage:{text:n,image:e}}),I.value="",I.style.height="auto",s(O,null),E(w,n,e)};C?.addEventListener("click",N),I?.addEventListener("keydown",n=>{"Enter"!==n.key||n.shiftKey||(n.preventDefault(),N())}),document.addEventListener("regenerate-request",n=>{const{messageEl:e}=n.detail;if(!e)return;if(l.isProcessing)return;const t=l.conversationHistory,a=t.length-1;if(a<0||"assistant"!==t[a].role)return;const o=a-1;if(o<0||"user"!==t[o].role)return;const r=t[o].content;t.pop(),e.remove(),l.lastBotMessageEl===e&&c({lastBotMessageEl:null}),E(w,r,null,{skipUserAdd:!0})});const R=function(){try{const n=localStorage.getItem("nexus-chat-history");return n?JSON.parse(n):null}catch{return null}}();R&&R.length>0&&(l.conversationHistory=[{role:"system",content:r.SYSTEM_PROMPT}],R.forEach(n=>{if("user"===n.role)o(w,n.content,"user");else if("assistant"===n.role){const e=o(w,n.content,"bot",null,{withRegenerate:!0});e&&n===R[R.length-1]&&c({lastBotMessageEl:e})}l.conversationHistory.push(n)}));const B=await async function(){try{const n=(await g()).transaction(p,"readonly").objectStore(p);return new Promise(e=>{const t=n.count();t.onsuccess=()=>{e({totalEntries:t.result,database:d,version:1})},t.onerror=()=>e(null)})}catch{return null}}();console.log("📊 Cache Stats:",B||"No cache yet"),console.log("✨ Nexus AI v2.0 initialized with theme:",r.THEME)}l.conversationHistory=[{role:"system",content:n.SYSTEM_PROMPT}],"loading"===document.readyState?document.addEventListener("DOMContentLoaded",T):T()})();
|