research-agent-ui 0.1.293 β†’ 0.1.294

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.
Files changed (54) hide show
  1. package/README.md +42 -1
  2. package/dist/ArticleExtractPanel-25tRTnpl.cjs +2 -0
  3. package/dist/ArticleExtractPanel-D_y16YHG.js +389 -0
  4. package/dist/ChatConversationThread-BIJ8aNug.js +1428 -0
  5. package/dist/ChatConversationThread-BoNH0wvs.cjs +17 -0
  6. package/dist/DownloadsDialog-CCPj0hA8.cjs +1 -0
  7. package/dist/{DownloadsDialog-bTEDe2DI.js β†’ DownloadsDialog-DvS23Dh-.js} +8 -9
  8. package/dist/api/handlers/autocomplete.d.ts +6 -9
  9. package/dist/api/handlers/openai-transcription.d.ts +53 -0
  10. package/dist/api/handlers/transcript.d.ts +33 -0
  11. package/dist/api/index.d.ts +1 -0
  12. package/dist/api.cjs +10 -10
  13. package/dist/api.mjs +161 -208
  14. package/dist/app/index.d.ts +1 -1
  15. package/dist/app/useChunkErrorReload.d.ts +13 -2
  16. package/dist/components/ArticleReader/ArticleReaderMount.d.ts +5 -0
  17. package/dist/components/ArticleReader/ExtractPanelContext.d.ts +17 -1
  18. package/dist/components/ChatConversation/MessageBubble/AssistantMessageActions.d.ts +2 -1
  19. package/dist/components/ChatConversation/background-art.d.ts +3 -1
  20. package/dist/components/ChatConversation/background-cache.d.ts +41 -0
  21. package/dist/components/Footer.d.ts +5 -4
  22. package/dist/components/Footer.stories.d.ts +3 -3
  23. package/dist/config.cjs +1 -1
  24. package/dist/config.d.ts +9 -0
  25. package/dist/config.mjs +4 -3
  26. package/dist/dialog-7GDz0sWx.cjs +1 -0
  27. package/dist/dialog-Cufn2u50.js +80 -0
  28. package/dist/hooks/voice/useKokoroTTS.d.ts +2 -0
  29. package/dist/hooks/voice/useTextToVoice.d.ts +2 -1
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.ts +5 -1
  32. package/dist/index.mjs +4 -2
  33. package/dist/lib/file-sources.d.ts +8 -0
  34. package/dist/lib/google-drive-source.d.ts +100 -0
  35. package/dist/lib/kokoro.d.ts +14 -2
  36. package/dist/lib/qwksearch-connect.d.ts +79 -0
  37. package/dist/settings/index.d.ts +15 -0
  38. package/dist/src-GPr1ceY8.js +6165 -0
  39. package/dist/src-ebmQu_if.cjs +34 -0
  40. package/dist/tooltip-Bl3IdX-I.js +179 -0
  41. package/dist/tooltip-DmUO6ZUz.cjs +1 -0
  42. package/dist/types/fileSource.d.ts +9 -0
  43. package/dist/workspace/QwkSearchWorkspaceApp.d.ts +9 -2
  44. package/dist/workspace/ResearchWorkspaceView.d.ts +18 -4
  45. package/dist/workspace.cjs +1 -1
  46. package/dist/workspace.d.ts +1 -1
  47. package/dist/workspace.mjs +234 -91
  48. package/package.json +54 -51
  49. package/src/app/useChunkErrorReload.ts +92 -0
  50. package/src/settings/index.ts +23 -0
  51. package/src/settings/search.json +19 -1
  52. package/dist/DownloadsDialog-Ca6aRRcP.cjs +0 -1
  53. package/dist/src-HL9JjPc-.js +0 -8218
  54. package/dist/src-r4BsCIB4.cjs +0 -51
package/README.md CHANGED
@@ -20,6 +20,10 @@
20
20
  </p>
21
21
  <!-- template-git-repo:badges:end -->
22
22
 
23
+ <!-- skills:install:start -->
24
+ **πŸ€– Agent skill** β€” `npx skills@latest add https://github.com/OpenSourceAGI/qwksearch-research-agent --skill research-agent-ui` ([what it covers](../../skills/research-agent-ui/SKILL.md))
25
+ <!-- skills:install:end -->
26
+
23
27
  # research-agent-ui
24
28
 
25
29
 
@@ -70,7 +74,7 @@ import { QwkSearchProviders, openSpotlight } from 'research-agent-ui';
70
74
  </QwkSearchProviders>;
71
75
 
72
76
  // …and from a button somewhere in your own chrome:
73
- <button onClick={() => openSpotlight()}>Search everything</button>;
77
+ <button onClick={() => openSpotlight()}>Search </button>;
74
78
  ```
75
79
 
76
80
  Rows that open a chat or a settings section go through `onOpenChat` /
@@ -296,6 +300,43 @@ export const dynamic = "force-dynamic";
296
300
  export const POST = handleChatRequest;
297
301
  ```
298
302
 
303
+ ## Embedding on another site: "Sign in with QwkSearch"
304
+
305
+ A site that embeds the research agent has no QwkSearch session cookie, so by
306
+ default the embed runs as a guest. `createQwkSearchConnectAuthClient` makes the
307
+ login button go through QwkSearch's OAuth-style consent screen
308
+ (`https://qwksearch.com/connect`, authorization code + PKCE) instead. Your
309
+ server finishes the exchange at `POST https://qwksearch.com/api/connect/token`,
310
+ stores the returned API key against your own user, and hands it back to the
311
+ embed; every request the embed then makes to `qwksearch.com/api/` carries it as
312
+ `X-API-Key`, so chats, history and the user's QwkSearch plan work in the embed.
313
+
314
+ ```tsx
315
+ import { SessionProvider, createQwkSearchConnectAuthClient } from 'research-agent-ui';
316
+
317
+ const authClient = createQwkSearchConnectAuthClient({
318
+ sessionUrl: '/api/qwksearch/session', // GET -> { connected, user, apiKey, plan, upgradeUrl }
319
+ connectUrl: '/api/qwksearch/connect', // GET -> PKCE + redirect to qwksearch.com/connect
320
+ disconnectUrl: '/api/qwksearch/disconnect' // POST -> forget the stored key
321
+ });
322
+
323
+ <SessionProvider authClient={authClient} enableGoogleOneTap={false}>…</SessionProvider>
324
+ ```
325
+
326
+ The flow, from your server's side:
327
+
328
+ 1. Make a PKCE verifier, keep it (an httpOnly cookie), and redirect to
329
+ `https://qwksearch.com/connect?redirect_uri=<your callback>&state=<random>&code_challenge=<S256>&code_challenge_method=S256`.
330
+ 2. QwkSearch signs the user in if needed and asks them to allow the link, then
331
+ redirects to `redirect_uri?code=…&state=…` (or `?error=access_denied`).
332
+ 3. `POST /api/connect/token` with `{ code, redirect_uri, code_verifier }` β†’
333
+ `{ api_key, user, plan, upgrade_url }`.
334
+ 4. `GET /api/connect/me` with the key as `X-API-Key` refreshes the profile and
335
+ plan later β€” e.g. after the user follows `upgrade_url` to upgrade.
336
+
337
+ `redirect_uri` must be on QwkSearch's allowlist (debate-ai.com, ebate.app and localhost
338
+ by default; more via the `QWKSEARCH_CONNECT_ORIGINS` env var on qwksearch-web).
339
+
299
340
  ## Configuration
300
341
 
301
342
  `configureResearchAgentUI` overrides app-specific values (branding strings,
@@ -0,0 +1,2 @@
1
+ const e=require("./rolldown-runtime-BocRIvOZ.cjs"),t=require("./dialog-7GDz0sWx.cjs"),n=require("./tooltip-DmUO6ZUz.cjs"),r=require("./src-ebmQu_if.cjs"),i=require("./config.cjs");let a=require("react");a=e.t(a,1);let o=require("react/jsx-runtime"),s=require("sonner"),c=require("qwksearch-api-client");var l=a.forwardRef(({className:e,...n},r)=>(0,o.jsx)(`span`,{ref:r,className:t.o(`absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0`,e),style:{clip:`rect(0, 0, 0, 0)`,clipPath:`inset(50%)`},...n}));l.displayName=`VisuallyHidden`;function u(e){return e instanceof Error&&e.name===`AbortError`}async function ee(e,t){if(t.share)try{return await t.share(e),`shared`}catch(e){if(u(e))return`cancelled`}return await t.writeText(e.url),`copied`}var d=e=>{let{isOpen:u,url:d,searchText:te,suggestQuestions:f,panelWidth:ne,setPanelWidth:p,closePanel:re}=n.a(),{isAuthenticated:ie}=n.c(),{chatModelProvider:m}=n.u(),h=e.isOpen===void 0?u:e.isOpen,g=e.onClose||re,_=e.url||d,ae=e.searchText===void 0?te:e.searchText,[v,y]=(0,a.useState)(null),[b,oe]=(0,a.useState)([]),[se,x]=(0,a.useState)(!1),[ce,S]=(0,a.useState)(!1),[C,w]=(0,a.useState)(i.researchAgentUIConfig.defaultSummarizePrompt),[le,T]=(0,a.useState)(!1),[E,ue]=(0,a.useState)(!1),[D,de]=(0,a.useState)(!1),[O,fe]=(0,a.useState)(``),[k,pe]=(0,a.useState)(``),[me,A]=(0,a.useState)([]),[he,ge]=(0,a.useState)(``),[j,M]=(0,a.useState)(!1),[_e,N]=(0,a.useState)(!1),[P,F]=(0,a.useState)(!1),[I,ve]=(0,a.useState)(!1),[ye,be]=(0,a.useState)(ne),[L,R]=(0,a.useState)(!1),[xe,z]=(0,a.useState)(!1),[B,V]=(0,a.useState)(1),Se=(0,a.useRef)(null),H=(0,a.useRef)({}),U=.5,W=1.8,G=.1;(0,a.useEffect)(()=>{let e=parseFloat(localStorage.getItem(`articleFontScale`)||``);Number.isNaN(e)||V(Math.min(W,Math.max(U,e)))},[]);let K=e=>{let t=Math.min(W,Math.max(U,Math.round(e*100)/100));V(t);try{localStorage.setItem(`articleFontScale`,String(t))}catch(e){console.error(`Error storing article font scale:`,e)}},q=()=>K(B+G),J=()=>K(B-G),Y=()=>K(1);(0,a.useEffect)(()=>{let e=()=>{ve(window.innerWidth>=1024)};return e(),window.addEventListener(`resize`,e),()=>window.removeEventListener(`resize`,e)},[]),(0,a.useEffect)(()=>{let e=e=>{if(!L)return;let t=window.innerWidth-e.clientX,n=Math.max(400,Math.min(t,window.innerWidth-100));be(n),p(n)},t=()=>{R(!1)};return L&&(document.addEventListener(`mousemove`,e),document.addEventListener(`mouseup`,t),document.body.style.cursor=`ew-resize`,document.body.style.userSelect=`none`),()=>{document.removeEventListener(`mousemove`,e),document.removeEventListener(`mouseup`,t),document.body.style.cursor=``,document.body.style.userSelect=``}},[L,p]),(0,a.useEffect)(()=>{h&&_?(z(!1),Ce()):h||z(!1)},[h,_]);let Ce=async()=>{console.log(`[ArticleExtractPanel] Extracting URL:`,_);try{let e=new URL(_);if([/^https?:\/\/(www\.)?google\.[^/]+\/search/i,/^https?:\/\/(www\.)?bing\.com\/search/i,/^https?:\/\/(www\.)?duckduckgo\.com\/\?/i].some(e=>e.test(_))){s.toast.error(`Cannot extract articles from search result pages`),T(!1),z(!0);return}(e.pathname===`/`||e.pathname===``)&&(console.error(`[ArticleExtractPanel] URL is domain-only, missing article path:`,_),s.toast.error(`Invalid article URL - only domain provided`))}catch(e){console.error(`[ArticleExtractPanel] Invalid URL format:`,_,e)}T(!0);try{let{data:e}=await(0,c.getArticle)({query:{url:_}}),t=e?.article,n=t?.html?.replace(/<[^>]*>/g,``).trim()||``;t?.error||n.length<20?(document.dispatchEvent(new CustomEvent(`onInvokeChromeAPI`,{detail:{type:`extractURL`,url:_}})),await Promise.race([new Promise(e=>{let t=async n=>{window.removeEventListener(`onExtractionResult`,t);let{data:r}=await(0,c.getArticle)({query:{url:_}}),i=r?.article;i&&(y(i),i.followUpQuestions?.length>0?A(i.followUpQuestions):f&&(A([]),Z(`suggest-followups`,{source:i}))),e()};window.addEventListener(`onExtractionResult`,t)}),new Promise(e=>setTimeout(e,1e4))])):(y(t),t?.followUpQuestions&&t.followUpQuestions.length>0?A(t.followUpQuestions):f&&t&&(A([]),Z(`suggest-followups`,{source:t}))),ie&&we()}catch(e){console.error(`[ArticleExtractPanel] Error extracting URL:`,e),s.toast.error(`Failed to load article`)}finally{T(!1),z(!0)}},we=async()=>{try{let{data:e}=await(0,c.listFavorites)(),t=e?.favorites?.some(e=>e.url===_);M(!!t)}catch(e){console.error(`Error checking favorite status:`,e)}},X=async()=>{if(v){N(!0);try{j?(await(0,c.removeFavorite)({query:{url:_}}),M(!1),s.toast.info(`Removed from favorites`)):(await(0,c.addFavorite)({body:{url:v.url||_,title:v.title,cite:v.cite,author:v.author,author_cite:v.author_cite,date:v.date,source:v.source,word_count:v.word_count,html:v.html}}),M(!0),s.toast.success(`Added to favorites`))}catch(e){console.error(`Error toggling favorite:`,e),s.toast.error(`Failed to toggle favorite`)}finally{N(!1)}}},Te=()=>{if(m?.key&&m?.providerId)return m;try{let e=localStorage.getItem(`chatModelKey`)||``,t=localStorage.getItem(`chatModelProviderId`)||``;if(e&&t)return{key:e,providerId:t}}catch(e){console.error(`Error reading persisted chat model:`,e)}return m},Z=async(e,{source:t=v,question:n=C}={})=>{if(!t)return;let r=Te(),a=t.html?.replace(/<[^>]*>?/g,``).slice(0,i.researchAgentUIConfig.maxArticleLength),o=e===`question`,s=o?ue:de,l=o?pe:ge;s(!0),l(``);try{if(o){let e=[ae,n].filter(Boolean).join(`
2
+ `);if(!a||a.length===0)throw Error(`Article content is empty`);let{data:t,error:i}=await(0,c.articleQa)({body:{article:a,question:e,chatHistory:b.slice(-5),chatModel:r}});if(i){let e=i.details||i.error||i.message||`Article QA failed`;throw Error(e)}let o=t?.content||``;fe(o),oe(e=>[...e,{role:`user`,content:n,time:new Date().toISOString()},{role:`assistant`,content:o,time:new Date().toISOString()}]);try{await(0,c.updateArticle)({body:{url:_,question:n,answer:o}})}catch(e){console.error(`Error storing Q&A in cache:`,e)}}else{let e=parseInt(localStorage.getItem(`maxFollowupQuestions`)||`4`);if(!a||a.length===0)throw Error(`Article content is empty`);let{data:t,error:n}=await(0,c.articleFollowups)({body:{article:a,chatHistory:b.slice(-5),maxQuestions:e,chatModel:r}});if(n){let e=n.details||n.error||n.message||`Article followups failed`;throw Error(e)}let i=t?.extract||[];A(i);try{await(0,c.updateArticle)({body:{url:_,followUpQuestions:i}})}catch(e){console.error(`Error storing follow-up questions in cache:`,e)}}}catch(e){console.error(`Error calling language API:`,e),l(e instanceof Error||typeof e==`object`&&e&&`message`in e?e.message:`Failed to get AI response`)}finally{s(!1)}},Ee=e=>{w(e),Z(`question`,{question:e})},Q=async()=>{if(!v)return;let e=`${O}\n\n\n${v.cite||``}\n\n\n${v.html}`;try{await navigator.clipboard.writeText(e),x(!0),setTimeout(()=>x(!1),2e3)}catch(e){console.error(`Failed to copy to clipboard:`,e)}},De=async()=>{let e=v?.url||_;if(e)try{await ee({title:v?.title,text:v?.cite,url:e},{share:typeof navigator<`u`&&navigator.share?e=>navigator.share(e):void 0,writeText:e=>navigator.clipboard.writeText(e)})===`copied`&&(S(!0),setTimeout(()=>S(!1),2e3))}catch(e){console.error(`Failed to share article:`,e)}};H.current={ask:()=>Z(`question`),suggest:()=>Z(`suggest-followups`),copy:Q,highlight:()=>F(e=>!e),favorite:X,open:()=>{let e=v?.url||_;e&&window.open(e,`_blank`,`noopener,noreferrer`)},zoomIn:q,zoomOut:J,zoomReset:Y,close:g},(0,a.useEffect)(()=>{if(!h)return;let e=e=>/^[a-z]$/.test(e)?`Key${e.toUpperCase()}`:/^[0-9]$/.test(e)?`Digit${e}`:e===`-`?`Minus`:e===`=`?`Equal`:e,t={};Object.keys(r.D).forEach(n=>{let{alt:i,key:a}=r.D[n];i&&(t[e(a.toLowerCase())]=n)});let n=e=>{if(e.key===`Escape`){H.current.close?.();return}if(!e.altKey||e.ctrlKey||e.metaKey)return;let n=e.target;if(n&&(n.tagName===`INPUT`||n.tagName===`TEXTAREA`||n.isContentEditable))return;let r=t[e.code];if(!r)return;let i=H.current[r];i&&(e.preventDefault(),i())};return window.addEventListener(`keydown`,n),()=>window.removeEventListener(`keydown`,n)},[h]);let $=()=>(0,o.jsxs)(`div`,{className:`flex h-full flex-col bg-background shadow-xl`,children:[(0,o.jsx)(`div`,{className:`shrink-0 border-b border-border bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80`,children:(0,o.jsx)(r.O,{isLoadingAI:E,isLoadingFollowups:D,isLoadingFavorite:_e,isFavorited:j,isHighlightMode:P,articleUrl:v?.url||_,fontScale:B,onAskClick:()=>Z(`question`),onSuggestClick:()=>Z(`suggest-followups`),onCopyClick:Q,onShareClick:De,onFavoriteClick:X,onHighlightToggle:()=>F(!P),onZoomIn:q,onZoomOut:J,onZoomReset:Y,onClose:g})}),(0,o.jsx)(`div`,{className:`flex-1 overflow-y-auto`,children:(0,o.jsxs)(`div`,{className:`p-4 space-y-6`,children:[(0,o.jsxs)(`div`,{className:`space-y-4`,children:[se&&(0,o.jsx)(`div`,{className:`bg-blue-500 text-white text-sm font-medium px-3 py-2 rounded-md shadow-lg`,children:`Copied!`}),ce&&(0,o.jsx)(`div`,{className:`bg-blue-500 text-white text-sm font-medium px-3 py-2 rounded-md shadow-lg`,children:`Link copied!`}),(0,o.jsx)(r.E,{value:C,onChange:w,onSubmit:()=>Z(`question`)}),(0,o.jsx)(r.T,{questions:me,isLoading:D,error:he,onQuestionClick:Ee}),b.length>0&&(0,o.jsx)(`div`,{className:`space-y-4`,children:b.map((e,t)=>(0,o.jsx)(`div`,{className:`space-y-2`,children:e.role===`user`?(0,o.jsxs)(`div`,{className:`bg-primary/10 border border-primary/20 rounded-lg p-3`,children:[(0,o.jsx)(`div`,{className:`text-xs font-semibold text-primary mb-1`,children:`Your Question`}),(0,o.jsx)(`div`,{className:`text-sm text-foreground`,children:e.content})]}):(0,o.jsxs)(`div`,{className:`bg-muted rounded-lg shadow-md p-4`,children:[(0,o.jsx)(`div`,{className:`text-xs font-semibold text-muted-foreground mb-2`,children:`AI Response`}),(0,o.jsx)(`div`,{className:`text-sm`,dangerouslySetInnerHTML:{__html:e.content}})]})},t))}),(E||O&&b.length===0)&&(0,o.jsx)(r.w,{response:O,isLoading:E,error:k}),k&&!E&&(0,o.jsx)(`div`,{className:`bg-red-500 text-white p-2 rounded-md text-sm`,children:k})]}),v&&(0,o.jsx)(r.S,{article:v,isHighlightMode:P,fontScale:B})]})})]});return!h||!xe?null:I?(0,o.jsxs)(`div`,{className:`fixed top-0 right-0 h-screen z-40 transition-all duration-300`,style:{width:`${ye}px`},children:[(0,o.jsx)(`div`,{ref:Se,onMouseDown:()=>R(!0),className:`absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize hover:bg-primary bg-transparent transition-colors z-50`,style:{touchAction:`none`},children:(0,o.jsx)(`div`,{className:`absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-1 h-12 rounded-full bg-muted-foreground opacity-50 hover:opacity-100 transition-opacity`})}),$()]}):(0,o.jsx)(t.t,{open:h,onOpenChange:e=>!e&&g(),children:(0,o.jsxs)(t.n,{className:`pointer-events-auto fixed inset-0 !top-0 !left-0 !translate-x-0 !translate-y-0 h-full w-full max-w-none p-0 m-0 rounded-none border-0 overflow-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right`,hideCloseButton:!0,children:[(0,o.jsx)(l,{children:(0,o.jsx)(t.i,{children:`Article Details`})}),(0,o.jsx)(`div`,{className:`h-full overflow-y-auto`,children:$()})]})})};exports.default=d;
@@ -0,0 +1,389 @@
1
+ import { i as e, n as t, o as n, t as r } from "./dialog-Cufn2u50.js";
2
+ import { a as i, c as a, u as o } from "./tooltip-Bl3IdX-I.js";
3
+ import { D as s, E as ee, O as te, S as ne, T as re, w as ie } from "./src-GPr1ceY8.js";
4
+ import { researchAgentUIConfig as ae } from "./config.mjs";
5
+ import * as c from "react";
6
+ import { useEffect as l, useRef as u, useState as d } from "react";
7
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
8
+ import { toast as m } from "sonner";
9
+ import { addFavorite as oe, articleFollowups as se, articleQa as ce, getArticle as h, listFavorites as le, removeFavorite as ue, updateArticle as g } from "qwksearch-api-client";
10
+ // src/ui/visually-hidden.tsx
11
+ var _ = c.forwardRef(({ className: e, ...t }, r) => /* @__PURE__ */ f("span", {
12
+ ref: r,
13
+ className: n("absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0", e),
14
+ style: {
15
+ clip: "rect(0, 0, 0, 0)",
16
+ clipPath: "inset(50%)"
17
+ },
18
+ ...t
19
+ }));
20
+ _.displayName = "VisuallyHidden";
21
+ // src/lib/shareArticle.ts
22
+ function v(e) {
23
+ return e instanceof Error && e.name === "AbortError";
24
+ }
25
+ async function de(e, t) {
26
+ if (t.share) try {
27
+ return await t.share(e), "shared";
28
+ } catch (e) {
29
+ if (v(e)) return "cancelled";
30
+ }
31
+ return await t.writeText(e.url), "copied";
32
+ }
33
+ // src/components/ArticleReader/ArticleExtractPanel.tsx
34
+ var y = (n) => {
35
+ let { isOpen: c, url: v, searchText: y, suggestQuestions: fe, panelWidth: pe, setPanelWidth: b, closePanel: me } = i(), { isAuthenticated: he } = a(), { chatModelProvider: x } = o(), S = n.isOpen === void 0 ? c : n.isOpen, C = n.onClose || me, w = n.url || v, ge = n.searchText === void 0 ? y : n.searchText, [T, E] = d(null), [D, _e] = d([]), [ve, O] = d(!1), [ye, k] = d(!1), [A, j] = d(ae.defaultSummarizePrompt), [be, M] = d(!1), [N, xe] = d(!1), [P, Se] = d(!1), [F, Ce] = d(""), [I, we] = d(""), [Te, L] = d([]), [Ee, De] = d(""), [R, z] = d(!1), [Oe, B] = d(!1), [V, H] = d(!1), [ke, Ae] = d(!1), [je, Me] = d(pe), [U, W] = d(!1), [Ne, G] = d(!1), [K, q] = d(1), Pe = u(null), J = u({}), Y = .5, X = 1.8, Z = .1;
36
+ l(() => {
37
+ let e = parseFloat(localStorage.getItem("articleFontScale") || "");
38
+ Number.isNaN(e) || q(Math.min(X, Math.max(Y, e)));
39
+ }, []);
40
+ let Q = (e) => {
41
+ let t = Math.min(X, Math.max(Y, Math.round(e * 100) / 100));
42
+ q(t);
43
+ try {
44
+ localStorage.setItem("articleFontScale", String(t));
45
+ } catch (e) {
46
+ console.error("Error storing article font scale:", e);
47
+ }
48
+ }, Fe = () => Q(K + Z), Ie = () => Q(K - Z), Le = () => Q(1);
49
+ l(() => {
50
+ let e = () => {
51
+ Ae(window.innerWidth >= 1024);
52
+ };
53
+ return e(), window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
54
+ }, []), l(() => {
55
+ let e = (e) => {
56
+ if (!U) return;
57
+ let t = window.innerWidth - e.clientX, n = Math.max(400, Math.min(t, window.innerWidth - 100));
58
+ Me(n), b(n);
59
+ }, t = () => {
60
+ W(!1);
61
+ };
62
+ return U && (document.addEventListener("mousemove", e), document.addEventListener("mouseup", t), document.body.style.cursor = "ew-resize", document.body.style.userSelect = "none"), () => {
63
+ document.removeEventListener("mousemove", e), document.removeEventListener("mouseup", t), document.body.style.cursor = "", document.body.style.userSelect = "";
64
+ };
65
+ }, [U, b]), l(() => {
66
+ S && w ? (G(!1), Re()) : S || G(!1);
67
+ }, [S, w]);
68
+ let Re = async () => {
69
+ console.log("[ArticleExtractPanel] Extracting URL:", w);
70
+ try {
71
+ let e = new URL(w);
72
+ if ([
73
+ /^https?:\/\/(www\.)?google\.[^/]+\/search/i,
74
+ /^https?:\/\/(www\.)?bing\.com\/search/i,
75
+ /^https?:\/\/(www\.)?duckduckgo\.com\/\?/i
76
+ ].some((e) => e.test(w))) {
77
+ m.error("Cannot extract articles from search result pages"), M(!1), G(!0);
78
+ return;
79
+ }
80
+ (e.pathname === "/" || e.pathname === "") && (console.error("[ArticleExtractPanel] URL is domain-only, missing article path:", w), m.error("Invalid article URL - only domain provided"));
81
+ } catch (e) {
82
+ console.error("[ArticleExtractPanel] Invalid URL format:", w, e);
83
+ }
84
+ M(!0);
85
+ try {
86
+ let { data: e } = await h({ query: { url: w } }), t = e?.article, n = t?.html?.replace(/<[^>]*>/g, "").trim() || "";
87
+ t?.error || n.length < 20 ? (document.dispatchEvent(new CustomEvent("onInvokeChromeAPI", { detail: {
88
+ type: "extractURL",
89
+ url: w
90
+ } })), await Promise.race([new Promise((e) => {
91
+ let t = async (n) => {
92
+ window.removeEventListener("onExtractionResult", t);
93
+ let { data: r } = await h({ query: { url: w } }), i = r?.article;
94
+ i && (E(i), i.followUpQuestions?.length > 0 ? L(i.followUpQuestions) : fe && (L([]), $("suggest-followups", { source: i }))), e();
95
+ };
96
+ window.addEventListener("onExtractionResult", t);
97
+ }), new Promise((e) => setTimeout(e, 1e4))])) : (E(t), t?.followUpQuestions && t.followUpQuestions.length > 0 ? L(t.followUpQuestions) : fe && t && (L([]), $("suggest-followups", { source: t }))), he && ze();
98
+ } catch (e) {
99
+ console.error("[ArticleExtractPanel] Error extracting URL:", e), m.error("Failed to load article");
100
+ } finally {
101
+ M(!1), G(!0);
102
+ }
103
+ }, ze = async () => {
104
+ try {
105
+ let { data: e } = await le(), t = e?.favorites?.some((e) => e.url === w);
106
+ z(!!t);
107
+ } catch (e) {
108
+ console.error("Error checking favorite status:", e);
109
+ }
110
+ }, Be = async () => {
111
+ if (T) {
112
+ B(!0);
113
+ try {
114
+ R ? (await ue({ query: { url: w } }), z(!1), m.info("Removed from favorites")) : (await oe({ body: {
115
+ url: T.url || w,
116
+ title: T.title,
117
+ cite: T.cite,
118
+ author: T.author,
119
+ author_cite: T.author_cite,
120
+ date: T.date,
121
+ source: T.source,
122
+ word_count: T.word_count,
123
+ html: T.html
124
+ } }), z(!0), m.success("Added to favorites"));
125
+ } catch (e) {
126
+ console.error("Error toggling favorite:", e), m.error("Failed to toggle favorite");
127
+ } finally {
128
+ B(!1);
129
+ }
130
+ }
131
+ }, Ve = () => {
132
+ if (x?.key && x?.providerId) return x;
133
+ try {
134
+ let e = localStorage.getItem("chatModelKey") || "", t = localStorage.getItem("chatModelProviderId") || "";
135
+ if (e && t) return {
136
+ key: e,
137
+ providerId: t
138
+ };
139
+ } catch (e) {
140
+ console.error("Error reading persisted chat model:", e);
141
+ }
142
+ return x;
143
+ }, $ = async (e, { source: t = T, question: n = A } = {}) => {
144
+ if (!t) return;
145
+ let r = Ve(), i = t.html?.replace(/<[^>]*>?/g, "").slice(0, ae.maxArticleLength), a = e === "question", o = a ? xe : Se, s = a ? we : De;
146
+ o(!0), s("");
147
+ try {
148
+ if (a) {
149
+ let e = [ge, n].filter(Boolean).join("\n");
150
+ if (!i || i.length === 0) throw Error("Article content is empty");
151
+ let { data: t, error: a } = await ce({ body: {
152
+ article: i,
153
+ question: e,
154
+ chatHistory: D.slice(-5),
155
+ chatModel: r
156
+ } });
157
+ if (a) {
158
+ let e = a.details || a.error || a.message || "Article QA failed";
159
+ throw Error(e);
160
+ }
161
+ let o = t?.content || "";
162
+ Ce(o), _e((e) => [
163
+ ...e,
164
+ {
165
+ role: "user",
166
+ content: n,
167
+ time: (/* @__PURE__ */ new Date()).toISOString()
168
+ },
169
+ {
170
+ role: "assistant",
171
+ content: o,
172
+ time: (/* @__PURE__ */ new Date()).toISOString()
173
+ }
174
+ ]);
175
+ try {
176
+ await g({ body: {
177
+ url: w,
178
+ question: n,
179
+ answer: o
180
+ } });
181
+ } catch (e) {
182
+ console.error("Error storing Q&A in cache:", e);
183
+ }
184
+ } else {
185
+ let e = parseInt(localStorage.getItem("maxFollowupQuestions") || "4");
186
+ if (!i || i.length === 0) throw Error("Article content is empty");
187
+ let { data: t, error: n } = await se({ body: {
188
+ article: i,
189
+ chatHistory: D.slice(-5),
190
+ maxQuestions: e,
191
+ chatModel: r
192
+ } });
193
+ if (n) {
194
+ let e = n.details || n.error || n.message || "Article followups failed";
195
+ throw Error(e);
196
+ }
197
+ let a = t?.extract || [];
198
+ L(a);
199
+ try {
200
+ await g({ body: {
201
+ url: w,
202
+ followUpQuestions: a
203
+ } });
204
+ } catch (e) {
205
+ console.error("Error storing follow-up questions in cache:", e);
206
+ }
207
+ }
208
+ } catch (e) {
209
+ console.error("Error calling language API:", e), s(e instanceof Error || typeof e == "object" && e && "message" in e ? e.message : "Failed to get AI response");
210
+ } finally {
211
+ o(!1);
212
+ }
213
+ }, He = (e) => {
214
+ j(e), $("question", { question: e });
215
+ }, Ue = async () => {
216
+ if (!T) return;
217
+ let e = `${F}\n\n\n${T.cite || ""}\n\n\n${T.html}`;
218
+ try {
219
+ await navigator.clipboard.writeText(e), O(!0), setTimeout(() => O(!1), 2e3);
220
+ } catch (e) {
221
+ console.error("Failed to copy to clipboard:", e);
222
+ }
223
+ }, We = async () => {
224
+ let e = T?.url || w;
225
+ if (e) try {
226
+ await de({
227
+ title: T?.title,
228
+ text: T?.cite,
229
+ url: e
230
+ }, {
231
+ share: typeof navigator < "u" && navigator.share ? (e) => navigator.share(e) : void 0,
232
+ writeText: (e) => navigator.clipboard.writeText(e)
233
+ }) === "copied" && (k(!0), setTimeout(() => k(!1), 2e3));
234
+ } catch (e) {
235
+ console.error("Failed to share article:", e);
236
+ }
237
+ };
238
+ J.current = {
239
+ ask: () => $("question"),
240
+ suggest: () => $("suggest-followups"),
241
+ copy: Ue,
242
+ highlight: () => H((e) => !e),
243
+ favorite: Be,
244
+ open: () => {
245
+ let e = T?.url || w;
246
+ e && window.open(e, "_blank", "noopener,noreferrer");
247
+ },
248
+ zoomIn: Fe,
249
+ zoomOut: Ie,
250
+ zoomReset: Le,
251
+ close: C
252
+ }, l(() => {
253
+ if (!S) return;
254
+ let e = (e) => /^[a-z]$/.test(e) ? `Key${e.toUpperCase()}` : /^[0-9]$/.test(e) ? `Digit${e}` : e === "-" ? "Minus" : e === "=" ? "Equal" : e, t = {};
255
+ Object.keys(s).forEach((n) => {
256
+ let { alt: r, key: i } = s[n];
257
+ r && (t[e(i.toLowerCase())] = n);
258
+ });
259
+ let n = (e) => {
260
+ if (e.key === "Escape") {
261
+ J.current.close?.();
262
+ return;
263
+ }
264
+ if (!e.altKey || e.ctrlKey || e.metaKey) return;
265
+ let n = e.target;
266
+ if (n && (n.tagName === "INPUT" || n.tagName === "TEXTAREA" || n.isContentEditable)) return;
267
+ let r = t[e.code];
268
+ if (!r) return;
269
+ let i = J.current[r];
270
+ i && (e.preventDefault(), i());
271
+ };
272
+ return window.addEventListener("keydown", n), () => window.removeEventListener("keydown", n);
273
+ }, [S]);
274
+ let Ge = () => /* @__PURE__ */ p("div", {
275
+ className: "flex h-full flex-col bg-background shadow-xl",
276
+ children: [/* @__PURE__ */ f("div", {
277
+ className: "shrink-0 border-b border-border bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80",
278
+ children: /* @__PURE__ */ f(te, {
279
+ isLoadingAI: N,
280
+ isLoadingFollowups: P,
281
+ isLoadingFavorite: Oe,
282
+ isFavorited: R,
283
+ isHighlightMode: V,
284
+ articleUrl: T?.url || w,
285
+ fontScale: K,
286
+ onAskClick: () => $("question"),
287
+ onSuggestClick: () => $("suggest-followups"),
288
+ onCopyClick: Ue,
289
+ onShareClick: We,
290
+ onFavoriteClick: Be,
291
+ onHighlightToggle: () => H(!V),
292
+ onZoomIn: Fe,
293
+ onZoomOut: Ie,
294
+ onZoomReset: Le,
295
+ onClose: C
296
+ })
297
+ }), /* @__PURE__ */ f("div", {
298
+ className: "flex-1 overflow-y-auto",
299
+ children: /* @__PURE__ */ p("div", {
300
+ className: "p-4 space-y-6",
301
+ children: [/* @__PURE__ */ p("div", {
302
+ className: "space-y-4",
303
+ children: [
304
+ ve && /* @__PURE__ */ f("div", {
305
+ className: "bg-blue-500 text-white text-sm font-medium px-3 py-2 rounded-md shadow-lg",
306
+ children: "Copied!"
307
+ }),
308
+ ye && /* @__PURE__ */ f("div", {
309
+ className: "bg-blue-500 text-white text-sm font-medium px-3 py-2 rounded-md shadow-lg",
310
+ children: "Link copied!"
311
+ }),
312
+ /* @__PURE__ */ f(ee, {
313
+ value: A,
314
+ onChange: j,
315
+ onSubmit: () => $("question")
316
+ }),
317
+ /* @__PURE__ */ f(re, {
318
+ questions: Te,
319
+ isLoading: P,
320
+ error: Ee,
321
+ onQuestionClick: He
322
+ }),
323
+ D.length > 0 && /* @__PURE__ */ f("div", {
324
+ className: "space-y-4",
325
+ children: D.map((e, t) => /* @__PURE__ */ f("div", {
326
+ className: "space-y-2",
327
+ children: e.role === "user" ? /* @__PURE__ */ p("div", {
328
+ className: "bg-primary/10 border border-primary/20 rounded-lg p-3",
329
+ children: [/* @__PURE__ */ f("div", {
330
+ className: "text-xs font-semibold text-primary mb-1",
331
+ children: "Your Question"
332
+ }), /* @__PURE__ */ f("div", {
333
+ className: "text-sm text-foreground",
334
+ children: e.content
335
+ })]
336
+ }) : /* @__PURE__ */ p("div", {
337
+ className: "bg-muted rounded-lg shadow-md p-4",
338
+ children: [/* @__PURE__ */ f("div", {
339
+ className: "text-xs font-semibold text-muted-foreground mb-2",
340
+ children: "AI Response"
341
+ }), /* @__PURE__ */ f("div", {
342
+ className: "text-sm",
343
+ dangerouslySetInnerHTML: { __html: e.content }
344
+ })]
345
+ })
346
+ }, t))
347
+ }),
348
+ (N || F && D.length === 0) && /* @__PURE__ */ f(ie, {
349
+ response: F,
350
+ isLoading: N,
351
+ error: I
352
+ }),
353
+ I && !N && /* @__PURE__ */ f("div", {
354
+ className: "bg-red-500 text-white p-2 rounded-md text-sm",
355
+ children: I
356
+ })
357
+ ]
358
+ }), T && /* @__PURE__ */ f(ne, {
359
+ article: T,
360
+ isHighlightMode: V,
361
+ fontScale: K
362
+ })]
363
+ })
364
+ })]
365
+ });
366
+ return !S || !Ne ? null : ke ? /* @__PURE__ */ p("div", {
367
+ className: "fixed top-0 right-0 h-screen z-40 transition-all duration-300",
368
+ style: { width: `${je}px` },
369
+ children: [/* @__PURE__ */ f("div", {
370
+ ref: Pe,
371
+ onMouseDown: () => W(!0),
372
+ className: "absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize hover:bg-primary bg-transparent transition-colors z-50",
373
+ style: { touchAction: "none" },
374
+ children: /* @__PURE__ */ f("div", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-1 h-12 rounded-full bg-muted-foreground opacity-50 hover:opacity-100 transition-opacity" })
375
+ }), Ge()]
376
+ }) : /* @__PURE__ */ f(r, {
377
+ open: S,
378
+ onOpenChange: (e) => !e && C(),
379
+ children: /* @__PURE__ */ p(t, {
380
+ className: "pointer-events-auto fixed inset-0 !top-0 !left-0 !translate-x-0 !translate-y-0 h-full w-full max-w-none p-0 m-0 rounded-none border-0 overflow-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
381
+ hideCloseButton: !0,
382
+ children: [/* @__PURE__ */ f(_, { children: /* @__PURE__ */ f(e, { children: "Article Details" }) }), /* @__PURE__ */ f("div", {
383
+ className: "h-full overflow-y-auto",
384
+ children: Ge()
385
+ })]
386
+ })
387
+ });
388
+ };
389
+ export { y as default };