@yuktishaalaa/yuktai 5.3.8 → 5.3.9

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
@@ -46,6 +46,39 @@ Six modules ship in a single npm package, for React/Next.js and, since v5.3, for
46
46
 
47
47
  ---
48
48
 
49
+ ## What's new in v5.3.9: `YuktAIWrapper` launcher, opt-in buttons, Rust Ask page
50
+
51
+ Turn on only the buttons a site needs, put them behind one launcher, and let 💬 Ask page find answers with the Rust RAG first.
52
+
53
+ ```tsx
54
+ "use client";
55
+ import type { ReactNode } from "react";
56
+ import { YuktAIWrapper } from "@yuktishaalaa/yuktai";
57
+
58
+ export default function YuktaiClient({ children }: { children: ReactNode }) {
59
+ return (
60
+ <YuktAIWrapper position="left" launcher showAccessibility showRag showAgent showVibeCoder engine="rust">
61
+ {children}
62
+ </YuktAIWrapper>
63
+ );
64
+ }
65
+ ```
66
+
67
+ | Prop | Button |
68
+ |---|---|
69
+ | `launcher` | ✨ one button that shows/hides the others. Remembers the visitor's choice, closes with Esc, sets `<html data-yuktai-tools="open\|closed">` for styling |
70
+ | `showAccessibility` | ♿ Accessibility preferences |
71
+ | `showRag` | 💬 Ask page |
72
+ | `showAgent` | 🤖 AI Agent |
73
+ | `showVibeCoder` | ⚡ Vibe Coder (a developer tool; leave it off on public sites) |
74
+ | `engine` | 💬 Ask page search: `"rust"`, `"auto"` or `"ts"` |
75
+
76
+ - **Opt-in:** as soon as you set `launcher`, `showAccessibility` or `showVibeCoder`, only the buttons you turn on are shown, stacked with no gaps. Without those props the wrapper behaves exactly as before (♿ always; ⚡ with `showAgent`).
77
+ - **💬 with `engine`:** the page RAG finds the best passages first (Rust: fast, offline, Telugu-aware) and shows them at once with "From this page" and the search time. If Gemini Nano or Transformers.js is available, the model then writes the answer from **only those passages**, instead of reading the whole page (Gemini) or embedding every chunk for each question (Transformers.js). It works even with no model at all. Without `engine`, 💬 works exactly as before.
78
+ - **Fixes:** ⚡ Vibe Coder can now be turned off on its own (it used to follow `showAgent`); Esc now closes the Vibe Coder panel too.
79
+
80
+ ---
81
+
49
82
  ## What's new in v5.3.8: Rust RAG for "Ask page" (optional)
50
83
 
51
84
  The page + data search behind **Ask page** (`createContextIndex`, used after `askAboutPage`) can now run in **Rust**. Same passages, same BM25 scores (bit for bit), same answers: only faster. Free, offline, and it works in PWAs: the engine (about 33 KB of WebAssembly) is embedded in the package.
@@ -10,7 +10,7 @@ The page has form fields the user may need to fill.`:""}
10
10
 
11
11
  User task: ${a}
12
12
 
13
- Action plan:`,i=await c.prompt(r);return c.destroy(),i?.trim()||""}async function H(n,a){let{askPageWithTransformers:t}=await import("./transformers-rag-HW4QKA5T.mjs");return(await t(`How do I: ${a}`)).answer||"I could not create a plan for this task."}async function S(n,a,t){if(!n.trim())return{success:!1,steps:[],error:"Please tell me what you want to do."};if(!a)return{success:!1,steps:[],error:"No AI engine available on this device."};let o=[],l=(c,e="info")=>{let r={text:c,type:e};o.push(r),t(r)};try{l("\u{1F4D6} Reading page content...","info");let c=T(),e=x(),r=e.length>0;c.length<50&&l("\u26A0\uFE0F Page content is very limited. This may be a static image page.","error"),l(r?`\u{1F4CB} Found ${e.length} form field${e.length!==1?"s":""} on this page`:"\u{1F4C4} No form fields found \u2014 this appears to be a content page","info"),l("\u{1F916} Creating action plan...","info");let i="";try{a==="gemini"?i=await M(c,n,r):i=await H(c,n)}catch{i=r?`1. Locate the form on this page
13
+ Action plan:`,i=await c.prompt(r);return c.destroy(),i?.trim()||""}async function H(n,a){let{askPageWithTransformers:t}=await import("./transformers-rag-MQZRMFTG.mjs");return(await t(`How do I: ${a}`)).answer||"I could not create a plan for this task."}async function S(n,a,t){if(!n.trim())return{success:!1,steps:[],error:"Please tell me what you want to do."};if(!a)return{success:!1,steps:[],error:"No AI engine available on this device."};let o=[],l=(c,e="info")=>{let r={text:c,type:e};o.push(r),t(r)};try{l("\u{1F4D6} Reading page content...","info");let c=T(),e=x(),r=e.length>0;c.length<50&&l("\u26A0\uFE0F Page content is very limited. This may be a static image page.","error"),l(r?`\u{1F4CB} Found ${e.length} form field${e.length!==1?"s":""} on this page`:"\u{1F4C4} No form fields found \u2014 this appears to be a content page","info"),l("\u{1F916} Creating action plan...","info");let i="";try{a==="gemini"?i=await M(c,n,r):i=await H(c,n)}catch{i=r?`1. Locate the form on this page
14
14
  2. Fill each required field
15
15
  3. Review your answers
16
16
  4. Submit the form`:`1. Read the page carefully
@@ -0,0 +1,11 @@
1
+ function i(){return new Promise(t=>{let r=setTimeout(t,1500),e=new MutationObserver(()=>{clearTimeout(r),r=setTimeout(()=>{e.disconnect(),t()},500)});e.observe(document.body,{childList:!0,subtree:!0})})}function c(){let t=[],r=document.querySelectorAll("*");for(let e of r){if(e.closest("[data-yuktai-panel]"))continue;let s=e.innerText?.trim();s&&s.length>30&&t.push(s);let n=e.getAttribute("aria-label");if(n&&n.length>10&&t.push(n),(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement)&&(e.placeholder&&t.push(e.placeholder),e.value&&t.push(e.value)),e instanceof HTMLButtonElement){let a=e.innerText||e.getAttribute("aria-label");a&&t.push(a)}}return t.join(" ").slice(0,3500)}async function l(t){if(!t.trim())return{success:!1,answer:"",error:"Please type a question."};try{let r=window,e=r.LanguageModel||r.ai?.languageModel;if(!e)return{success:!1,answer:"",error:"Gemini Nano not available."};await i();let s=c();if(!s||s.length<100)return{success:!1,answer:"",error:"Page content not readable."};let n;try{n=await e.create({systemPrompt:`Answer ONLY using page content.
2
+ Keep answer short (2\u20133 sentences).
3
+ If not found say: "I could not find that on this page."`,outputLanguage:"en"})}catch{n=await e.create()}let a=`Page:
4
+ ${s}
5
+
6
+ Q: ${t}`,o=await n.prompt(a);return n?.destroy&&n.destroy(),{success:!0,answer:o?.trim()||"No answer found."}}catch(r){return{success:!1,answer:"",error:r instanceof Error?r.message:"Error occurred"}}}async function f(t,r){if(!t.trim()||!r.trim())return{success:!1,answer:"",error:"Nothing to answer from."};try{let e=window,s=e.LanguageModel||e.ai?.languageModel;if(!s)return{success:!1,answer:"",error:"Gemini Nano not available."};let n;try{n=await s.create({systemPrompt:`Answer ONLY using page content.
7
+ Keep answer short (2\u20133 sentences).
8
+ If not found say: "I could not find that on this page."`,outputLanguage:"en"})}catch{n=await s.create()}let a=await n.prompt(`Page:
9
+ ${r.slice(0,3500)}
10
+
11
+ Q: ${t}`);return n?.destroy&&n.destroy(),{success:!0,answer:a?.trim()||"No answer found."}}catch(e){return{success:!1,answer:"",error:e instanceof Error?e.message:"Error occurred"}}}export{l as a,f as b};
@@ -0,0 +1,13 @@
1
+ var g=null,m=null,l=!1,f=!1;function h(){return typeof navigator>"u"?!1:/Android|iPhone|iPad|iPod|Mobile|Tablet/i.test(navigator.userAgent)}function p(){if(h())return"wasm";try{if(typeof navigator<"u"&&"gpu"in navigator&&navigator.gpu!==void 0)return"webgpu"}catch{}return"wasm"}async function w(){if(!f){if(l){for(;l;)await new Promise(e=>setTimeout(e,200));return}l=!0;try{let{pipeline:e,env:t}=await import("@huggingface/transformers");t.allowRemoteModels=!0,t.allowLocalModels=!1,typeof window<"u"&&typeof caches<"u"&&(t.useWasmCache=!0);let n=p(),s=h();console.log(`yuktai: Transformers.js \u2014 device: ${n}, mobile: ${s}`),g=await e("feature-extraction","Xenova/all-MiniLM-L6-v2",{device:n,dtype:s?"q4":"fp32"}),m=await e("text2text-generation","Xenova/flan-t5-small",{device:n,dtype:s?"q4":"fp32"}),f=!0,l=!1,console.log("yuktai: Transformers.js models loaded \u2705")}catch(e){throw l=!1,console.error("yuktai: Transformers.js model load failed",e),e}}}function y(){return new Promise(e=>{let t=setTimeout(e,1500),n=new MutationObserver(()=>{clearTimeout(t),t=setTimeout(()=>{n.disconnect(),e()},500)});n.observe(document.body,{childList:!0,subtree:!0})})}function b(){let e=[],t=new Set,n=document.querySelectorAll("p, h1, h2, h3, h4, h5, h6, li, td, th, label, figcaption, blockquote, span, a, button, div");for(let i of n){if(i.closest("[data-yuktai-panel]")||i.querySelector("p, h1, h2, h3, h4, li, td, div"))continue;let a=i.innerText?.trim();if(!a||a.length<15||t.has(a))continue;t.add(a),e.push(a);let c=i.getAttribute("aria-label")?.trim();c&&c.length>8&&!t.has(c)&&(t.add(c),e.push(c))}let s=document.title?.trim();s&&!t.has(s)&&e.unshift(s);let r=document.querySelector('meta[name="description"]')?.getAttribute("content")?.trim();return r&&!t.has(r)&&e.unshift(r),e.join(" ").slice(0,8e3)}function v(e,t=150,n=30){if(typeof e!="string")try{e=String(e??"")}catch{return[]}let s=e.trim();if(!s)return[];let o=Math.min(n,Math.floor(t/2)),r=s.split(/\s+/),i=[],u=t-o;for(let a=0;a<r.length;a+=u){let c=r.slice(a,a+t).join(" ");c.trim().length>20&&i.push(c)}return i}function k(e,t){let n=0,s=0,o=0;for(let r=0;r<e.length;r++)n+=e[r]*t[r],s+=e[r]*e[r],o+=t[r]*t[r];return n/(Math.sqrt(s)*Math.sqrt(o)+1e-8)}async function d(e){let t=await g(e,{pooling:"mean",normalize:!0}),n=t?.data??t;return Array.from(n)}async function x(e,t,n=3){let s=await d(e),o=await Promise.all(t.map(async r=>{let i=await d(r),u=k(s,i);return{chunk:r,score:u}}));return o.sort((r,i)=>i.score-r.score),o.slice(0,n).map(r=>r.chunk)}async function M(e){if(!e.trim())return{success:!1,answer:"",error:"Please type a question."};try{await w(),await y();let t=b();if(!t||t.length<50)return{success:!1,answer:"",error:"Not enough content on this page."};let n=v(t);if(n.length===0)return{success:!1,answer:"",error:"Could not process page content."};let r=`Answer the question based on the context. Give a complete answer in 2-3 sentences.
2
+
3
+ Context: ${(await x(e,n,3)).join(" ").slice(0,1200)}
4
+
5
+ Question: ${e}
6
+
7
+ Answer:`,u=(await m(r,{max_new_tokens:120,min_new_tokens:10}))?.[0]?.generated_text?.trim()||"";return u?{success:!0,answer:u}:{success:!0,answer:"I could not find a specific answer on this page."}}catch(t){console.error("yuktai: Transformers RAG error",t);let n=t instanceof Error?t.message:"";return n.includes("Out of memory")||n.includes("memory")?{success:!1,answer:"",error:"Not enough device memory. Try on a device with more RAM or use desktop Chrome with Gemini Nano."}:{success:!1,answer:"",error:n||"Transformers.js error."}}}async function P(e,t){if(!e.trim()||!t.trim())return{success:!1,answer:"",error:"Nothing to answer from."};try{await w();let n=`Answer the question based on the context. Give a complete answer in 2-3 sentences.
8
+
9
+ Context: ${t.slice(0,1200)}
10
+
11
+ Question: ${e}
12
+
13
+ Answer:`,o=(await m(n,{max_new_tokens:120,min_new_tokens:10}))?.[0]?.generated_text?.trim()||"";return o?{success:!0,answer:o}:{success:!1,answer:"",error:"No answer generated."}}catch(n){return{success:!1,answer:"",error:n instanceof Error?n.message:"Transformers.js error."}}}function A(){try{return typeof WebAssembly<"u"&&typeof Worker<"u"}catch{return!1}}function C(){return f?"ready":l?"loading":"idle"}export{M as a,P as b,A as c,C as d};
package/dist/index.d.mts CHANGED
@@ -93,25 +93,6 @@ declare const wcagPlugin: {
93
93
  }[];
94
94
  };
95
95
 
96
- interface YuktAIWrapperProps {
97
- position?: "left" | "right";
98
- children: ReactNode;
99
- config?: Partial<A11yConfig>;
100
- showRag?: boolean;
101
- showAgent?: boolean;
102
- }
103
- declare function YuktAIWrapper({ position, children, config: configOverrides, showRag, showAgent, }: YuktAIWrapperProps): react__default.JSX.Element;
104
-
105
- declare const aiPlugin: {
106
- name: string;
107
- execute(input: string): Promise<string>;
108
- };
109
-
110
- declare const voicePlugin: {
111
- name: string;
112
- execute(input: string): Promise<string>;
113
- };
114
-
115
96
  /** Which engine sorts and searches the grid. */
116
97
  type GridEngine = "ts" | "rust" | "auto";
117
98
  /** `engine: "auto"` switches to Rust from this many rows. */
@@ -158,6 +139,38 @@ declare class GridCore {
158
139
  sortByKeys(keys: Float64Array, desc: boolean): Uint32Array | null;
159
140
  }
160
141
 
142
+ interface YuktAIWrapperProps {
143
+ position?: "left" | "right";
144
+ children: ReactNode;
145
+ config?: Partial<A11yConfig>;
146
+ showRag?: boolean;
147
+ showAgent?: boolean;
148
+ /** v5.3.9 — show ♿ Accessibility. Setting this (or showVibeCoder/launcher) switches every button to opt-in. */
149
+ showAccessibility?: boolean;
150
+ /** v5.3.9 — show ⚡ Vibe Coder (a developer tool; in legacy mode it follows showAgent). */
151
+ showVibeCoder?: boolean;
152
+ /** v5.3.9 — one ✨ button that shows/hides the others; remembers the choice; Esc closes. */
153
+ launcher?: boolean;
154
+ /**
155
+ * v5.3.9 — 💬 Ask page finds passages first with the page RAG ("rust",
156
+ * "auto" or "ts"), shows them at once, then lets Gemini Nano /
157
+ * Transformers.js write the answer from only those passages.
158
+ * Not set: 💬 works exactly as before.
159
+ */
160
+ engine?: GridEngine;
161
+ }
162
+ declare function YuktAIWrapper({ position, children, config: configOverrides, showRag, showAgent, showAccessibility, showVibeCoder, launcher, engine, }: YuktAIWrapperProps): react__default.JSX.Element;
163
+
164
+ declare const aiPlugin: {
165
+ name: string;
166
+ execute(input: string): Promise<string>;
167
+ };
168
+
169
+ declare const voicePlugin: {
170
+ name: string;
171
+ execute(input: string): Promise<string>;
172
+ };
173
+
161
174
  /** One search/sort run: what was searched, how long it took, which engine answered. */
162
175
  interface GridSearchTime {
163
176
  /** The search text ("" when only sorting) */
package/dist/index.d.ts CHANGED
@@ -93,25 +93,6 @@ declare const wcagPlugin: {
93
93
  }[];
94
94
  };
95
95
 
96
- interface YuktAIWrapperProps {
97
- position?: "left" | "right";
98
- children: ReactNode;
99
- config?: Partial<A11yConfig>;
100
- showRag?: boolean;
101
- showAgent?: boolean;
102
- }
103
- declare function YuktAIWrapper({ position, children, config: configOverrides, showRag, showAgent, }: YuktAIWrapperProps): react__default.JSX.Element;
104
-
105
- declare const aiPlugin: {
106
- name: string;
107
- execute(input: string): Promise<string>;
108
- };
109
-
110
- declare const voicePlugin: {
111
- name: string;
112
- execute(input: string): Promise<string>;
113
- };
114
-
115
96
  /** Which engine sorts and searches the grid. */
116
97
  type GridEngine = "ts" | "rust" | "auto";
117
98
  /** `engine: "auto"` switches to Rust from this many rows. */
@@ -158,6 +139,38 @@ declare class GridCore {
158
139
  sortByKeys(keys: Float64Array, desc: boolean): Uint32Array | null;
159
140
  }
160
141
 
142
+ interface YuktAIWrapperProps {
143
+ position?: "left" | "right";
144
+ children: ReactNode;
145
+ config?: Partial<A11yConfig>;
146
+ showRag?: boolean;
147
+ showAgent?: boolean;
148
+ /** v5.3.9 — show ♿ Accessibility. Setting this (or showVibeCoder/launcher) switches every button to opt-in. */
149
+ showAccessibility?: boolean;
150
+ /** v5.3.9 — show ⚡ Vibe Coder (a developer tool; in legacy mode it follows showAgent). */
151
+ showVibeCoder?: boolean;
152
+ /** v5.3.9 — one ✨ button that shows/hides the others; remembers the choice; Esc closes. */
153
+ launcher?: boolean;
154
+ /**
155
+ * v5.3.9 — 💬 Ask page finds passages first with the page RAG ("rust",
156
+ * "auto" or "ts"), shows them at once, then lets Gemini Nano /
157
+ * Transformers.js write the answer from only those passages.
158
+ * Not set: 💬 works exactly as before.
159
+ */
160
+ engine?: GridEngine;
161
+ }
162
+ declare function YuktAIWrapper({ position, children, config: configOverrides, showRag, showAgent, showAccessibility, showVibeCoder, launcher, engine, }: YuktAIWrapperProps): react__default.JSX.Element;
163
+
164
+ declare const aiPlugin: {
165
+ name: string;
166
+ execute(input: string): Promise<string>;
167
+ };
168
+
169
+ declare const voicePlugin: {
170
+ name: string;
171
+ execute(input: string): Promise<string>;
172
+ };
173
+
161
174
  /** One search/sort run: what was searched, how long it took, which engine answered. */
162
175
  interface GridSearchTime {
163
176
  /** The search text ("" when only sorting) */