research-agent-ui 0.1.141 → 0.1.143

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
@@ -25,20 +25,81 @@
25
25
  <a href="https://codespaces.new/vtempest/qwksearch-research-agent">
26
26
  <img src="https://github.com/codespaces/badge.svg" width="150" height="20" />
27
27
  </a>
28
+ <a href="https://codecov.io/gh/OpenSourceAGI/qwksearch-research-agent"><img src="https://codecov.io/gh/OpenSourceAGI/qwksearch-research-agent/graph/badge.svg?component=package-research-agent-ui" alt="Coverage" /></a>
28
29
  </p>
29
30
 
30
31
  # research-agent-ui
31
32
 
32
33
 
33
- Chat research agent UI: conversation window, article reader, search config,
34
- file uploads, and chat history for QwkSearch-style apps. Includes the shadcn
35
- primitives and icons the components depend on, so it can be dropped into a
36
- Next.js app with a single dependency.
34
+ The QwkSearch app UI: conversation window, article reader, search config,
35
+ file uploads, chat history — plus the app shell (providers, app dock, cookie
36
+ banner, the research/docs view switch) that assembles them into a whole app.
37
+ Includes the shadcn primitives and icons the components depend on, so it can be
38
+ dropped into a Next.js app with a single dependency.
37
39
 
38
40
  ![img1](https://i.imgur.com/UxNJOKy.png)
39
41
 
42
+ ## Two entry points: with and without the editor
43
+
44
+ The package ships the same app twice, and the only difference is whether the
45
+ REASON document editor and its file sidebar come along:
46
+
47
+ | Import from | You get | Extra dependencies |
48
+ | --- | --- | --- |
49
+ | `research-agent-ui` | Chat, search, article reader, app shell | none |
50
+ | `research-agent-ui/workspace` | All of the above **plus** the REASON editor, document tree, and file sidebar | `react-reason-editor`, `react-reason-editor-sidebar` |
51
+
52
+ `research-agent-ui/workspace` re-exports everything the root entry does, so a
53
+ host that wants documents imports from that one path rather than mixing the
54
+ two. Going the other way, the root entry's import graph never reaches
55
+ `react-reason-editor` — the editor's (large) dependency tree stays out of a
56
+ chat-only consumer's bundle entirely. `test/entryBoundaries.test.ts` enforces
57
+ that in both directions.
58
+
59
+ The two editor packages are declared as **optional** peer dependencies:
60
+ installing `research-agent-ui` on its own is enough for the chat-only build,
61
+ and package managers will not warn about the missing peers.
62
+
40
63
  ## Usage
41
64
 
65
+ ### The whole app in one component
66
+
67
+ ```tsx
68
+ // Chat only — no editor, no sidebar.
69
+ import { QwkSearchApp } from 'research-agent-ui';
70
+
71
+ export default function Page() {
72
+ return (
73
+ <QwkSearchApp
74
+ authClient={myAuthClient}
75
+ config={{ appName: 'MyApp', footerLinks: myLinks }}
76
+ />
77
+ );
78
+ }
79
+ ```
80
+
81
+ ```tsx
82
+ // The same app, with documents.
83
+ import { QwkSearchWorkspaceApp } from 'research-agent-ui/workspace';
84
+
85
+ export default function Page() {
86
+ return (
87
+ <QwkSearchWorkspaceApp
88
+ authClient={myAuthClient}
89
+ config={{ appName: 'MyApp', footerLinks: myLinks }}
90
+ />
91
+ );
92
+ }
93
+ ```
94
+
95
+ `QwkSearchProviders` is the same shell without a page inside it, for hosts that
96
+ render their own routes within the app chrome. It accepts a `ChromeProvider` to
97
+ mount app-owned context (a settings modal, say) inside the stack, and
98
+ `showDock` / `showCookieConsent` / `showToaster` to opt out of individual
99
+ pieces.
100
+
101
+ ### Composing the pieces yourself
102
+
42
103
  ```tsx
43
104
  import {
44
105
  ChatProvider,
package/dist/api.cjs CHANGED
@@ -33,6 +33,6 @@ User question: ${i}
33
33
 
34
34
  Please provide a helpful answer based on the article content above.`,{text:d}=await(0,t.generateText)({model:c,messages:[{role:`system`,content:`You are a helpful AI assistant that answers questions about articles.
35
35
  Provide clear, concise, and accurate answers based on the article content provided.
36
- If the answer is not in the article, say so.`},{role:`user`,content:u}]});return Response.json({content:d,success:!0})}catch(e){return console.error(`Error in article Q&A:`,e),Response.json({error:`An error occurred while generating the answer`,details:e instanceof Error?e.message:String(e)},{status:500})}}}function _(e){let{chats:t,messages:n}=e.schema;return{GET:async i=>{try{let i=e.getDB(),a=await e.requireUserId(),o=await i.select({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files,messageCount:(0,r.count)(n.id),lastMessageAt:(0,r.max)(n.createdAt)}).from(t).leftJoin(n,(0,r.and)((0,r.eq)(n.chatId,t.id),(0,r.eq)(n.role,`user`))).where((0,r.eq)(t.userId,a)).groupBy(t.id,t.title,t.createdAt,t.focusMode,t.userId,t.files).orderBy(r.sql`${t.createdAt} DESC`);return Response.json({chats:o},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in getting chats: `,e),Response.json({message:`An error has occurred.`},{status:500}))}},DELETE:async i=>{try{let i=e.getDB(),a=await e.requireUserId(),o=await i.query.chats.findMany({where:(0,r.eq)(t.userId,a),columns:{id:!0}});if(o.length>0){let e=o.map(e=>e.id);await i.delete(n).where((0,r.inArray)(n.chatId,e)),await i.delete(t).where((0,r.eq)(t.userId,a))}return Response.json({message:`All chats deleted`},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in deleting all chats: `,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function v(e){let{chats:t,messages:n}=e.schema;return{GET:async(i,{params:a})=>{try{let i=e.getDB(),{id:o}=await a,s;try{s=await e.requireUserId()}catch{s=void 0}let c=await i.query.chats.findFirst({where:(0,r.eq)(t.id,o)});if(!c)return Response.json({message:`Chat not found`},{status:404});if(!(c.userId===s||c.isPublic))return Response.json({message:`Unauthorized - this chat is private`},{status:403});let l=await i.query.messages.findMany({where:(0,r.eq)(n.chatId,o)});return Response.json({chat:c,messages:l},{status:200})}catch(e){return console.error(`Error in getting chat by id: `,e),Response.json({message:`An error has occurred.`},{status:500})}},DELETE:async(i,{params:a})=>{try{let i=e.getDB(),{id:o}=await a,s=await e.requireUserId();return await i.query.chats.findFirst({where:(0,r.and)((0,r.eq)(t.id,o),(0,r.eq)(t.userId,s))})?(await i.delete(t).where((0,r.eq)(t.id,o)).execute(),await i.delete(n).where((0,r.eq)(n.chatId,o)).execute(),Response.json({message:`Chat deleted successfully`},{status:200})):Response.json({message:`Chat not found`},{status:404})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in deleting chat by id: `,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function y(e){let{chats:t,messages:n}=e.schema;return{GET:async i=>{try{let a=e.getDB(),o=await e.requireUserId(),{searchParams:s}=new URL(i.url),c=s.get(`q`);if(!c||c.trim().length===0)return Response.json({chats:[]},{status:200});let l=`%${c.trim()}%`,u=await a.select({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files}).from(t).where((0,r.and)((0,r.eq)(t.userId,o),(0,r.like)(t.title,l))).orderBy(r.sql`${t.createdAt} DESC`),d=await a.selectDistinct({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files}).from(n).innerJoin(t,(0,r.eq)(n.chatId,t.id)).where((0,r.and)((0,r.eq)(t.userId,o),(0,r.or)((0,r.like)(n.content,l)))).orderBy(r.sql`${t.createdAt} DESC`),f=[...u,...d],p=Array.from(new Map(f.map(e=>[e.id,e])).values()),m=p.map(e=>e.id),h=m.length?await a.select({chatId:n.chatId,count:r.sql`count(*)`}).from(n).where((0,r.and)(r.sql`${n.chatId} IN (${r.sql.join(m.map(e=>r.sql`${e}`),r.sql`, `)})`,(0,r.eq)(n.role,`user`))).groupBy(n.chatId):[],g=new Map(h.map(e=>[e.chatId,Number(e.count)])),_=p.map(e=>({...e,messageCount:g.get(e.id)||0}));return Response.json({chats:_},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error searching chats:`,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function b(e){let{chats:t}=e.schema;return{POST:async n=>{try{let i=e.getDB(),{chatId:a}=await n.json();if(!a)return Response.json({success:!1,error:`Chat ID is required`},{status:400});let o=await e.requireUserId(),s=await i.query.chats.findFirst({where:(0,r.eq)(t.id,a)});if(!s)return Response.json({success:!1,error:`Chat not found`},{status:404});if(s.userId!==o)return Response.json({success:!1,error:`Unauthorized`},{status:403});await i.update(t).set({isPublic:1}).where((0,r.eq)(t.id,a)).execute();let c=`${new URL(n.url).origin}/c/${a}`;return Response.json({success:!0,data:{chatId:a,shareUrl:c}})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({success:!1,error:`Authentication required`},{status:401}):(console.error(`Error in making chat public: `,e),Response.json({success:!1,error:`An error has occurred.`},{status:500}))}}}}function x(e){let{chats:t}=e.schema;return{POST:async a=>{try{let o=await a.json(),s=o.chatHistory.filter(e=>e.role===`user`||e.role===`assistant`).map(e=>({role:e.role,content:String(e.content??``)}));if(s.length===0)return Response.json({message:`No conversation content`},{status:400});let c=await new n.default().loadChatModel(o.chatModel.providerId,o.chatModel.key),l=await(0,i.default)({chat_history:s},c);if(!l)return Response.json({message:`Failed to generate title`},{status:500});let u=e.getUserId?await e.getUserId():null;if(u&&o.chatId)try{let n=e.getDB(),i=await n.query.chats.findFirst({where:(0,r.eq)(t.id,o.chatId)});i&&i.userId===u&&await n.update(t).set({title:l}).where((0,r.eq)(t.id,o.chatId)).execute()}catch(e){console.error(`Failed to persist chat title:`,e)}return Response.json({title:l},{status:200})}catch(e){return console.error(`Error generating chat title:`,e),Response.json({message:`Failed to generate title`},{status:500})}}}}var S=[`assistant`,`user`,`source`,`suggestion`];function C(e){let t=[],n=e;for(let e=0;n!=null&&e<5;e++)t.push(n instanceof Error?n.message:String(n)),n=n instanceof Error?n.cause:void 0;return t.join(` <- caused by: `)}function w(e){return{POST:async t=>{try{let n=e.getDB(),r=await e.requireUserId(),{chatId:i,messageId:a,role:o,suggestions:s,content:c,sources:l}=await t.json();return!i||!a||!o?Response.json({message:`Missing required fields`},{status:400}):S.includes(o)?(await n.insert(e.messagesSchema).values({chatId:i,userId:r,messageId:a,role:o,content:c||``,suggestions:s||[],sources:l||[],createdAt:new Date().toISOString()}),Response.json({message:`Message saved successfully`},{status:200})):Response.json({message:`Invalid role: ${o}`},{status:400})}catch(e){return console.error(`Error saving message:`,C(e)),Response.json({message:`Failed to save message`},{status:500})}}}}function T(e){return{GET:async t=>{try{let r=new n.default,i=new URL(t.url).searchParams.get(`guest`),a=i===`true`;i===null&&(a=!await e.getSession());let o=(await r.getActiveProviders(a)).filter(e=>!e.chatModels.some(e=>e.key===`error`));return o.length===0?Response.json({providers:[],error:a?`No guest-safe AI providers available. Please sign in for more options.`:`No AI providers configured. Please add OPENROUTER_API_KEY to your environment variables or configure your own API keys in Settings.`},{status:200}):Response.json({providers:o,isGuest:a},{status:200})}catch(e){return console.error(`An error occurred while fetching providers`,e),Response.json({message:`An error has occurred.`},{status:500})}},POST:async e=>{try{let{type:t,config:r}=await e.json();if(!t||!r)return Response.json({message:`Missing required fields.`},{status:400});let i=await new n.default().addProvider(t,r);return Response.json({provider:i},{status:200})}catch(e){return console.error(`An error occurred while creating provider`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function E(){return{DELETE:async(e,{params:t})=>{try{let{id:e}=await t;return e?(await new n.default().removeProvider(e),Response.json({message:`Provider deleted successfully.`},{status:200})):Response.json({message:`Provider ID is required.`},{status:400})}catch(e){return console.error(`An error occurred while deleting provider`,e.message),Response.json({message:`An error has occurred.`},{status:500})}},PATCH:async(e,{params:t})=>{try{let{config:r}=await e.json(),{id:i}=await t;if(!i||!r)return Response.json({message:`Missing required fields.`},{status:400});let a=await new n.default().updateProvider(i,r);return Response.json({provider:a},{status:200})}catch(e){return console.error(`An error occurred while updating provider`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}function D(){return{POST:async(e,{params:t})=>{try{let{id:r}=await t,i=await e.json();return!i.key||!i.name?Response.json({message:`Key and name must be provided`},{status:400}):(await new n.default().addProviderModel(r,i.type,i),Response.json({message:`Model added successfully`},{status:200}))}catch(e){return console.error(`An error occurred while adding provider model`,e),Response.json({message:`An error has occurred.`},{status:500})}},DELETE:async(e,{params:t})=>{try{let{id:r}=await t,i=await e.json();return i.key?(await new n.default().removeProviderModel(r,i.type,i.key),Response.json({message:`Model added successfully`},{status:200})):Response.json({message:`Key and name must be provided`},{status:400})}catch(e){return console.error(`An error occurred while deleting provider model`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function O(e){return{GET:async t=>{try{let t=e.getConfiguredMCPServers();return Response.json({servers:t},{status:200})}catch(e){return console.error(`An error occurred while fetching MCP servers`,e),Response.json({message:`An error has occurred.`},{status:500})}},POST:async t=>{try{let{type:n,name:r,config:i}=await t.json();if(!n||!r||!i)return Response.json({message:`Missing required fields.`},{status:400});let a=e.configManager.addMCPServer(n,r,i);return Response.json({server:a},{status:200})}catch(e){return console.error(`An error occurred while creating MCP server`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function k(e){return{DELETE:async(t,{params:n})=>{try{let{id:t}=await n;return t?(e.configManager.removeMCPServer(t),Response.json({message:`MCP server deleted successfully.`},{status:200})):Response.json({message:`MCP Server ID is required.`},{status:400})}catch(e){return console.error(`An error occurred while deleting MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}},PATCH:async(t,{params:n})=>{try{let{name:r,config:i}=await t.json(),{id:a}=await n;if(!a||!r||!i)return Response.json({message:`Missing required fields.`},{status:400});let o=await e.configManager.updateMCPServer(a,r,i);return Response.json({server:o},{status:200})}catch(e){return console.error(`An error occurred while updating MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}function A(e){return{POST:async(t,{params:n})=>{try{let{enabled:r}=await t.json(),{id:i}=await n;if(!i||typeof r!=`boolean`)return Response.json({message:`Missing required fields.`},{status:400});let a=e.configManager.toggleMCPServer(i,r);return Response.json({server:a},{status:200})}catch(e){return console.error(`An error occurred while toggling MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}var j={tech:{query:[`technology news`,`latest tech`,`AI`,`science and innovation`],links:[`techcrunch.com`,`wired.com`,`theverge.com`]},finance:{query:[`finance news`,`economy`,`stock market`,`investing`],links:[`bloomberg.com`,`cnbc.com`,`marketwatch.com`]},art:{query:[`art news`,`culture`,`modern art`,`cultural events`],links:[`artnews.com`,`hyperallergic.com`,`theartnewspaper.com`]},sports:{query:[`sports news`,`latest sports`,`cricket football tennis`],links:[`espn.com`,`bbc.com/sport`,`skysports.com`]},entertainment:{query:[`entertainment news`,`movies`,`TV shows`,`celebrities`],links:[`hollywoodreporter.com`,`variety.com`,`deadline.com`]}};function M(e={}){let t=e.searxngDomain??`https://search.qwksearch.com`;return{GET:async e=>{let n=new URL(e.url).searchParams,r=n.get(`q`),i=n.get(`cat`)||`general`,o=parseInt(n.get(`page`)||`1`,10),s=n.get(`lang`)||`en-US`,c=n.get(`safesearch`)===`true`,l=n.get(`recency`)||void 0,u=n.get(`publicInstances`)===`true`;if(!r)return Response.json({error:`Query parameter is required`},{status:400});let d=Date.now();try{let e=await(0,a.searchWeb)(r,{category:i,recency:l,safesearch:c,maxRetries:6,privateSearxng:!u&&t,proxy:``,lang:s,page:o});e&&(Array.isArray(e)?e.length>0:e.results&&e.results.length>0)||(e=await(0,a.searchWeb)(r,{category:i,recency:l,safesearch:c,maxRetries:6,privateSearxng:!1,proxy:``,lang:s,page:o}));let n=Date.now()-d;return e?Array.isArray(e)?Response.json({results:e,elapsedTime:n}):Response.json({...e,elapsedTime:n}):Response.json({results:[],suggestions:[],elapsedTime:n})}catch(e){return console.error(`Search error:`,e),Response.json({error:`Search failed`,results:[]},{status:500})}}}}function N(){return{GET:async e=>{try{let t=new URL(e.url).searchParams,n=t.get(`mode`)||`normal`,r=j[t.get(`topic`)||`tech`],i=[];if(n===`normal`){let e=new Set;i=(await Promise.all(r.links.flatMap(e=>r.query.map(async t=>(await(0,a.searchSearxng)(`site:${e} ${t}`,{engines:[`bing news`],pageno:1,language:`en`})).results)))).flat().filter(t=>{let n=t.url?.toLowerCase().trim();return!e.has(n)&&(e.add(n),!0)}).sort(()=>Math.random()-.5)}else i=(await(0,a.searchSearxng)(`site:${r.links[Math.floor(Math.random()*r.links.length)]} ${r.query[Math.floor(Math.random()*r.query.length)]}`,{engines:[`bing news`],pageno:1,language:`en`})).results;return Response.json({blogs:i},{status:200})}catch(e){return console.error(`An error occurred in discover route: ${e}`),Response.json({message:`An error has occurred`},{status:500})}}}}var P=[`google`,`duckduckgo`,`wikipedia`],F=3,I=3,L=Object.entries(l.default).map(([e,t])=>({domain:e,name:typeof t?.[0]==`string`?t[0]:``,rank:typeof t?.[1]==`number`?t[1]:2**53-1})),R=null;function z(){return R||=new s.default(L,{keys:[{name:`name`,weight:.6},{name:`domain`,weight:.4}],threshold:.1,ignoreLocation:!0,includeScore:!0,minMatchCharLength:3}),R}function B(e,t,n){return{domain:e,name:t,favicon:`https://www.google.com/s2/favicons?domain=${encodeURIComponent(e)}&sz=64`,rank:n}}function V(e){let t=(0,c.parse)(e);return t.domain&&t.isIcann?t.hostname:null}function H(e){let t=e.split(/\s+/).filter(Boolean),n=t[t.length-1]||``;if(n.length<3)return[];let r=z().search(n,{limit:12}).sort((e,t)=>Math.round((e.score??1)*10)-Math.round((t.score??1)*10)||e.item.rank-t.item.rank).map(({item:e})=>B(e.domain,e.name,e.rank)),i=V(n);return i&&!r.some(e=>e.domain===i)&&r.unshift(B(i,``,2**53-1)),r.slice(0,I)}async function U(e,t,n){let r=await(0,o.searchAutocompleteMulti)(e,t,n);if(r.length>0)return r;let i=t.split(/\s+/).filter(Boolean),a=Math.min(F,i.length-1);for(let r=a;r>=1;r--){let a=i.slice(-r).join(` `),s=i.slice(0,-r).join(` `),c=await(0,o.searchAutocompleteMulti)(e,a,n);if(c.length>0){let e=new Set;for(let n of c){let r=s?`${s} ${n}`:n;r.toLowerCase()!==t.toLowerCase()&&e.add(r)}if(e.size>0)return Array.from(e)}}return[]}function W(){return{GET:async e=>{let{searchParams:t}=new URL(e.url),n=t.get(`q`)?.trim(),r=t.get(`locale`)||`en-US`,i=t.get(`backends`),a=parseInt(t.get(`limit`)||`8`,10);if(!n)return Response.json({suggestions:[],domains:[]});let o=i?i.split(`,`).map(e=>e.trim()).filter(Boolean):P;try{let e=await U(o,n,r),t=H(n);return Response.json({suggestions:e.slice(0,a),domains:t})}catch(e){return console.error(`Autocomplete error:`,e),Response.json({suggestions:[],domains:[]},{status:500})}}}}function G(){return{POST:async e=>{let t=await e.json(),r=t.chatHistory.filter(e=>e.role===`user`||e.role===`assistant`).map(e=>({role:e.role,content:String(e.content??``)})),i=await new n.default().loadChatModel(t.chatModel.providerId,t.chatModel.key),a=(await(0,u.default)({chat_history:r,maxQuestions:t.maxQuestions},i)).flatMap(e=>(e.match(/\?/g)||[]).length>1?e.split(/\?/).map(e=>e.trim()).filter(e=>e.length>0).map(e=>e+`?`):[e]);return Response.json({suggestions:a},{status:200})}}}function K(e){return{POST:async t=>{let n;try{n=await t.json()}catch{return Response.json({error:`Invalid JSON input`},{status:500})}n.ip=t.headers?.get?.(`x-forwarded-for`)||t.headers?.get?.(`x-real-ip`)||`unknown`;let i=e.getDB(),a=await e.getUserId(),o=null;return a&&(o=await i.query.user.findFirst({where:(0,r.eq)(e.userSchema.id,a)})),o&&(n.apiKey=o.settings?.providerApiKeys?.find(e=>e.provider==n.provider)?.key),n.apiKey||(n.apiKey=n.provider==`groq`&&e.getEnv(`GROQ_API_KEY`)),n.apiKey?Response.json({error:`Language generation endpoint is deprecated. Please use /api/agent/chat instead.`},{status:501}):Response.json({error:`API key is required`},{status:500})}}}function q(e){return{POST:async t=>{try{let{text:n,prompt:r}=await t.json();if(!n||typeof n!=`string`)return Response.json({error:`Text is required and must be a string`},{status:400});let i=e.getEnv(`GROQ_API_KEY`);if(!i)return console.error(`GROQ_API_KEY is not configured`),Response.json({error:`AI service is not configured. Please contact the administrator.`},{status:500});let a=e.createGroq({apiKey:i})(`llama-3.3-70b-versatile`),o=r||`Rewrite the following text to improve clarity, grammar, and style while maintaining the original meaning and tone. Only return the rewritten text without any explanation or additional commentary:
36
+ If the answer is not in the article, say so.`},{role:`user`,content:u}]});return Response.json({content:d,success:!0})}catch(e){return console.error(`Error in article Q&A:`,e),Response.json({error:`An error occurred while generating the answer`,details:e instanceof Error?e.message:String(e)},{status:500})}}}function _(e){let{chats:t,messages:n}=e.schema;return{GET:async i=>{try{let i=e.getDB(),a=await e.requireUserId(),o=await i.select({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files,messageCount:(0,r.count)(n.id),lastMessageAt:(0,r.max)(n.createdAt)}).from(t).leftJoin(n,(0,r.and)((0,r.eq)(n.chatId,t.id),(0,r.eq)(n.role,`user`))).where((0,r.eq)(t.userId,a)).groupBy(t.id,t.title,t.createdAt,t.focusMode,t.userId,t.files).orderBy(r.sql`${t.createdAt} DESC`);return Response.json({chats:o},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in getting chats: `,e),Response.json({message:`An error has occurred.`},{status:500}))}},DELETE:async i=>{try{let i=e.getDB(),a=await e.requireUserId(),o=await i.query.chats.findMany({where:(0,r.eq)(t.userId,a),columns:{id:!0}});if(o.length>0){let e=o.map(e=>e.id);await i.delete(n).where((0,r.inArray)(n.chatId,e)),await i.delete(t).where((0,r.eq)(t.userId,a))}return Response.json({message:`All chats deleted`},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in deleting all chats: `,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function v(e){let{chats:t,messages:n}=e.schema;return{GET:async(i,{params:a})=>{try{let i=e.getDB(),{id:o}=await a,s;try{s=await e.requireUserId()}catch{s=void 0}let c=await i.query.chats.findFirst({where:(0,r.eq)(t.id,o)});if(!c)return Response.json({message:`Chat not found`},{status:404});if(!(c.userId===s||c.isPublic))return Response.json({message:`Unauthorized - this chat is private`},{status:403});let l=await i.query.messages.findMany({where:(0,r.eq)(n.chatId,o)});return Response.json({chat:c,messages:l},{status:200})}catch(e){return console.error(`Error in getting chat by id: `,e),Response.json({message:`An error has occurred.`},{status:500})}},DELETE:async(i,{params:a})=>{try{let i=e.getDB(),{id:o}=await a,s=await e.requireUserId();return await i.query.chats.findFirst({where:(0,r.and)((0,r.eq)(t.id,o),(0,r.eq)(t.userId,s))})?(await i.delete(t).where((0,r.eq)(t.id,o)).execute(),await i.delete(n).where((0,r.eq)(n.chatId,o)).execute(),Response.json({message:`Chat deleted successfully`},{status:200})):Response.json({message:`Chat not found`},{status:404})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error in deleting chat by id: `,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function y(e){let{chats:t,messages:n}=e.schema;return{GET:async i=>{try{let a=e.getDB(),o=await e.requireUserId(),{searchParams:s}=new URL(i.url),c=s.get(`q`);if(!c||c.trim().length===0)return Response.json({chats:[]},{status:200});let l=`%${c.trim()}%`,u=await a.select({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files}).from(t).where((0,r.and)((0,r.eq)(t.userId,o),(0,r.like)(t.title,l))).orderBy(r.sql`${t.createdAt} DESC`),d=await a.selectDistinct({id:t.id,title:t.title,createdAt:t.createdAt,focusMode:t.focusMode,userId:t.userId,files:t.files}).from(n).innerJoin(t,(0,r.eq)(n.chatId,t.id)).where((0,r.and)((0,r.eq)(t.userId,o),(0,r.or)((0,r.like)(n.content,l)))).orderBy(r.sql`${t.createdAt} DESC`),f=[...u,...d],p=Array.from(new Map(f.map(e=>[e.id,e])).values()),m=p.map(e=>e.id),h=m.length?await a.select({chatId:n.chatId,count:r.sql`count(*)`}).from(n).where((0,r.and)(r.sql`${n.chatId} IN (${r.sql.join(m.map(e=>r.sql`${e}`),r.sql`, `)})`,(0,r.eq)(n.role,`user`))).groupBy(n.chatId):[],g=new Map(h.map(e=>[e.chatId,Number(e.count)])),_=p.map(e=>({...e,messageCount:g.get(e.id)||0}));return Response.json({chats:_},{status:200})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({message:`Authentication required`},{status:401}):(console.error(`Error searching chats:`,e),Response.json({message:`An error has occurred.`},{status:500}))}}}}function b(e){let{chats:t}=e.schema;return{POST:async n=>{try{let i=e.getDB(),{chatId:a}=await n.json();if(!a)return Response.json({success:!1,error:`Chat ID is required`},{status:400});let o=await e.requireUserId(),s=await i.query.chats.findFirst({where:(0,r.eq)(t.id,a)});if(!s)return Response.json({success:!1,error:`Chat not found`},{status:404});if(s.userId!==o)return Response.json({success:!1,error:`Unauthorized`},{status:403});await i.update(t).set({isPublic:1}).where((0,r.eq)(t.id,a)).execute();let c=`${new URL(n.url).origin}/c/${a}`;return Response.json({success:!0,data:{chatId:a,shareUrl:c}})}catch(e){return e instanceof Error&&e.message===`Unauthorized`?Response.json({success:!1,error:`Authentication required`},{status:401}):(console.error(`Error in making chat public: `,e),Response.json({success:!1,error:`An error has occurred.`},{status:500}))}}}}function x(e){let{chats:t}=e.schema;return{POST:async a=>{try{let o=await a.json(),s=o.chatHistory.filter(e=>e.role===`user`||e.role===`assistant`).map(e=>({role:e.role,content:String(e.content??``)}));if(s.length===0)return Response.json({message:`No conversation content`},{status:400});let c=await new n.default().loadChatModel(o.chatModel.providerId,o.chatModel.key),l=await(0,i.default)({chat_history:s},c);if(!l)return Response.json({message:`Failed to generate title`},{status:500});let u=e.getUserId?await e.getUserId():null;if(u&&o.chatId)try{let n=e.getDB(),i=await n.query.chats.findFirst({where:(0,r.eq)(t.id,o.chatId)});i&&i.userId===u&&await n.update(t).set({title:l}).where((0,r.eq)(t.id,o.chatId)).execute()}catch(e){console.error(`Failed to persist chat title:`,e)}return Response.json({title:l},{status:200})}catch(e){return console.error(`Error generating chat title:`,e),Response.json({message:`Failed to generate title`},{status:500})}}}}var S=[`assistant`,`user`,`source`,`suggestion`];function C(e){let t=[],n=e;for(let e=0;n!=null&&e<5;e++)t.push(n instanceof Error?n.message:String(n)),n=n instanceof Error?n.cause:void 0;return t.join(` <- caused by: `)}function w(e){return{POST:async t=>{try{let n=e.getDB(),r=await e.requireUserId(),{chatId:i,messageId:a,role:o,suggestions:s,content:c,sources:l}=await t.json();return!i||!a||!o?Response.json({message:`Missing required fields`},{status:400}):S.includes(o)?(await n.insert(e.messagesSchema).values({chatId:i,userId:r,messageId:a,role:o,content:c||``,suggestions:s||[],sources:l||[],createdAt:new Date().toISOString()}),Response.json({message:`Message saved successfully`},{status:200})):Response.json({message:`Invalid role: ${o}`},{status:400})}catch(e){return console.error(`Error saving message:`,C(e)),Response.json({message:`Failed to save message`},{status:500})}}}}function T(e){return{GET:async t=>{try{let r=new n.default,i=new URL(t.url).searchParams.get(`guest`),a=i===`true`;i===null&&(a=!await e.getSession());let o=(await r.getActiveProviders(a)).filter(e=>!e.chatModels.some(e=>e.key===`error`));return o.length===0?Response.json({providers:[],error:a?`No guest-safe AI providers available. Please sign in for more options.`:`No AI providers configured. Please add OPENROUTER_API_KEY to your environment variables or configure your own API keys in Settings.`},{status:200}):Response.json({providers:o,isGuest:a},{status:200})}catch(e){return console.error(`An error occurred while fetching providers`,e),Response.json({message:`An error has occurred.`},{status:500})}},POST:async e=>{try{let{type:t,config:r}=await e.json();if(!t||!r)return Response.json({message:`Missing required fields.`},{status:400});let i=await new n.default().addProvider(t,r);return Response.json({provider:i},{status:200})}catch(e){return console.error(`An error occurred while creating provider`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function E(){return{DELETE:async(e,{params:t})=>{try{let{id:e}=await t;return e?(await new n.default().removeProvider(e),Response.json({message:`Provider deleted successfully.`},{status:200})):Response.json({message:`Provider ID is required.`},{status:400})}catch(e){return console.error(`An error occurred while deleting provider`,e.message),Response.json({message:`An error has occurred.`},{status:500})}},PATCH:async(e,{params:t})=>{try{let{config:r}=await e.json(),{id:i}=await t;if(!i||!r)return Response.json({message:`Missing required fields.`},{status:400});let a=await new n.default().updateProvider(i,r);return Response.json({provider:a},{status:200})}catch(e){return console.error(`An error occurred while updating provider`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}function D(){return{POST:async(e,{params:t})=>{try{let{id:r}=await t,i=await e.json();return!i.key||!i.name?Response.json({message:`Key and name must be provided`},{status:400}):(await new n.default().addProviderModel(r,i.type,i),Response.json({message:`Model added successfully`},{status:200}))}catch(e){return console.error(`An error occurred while adding provider model`,e),Response.json({message:`An error has occurred.`},{status:500})}},DELETE:async(e,{params:t})=>{try{let{id:r}=await t,i=await e.json();return i.key?(await new n.default().removeProviderModel(r,i.type,i.key),Response.json({message:`Model added successfully`},{status:200})):Response.json({message:`Key and name must be provided`},{status:400})}catch(e){return console.error(`An error occurred while deleting provider model`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function O(e){return{GET:async t=>{try{let t=e.getConfiguredMCPServers();return Response.json({servers:t},{status:200})}catch(e){return console.error(`An error occurred while fetching MCP servers`,e),Response.json({message:`An error has occurred.`},{status:500})}},POST:async t=>{try{let{type:n,name:r,config:i}=await t.json();if(!n||!r||!i)return Response.json({message:`Missing required fields.`},{status:400});let a=e.configManager.addMCPServer(n,r,i);return Response.json({server:a},{status:200})}catch(e){return console.error(`An error occurred while creating MCP server`,e),Response.json({message:`An error has occurred.`},{status:500})}}}}function k(e){return{DELETE:async(t,{params:n})=>{try{let{id:t}=await n;return t?(e.configManager.removeMCPServer(t),Response.json({message:`MCP server deleted successfully.`},{status:200})):Response.json({message:`MCP Server ID is required.`},{status:400})}catch(e){return console.error(`An error occurred while deleting MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}},PATCH:async(t,{params:n})=>{try{let{name:r,config:i}=await t.json(),{id:a}=await n;if(!a||!r||!i)return Response.json({message:`Missing required fields.`},{status:400});let o=await e.configManager.updateMCPServer(a,r,i);return Response.json({server:o},{status:200})}catch(e){return console.error(`An error occurred while updating MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}function A(e){return{POST:async(t,{params:n})=>{try{let{enabled:r}=await t.json(),{id:i}=await n;if(!i||typeof r!=`boolean`)return Response.json({message:`Missing required fields.`},{status:400});let a=e.configManager.toggleMCPServer(i,r);return Response.json({server:a},{status:200})}catch(e){return console.error(`An error occurred while toggling MCP server`,e.message),Response.json({message:`An error has occurred.`},{status:500})}}}}var j={tech:{query:[`technology news`,`latest tech`,`AI`,`science and innovation`],links:[`techcrunch.com`,`wired.com`,`theverge.com`]},finance:{query:[`finance news`,`economy`,`stock market`,`investing`],links:[`bloomberg.com`,`cnbc.com`,`marketwatch.com`]},art:{query:[`art news`,`culture`,`modern art`,`cultural events`],links:[`artnews.com`,`hyperallergic.com`,`theartnewspaper.com`]},sports:{query:[`sports news`,`latest sports`,`cricket football tennis`],links:[`espn.com`,`bbc.com/sport`,`skysports.com`]},entertainment:{query:[`entertainment news`,`movies`,`TV shows`,`celebrities`],links:[`hollywoodreporter.com`,`variety.com`,`deadline.com`]}};function M(e={}){let t=e.searxngDomain??`https://search.qwksearch.com`;return{GET:async e=>{let n=new URL(e.url).searchParams,r=n.get(`q`),i=n.get(`cat`)||`general`,o=parseInt(n.get(`page`)||`1`,10),s=Number.isFinite(o)&&o>0?o:1,c=n.get(`lang`)||`en-US`,l=n.get(`safesearch`)===`true`,u=n.get(`recency`)||void 0,d=n.get(`publicInstances`)===`true`;if(!r)return Response.json({error:`Query parameter is required`},{status:400});let f=Date.now();try{let e=await(0,a.searchWeb)(r,{category:i,recency:u,safesearch:l,maxRetries:6,privateSearxng:!d&&t,proxy:``,lang:c,page:s});e&&(Array.isArray(e)?e.length>0:e.results&&e.results.length>0)||(e=await(0,a.searchWeb)(r,{category:i,recency:u,safesearch:l,maxRetries:6,privateSearxng:!1,proxy:``,lang:c,page:s}));let n=Date.now()-f;return e?Array.isArray(e)?Response.json({results:e,elapsedTime:n}):Response.json({...e,elapsedTime:n}):Response.json({results:[],suggestions:[],elapsedTime:n})}catch(e){let t=e instanceof Error?e.message:String(e);return console.error(`Search error for q="${r}" cat="${i}" page=${s}: ${t}`,e instanceof Error?e.stack:void 0),Response.json({error:`Search failed`,results:[]},{status:500})}}}}function N(){return{GET:async e=>{try{let t=new URL(e.url).searchParams,n=t.get(`mode`)||`normal`,r=j[t.get(`topic`)||`tech`],i=[];if(n===`normal`){let e=new Set;i=(await Promise.all(r.links.flatMap(e=>r.query.map(async t=>(await(0,a.searchSearxng)(`site:${e} ${t}`,{engines:[`bing news`],pageno:1,language:`en`})).results)))).flat().filter(t=>{let n=t.url?.toLowerCase().trim();return!e.has(n)&&(e.add(n),!0)}).sort(()=>Math.random()-.5)}else i=(await(0,a.searchSearxng)(`site:${r.links[Math.floor(Math.random()*r.links.length)]} ${r.query[Math.floor(Math.random()*r.query.length)]}`,{engines:[`bing news`],pageno:1,language:`en`})).results;return Response.json({blogs:i},{status:200})}catch(e){return console.error(`An error occurred in discover route: ${e}`),Response.json({message:`An error has occurred`},{status:500})}}}}var P=[`google`,`duckduckgo`,`wikipedia`],F=3,I=3,L=Object.entries(l.default).map(([e,t])=>({domain:e,name:typeof t?.[0]==`string`?t[0]:``,rank:typeof t?.[1]==`number`?t[1]:2**53-1})),R=null;function z(){return R||=new s.default(L,{keys:[{name:`name`,weight:.6},{name:`domain`,weight:.4}],threshold:.1,ignoreLocation:!0,includeScore:!0,minMatchCharLength:3}),R}function B(e,t,n){return{domain:e,name:t,favicon:`https://www.google.com/s2/favicons?domain=${encodeURIComponent(e)}&sz=64`,rank:n}}function V(e){let t=(0,c.parse)(e);return t.domain&&t.isIcann?t.hostname:null}function H(e){let t=e.split(/\s+/).filter(Boolean),n=t[t.length-1]||``;if(n.length<3)return[];let r=z().search(n,{limit:12}).sort((e,t)=>Math.round((e.score??1)*10)-Math.round((t.score??1)*10)||e.item.rank-t.item.rank).map(({item:e})=>B(e.domain,e.name,e.rank)),i=V(n);return i&&!r.some(e=>e.domain===i)&&r.unshift(B(i,``,2**53-1)),r.slice(0,I)}async function U(e,t,n){let r=await(0,o.searchAutocompleteMulti)(e,t,n);if(r.length>0)return r;let i=t.split(/\s+/).filter(Boolean),a=Math.min(F,i.length-1);for(let r=a;r>=1;r--){let a=i.slice(-r).join(` `),s=i.slice(0,-r).join(` `),c=await(0,o.searchAutocompleteMulti)(e,a,n);if(c.length>0){let e=new Set;for(let n of c){let r=s?`${s} ${n}`:n;r.toLowerCase()!==t.toLowerCase()&&e.add(r)}if(e.size>0)return Array.from(e)}}return[]}function W(){return{GET:async e=>{let{searchParams:t}=new URL(e.url),n=t.get(`q`)?.trim(),r=t.get(`locale`)||`en-US`,i=t.get(`backends`),a=parseInt(t.get(`limit`)||`8`,10);if(!n)return Response.json({suggestions:[],domains:[]});let o=i?i.split(`,`).map(e=>e.trim()).filter(Boolean):P;try{let e=await U(o,n,r),t=H(n);return Response.json({suggestions:e.slice(0,a),domains:t})}catch(e){return console.error(`Autocomplete error:`,e),Response.json({suggestions:[],domains:[]},{status:500})}}}}function G(){return{POST:async e=>{let t=await e.json(),r=t.chatHistory.filter(e=>e.role===`user`||e.role===`assistant`).map(e=>({role:e.role,content:String(e.content??``)})),i=await new n.default().loadChatModel(t.chatModel.providerId,t.chatModel.key),a=(await(0,u.default)({chat_history:r,maxQuestions:t.maxQuestions},i)).flatMap(e=>(e.match(/\?/g)||[]).length>1?e.split(/\?/).map(e=>e.trim()).filter(e=>e.length>0).map(e=>e+`?`):[e]);return Response.json({suggestions:a},{status:200})}}}function K(e){return{POST:async t=>{let n;try{n=await t.json()}catch{return Response.json({error:`Invalid JSON input`},{status:500})}n.ip=t.headers?.get?.(`x-forwarded-for`)||t.headers?.get?.(`x-real-ip`)||`unknown`;let i=e.getDB(),a=await e.getUserId(),o=null;return a&&(o=await i.query.user.findFirst({where:(0,r.eq)(e.userSchema.id,a)})),o&&(n.apiKey=o.settings?.providerApiKeys?.find(e=>e.provider==n.provider)?.key),n.apiKey||(n.apiKey=n.provider==`groq`&&e.getEnv(`GROQ_API_KEY`)),n.apiKey?Response.json({error:`Language generation endpoint is deprecated. Please use /api/agent/chat instead.`},{status:501}):Response.json({error:`API key is required`},{status:500})}}}function q(e){return{POST:async t=>{try{let{text:n,prompt:r}=await t.json();if(!n||typeof n!=`string`)return Response.json({error:`Text is required and must be a string`},{status:400});let i=e.getEnv(`GROQ_API_KEY`);if(!i)return console.error(`GROQ_API_KEY is not configured`),Response.json({error:`AI service is not configured. Please contact the administrator.`},{status:500});let a=e.createGroq({apiKey:i})(`llama-3.3-70b-versatile`),o=r||`Rewrite the following text to improve clarity, grammar, and style while maintaining the original meaning and tone. Only return the rewritten text without any explanation or additional commentary:
37
37
 
38
38
  ${n}`,s=(await e.generateText({model:a,prompt:o,temperature:.7})).text.trim();return Response.json({rewrittenText:s})}catch(e){return console.error(`AI rewrite error:`,e),Response.json({error:`Failed to process AI request. Please try again.`},{status:500})}}}}function J(e){return{POST:async t=>{let n,r,i;try{let e=await t.json();n=e.text,r=e.voice||e.speaker||`af_heart`,i=e.provider||`kokoro`}catch{return Response.json({error:`Invalid request body`},{status:400})}if(!n||typeof n!=`string`||n.trim().length===0)return Response.json({error:`text is required`},{status:400});let a=await e.getUserId()??t.headers?.get?.(`x-forwarded-for`)?.split(`,`)[0]?.trim()??t.headers?.get?.(`x-real-ip`)??`unknown`,{allowed:o}=e.checkTTSRateLimit(a);if(!o)return Response.json({error:`Daily TTS limit reached (10/day)`,rateLimited:!0},{status:429});try{let t=await e.generateSpeech({text:n.slice(0,5e3),provider:i,voice:r});return new Response(t.audio,{headers:{"Content-Type":t.contentType,"Cache-Control":`public, max-age=86400`,"Content-Disposition":`inline; filename="speech.${t.contentType.includes(`wav`)?`wav`:`mp3`}"`}})}catch(e){console.error(`[TTS] Error:`,e);let t=e instanceof Error?e.message:`TTS generation failed`;return t.includes(`Cloudflare AI binding`)?Response.json({error:`Deepgram provider requires Cloudflare AI binding. Use 'kokoro' provider instead.`},{status:503}):Response.json({error:t},{status:500})}}}}function Y(e){return e===`small`||e===`fast`?`@cf/openai/whisper-tiny-en`:e===`medium`||e===`turbo`?`@cf/openai/whisper-large-v3-turbo`:e===`large`?`@cf/openai/whisper-large-v3`:`@cf/openai/whisper-large-v3-turbo`}function X(e){return{POST:async t=>{try{let n=await t.formData(),r=n.get(`file`);if(!r||!(r instanceof File))return Response.json({error:`file is required`},{status:400});let i=Y(n.get(`model`)),a;try{a=e.getCloudflareContext().env?.AI}catch{}if(!a)return Response.json({error:`Cloudflare AI binding not available`},{status:503});let o=await r.arrayBuffer(),{text:s}=await a.run(i,{audio:[...new Uint8Array(o)]});return Response.json({text:s,model:i})}catch(e){return console.error(`Transcript error:`,e),Response.json({error:`Failed to transcribe audio`},{status:500})}}}}function Z(){return{POST:async e=>{try{let{providerType:t,apiKey:n,onlyFree:r=!0}=await e.json();if(!t)return Response.json({error:`Provider type is required`},{status:400});if(!n)return Response.json({error:`API key is required`},{status:400});let i=f.LANGUAGE_MODELS.find(e=>e.provider.toLowerCase()===t.toLowerCase());if(!i)return Response.json({error:`Provider ${t} not found`},{status:404});let a=await(0,d.testProviderModels)(t,n,i.models,{onlyFree:r,concurrency:3,timeout:15e3});return Response.json(a)}catch(e){return console.error(`[test-models] Error:`,e),Response.json({error:e.message||`Failed to test models`},{status:500})}}}}var Q=864e5,$=null;function ee(e){return{GET:async t=>{try{let t=Date.now();if($&&t-$.timestamp<Q)return Response.json({...$.result,cached:!0,cacheAge:t-$.timestamp});let n=await e.validateOpenRouterModels();return $={result:n,timestamp:t},Response.json({...n,cached:!1})}catch(e){return console.error(`[validate-openrouter] GET error:`,e),Response.json({error:e.message||`Validation failed`},{status:500})}},POST:async t=>{try{let{concurrency:n=3,timeout:r=15e3}=await t.json().catch(()=>({})),i=await e.validateOpenRouterModels(n,r);return $={result:i,timestamp:Date.now()},Response.json({...i,cached:!1})}catch(e){return console.error(`[validate-openrouter] POST error:`,e),Response.json({error:e.message||`Validation failed`},{status:500})}}}}exports.createAgentsHandler=K,exports.createArticleFollowupsHandler=p,exports.createArticleQAHandler=g,exports.createAutocompleteHandler=W,exports.createChatByIdHandler=v,exports.createChatTitleHandler=x,exports.createChatsHandler=_,exports.createChatsSearchHandler=y,exports.createChatsShareHandler=b,exports.createDiscoverHandler=N,exports.createMCPServerByIdHandler=k,exports.createMCPServerToggleHandler=A,exports.createMCPServersHandler=O,exports.createMessagesHandler=w,exports.createPageTipsHandler=m,exports.createProviderByIdHandler=E,exports.createProviderModelsHandler=D,exports.createProvidersHandler=T,exports.createRewriteHandler=q,exports.createSearchHandler=M,exports.createSuggestionsHandler=G,exports.createTestModelsHandler=Z,exports.createTopicSearchesHandler=h,exports.createTranscriptHandler=X,exports.createValidateOpenRouterHandler=ee,exports.createVoiceHandler=J,exports.describeError=C;
package/dist/api.mjs CHANGED
@@ -581,31 +581,31 @@ var L = {
581
581
  function R(e = {}) {
582
582
  let t = e.searxngDomain ?? "https://search.qwksearch.com";
583
583
  return { GET: async (e) => {
584
- let n = new URL(e.url).searchParams, r = n.get("q"), i = n.get("cat") || "general", a = parseInt(n.get("page") || "1", 10), o = n.get("lang") || "en-US", s = n.get("safesearch") === "true", c = n.get("recency") || void 0, l = n.get("publicInstances") === "true";
584
+ let n = new URL(e.url).searchParams, r = n.get("q"), i = n.get("cat") || "general", a = parseInt(n.get("page") || "1", 10), o = Number.isFinite(a) && a > 0 ? a : 1, s = n.get("lang") || "en-US", c = n.get("safesearch") === "true", l = n.get("recency") || void 0, u = n.get("publicInstances") === "true";
585
585
  if (!r) return Response.json({ error: "Query parameter is required" }, { status: 400 });
586
- let u = Date.now();
586
+ let d = Date.now();
587
587
  try {
588
588
  let e = await f(r, {
589
589
  category: i,
590
- recency: c,
591
- safesearch: s,
590
+ recency: l,
591
+ safesearch: c,
592
592
  maxRetries: 6,
593
- privateSearxng: !l && t,
593
+ privateSearxng: !u && t,
594
594
  proxy: "",
595
- lang: o,
596
- page: a
595
+ lang: s,
596
+ page: o
597
597
  });
598
598
  e && (Array.isArray(e) ? e.length > 0 : e.results && e.results.length > 0) || (e = await f(r, {
599
599
  category: i,
600
- recency: c,
601
- safesearch: s,
600
+ recency: l,
601
+ safesearch: c,
602
602
  maxRetries: 6,
603
603
  privateSearxng: !1,
604
604
  proxy: "",
605
- lang: o,
606
- page: a
605
+ lang: s,
606
+ page: o
607
607
  }));
608
- let n = Date.now() - u;
608
+ let n = Date.now() - d;
609
609
  return e ? Array.isArray(e) ? Response.json({
610
610
  results: e,
611
611
  elapsedTime: n
@@ -618,7 +618,8 @@ function R(e = {}) {
618
618
  elapsedTime: n
619
619
  });
620
620
  } catch (e) {
621
- return console.error("Search error:", e), Response.json({
621
+ let t = e instanceof Error ? e.message : String(e);
622
+ return console.error(`Search error for q="${r}" cat="${i}" page=${o}: ${t}`, e instanceof Error ? e.stack : void 0), Response.json({
622
623
  error: "Search failed",
623
624
  results: []
624
625
  }, { status: 500 });
@@ -0,0 +1 @@
1
+ export declare function CategoryDock(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function CookieConsent(): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ export type MainViewMode = 'research' | 'docs';
2
+ type MainViewContextValue = {
3
+ activeView: MainViewMode;
4
+ setActiveView: (view: MainViewMode) => void;
5
+ /**
6
+ * Whether the REASON document surface is mounted at all. False in the
7
+ * chat-only build (`research-agent-ui`), where the editor and its sidebar
8
+ * are not bundled — chrome that would switch to documents hides itself
9
+ * rather than offering a view nothing can render.
10
+ */
11
+ docsEnabled: boolean;
12
+ toggleToDocs: () => void;
13
+ toggleToResearch: () => void;
14
+ /** Bumped whenever the files sidebar should be opened (e.g. from a dock icon). */
15
+ filesSidebarRequestId: number;
16
+ requestFilesSidebar: () => void;
17
+ };
18
+ export declare function MainViewProvider({ children, docsEnabled, }: {
19
+ children: React.ReactNode;
20
+ /** Set false when the host mounts the chat surface without the REASON editor. */
21
+ docsEnabled?: boolean;
22
+ }): import("react").JSX.Element;
23
+ export declare function useMainView(): MainViewContextValue;
24
+ export {};
@@ -0,0 +1,4 @@
1
+ import { type QwkSearchProvidersProps } from './QwkSearchProviders';
2
+ export type QwkSearchAppProps = Omit<QwkSearchProvidersProps, 'children' | 'docsEnabled'>;
3
+ export declare function QwkSearchApp(props: QwkSearchAppProps): import("react").JSX.Element;
4
+ export default QwkSearchApp;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview The QwkSearch app shell's provider stack — everything the
3
+ * general app mounts around a page, independent of whether the REASON editor
4
+ * is part of the build.
5
+ */
6
+ import { type ComponentType, type ReactNode } from 'react';
7
+ import { type ResearchAgentAuthClient, type ResearchAgentUIConfig } from '../config';
8
+ export interface QwkSearchProvidersProps {
9
+ children: ReactNode;
10
+ /**
11
+ * The host app's configured auth client (e.g. a better-auth React client).
12
+ * See `ResearchAgentAuthClient` for the subset actually used.
13
+ */
14
+ authClient: ResearchAgentAuthClient;
15
+ /**
16
+ * Package configuration applied before the tree renders — the same values
17
+ * `configureResearchAgentUI` takes. Supplying it here keeps branding, footer
18
+ * links and callbacks with the mount instead of in a separate module
19
+ * side effect.
20
+ */
21
+ config?: Partial<ResearchAgentUIConfig>;
22
+ /**
23
+ * Whether to prompt Google One Tap. `'auto'` (the default) asks the backend
24
+ * which providers are configured and enables the prompt only when Google is
25
+ * among them — prompting without a provider behind it can only fail.
26
+ */
27
+ googleOneTap?: boolean | 'auto';
28
+ /**
29
+ * Whether the REASON document surface is part of this build. Set by the
30
+ * entry point rather than the host: `research-agent-ui` mounts chat only,
31
+ * `research-agent-ui/workspace` mounts chat plus the editor and its sidebar.
32
+ */
33
+ docsEnabled?: boolean;
34
+ /**
35
+ * An extra provider mounted just inside the dock/view providers, for
36
+ * app-owned context the shell's chrome reads — the settings modal, for
37
+ * instance. Receives the rest of the tree as `children`.
38
+ */
39
+ ChromeProvider?: ComponentType<{
40
+ children: ReactNode;
41
+ }>;
42
+ /** Render the app dock. Default true. */
43
+ showDock?: boolean;
44
+ /** Render the cookie-consent banner. Default true. */
45
+ showCookieConsent?: boolean;
46
+ /** Render the `sonner` toaster. Default true. */
47
+ showToaster?: boolean;
48
+ }
49
+ export declare function QwkSearchProviders({ children, authClient, config, googleOneTap, docsEnabled, ChromeProvider, showDock, showCookieConsent, showToaster, }: QwkSearchProvidersProps): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @fileoverview The QwkSearch general app shell, minus the REASON editor: the
3
+ * provider stack, the app dock, the cookie banner, the research/docs view
4
+ * switch, and chat-tab bookkeeping.
5
+ *
6
+ * Everything here is editor-free, so importing it never pulls the Tiptap/Plate
7
+ * dependency tree into a consuming bundle. The editor-bearing counterparts live
8
+ * in `research-agent-ui/workspace`.
9
+ */
10
+ export { QwkSearchApp, type QwkSearchAppProps } from './QwkSearchApp';
11
+ export { QwkSearchProviders, type QwkSearchProvidersProps, } from './QwkSearchProviders';
12
+ export { CategoryDock } from './CategoryDock';
13
+ export { CookieConsent } from './CookieConsent';
14
+ export { MainViewProvider, useMainView, type MainViewMode, } from './MainViewProvider';
15
+ export { useChatTabs, type ChatTab } from './useChatTabs';
16
+ export { useChunkErrorReload } from './useChunkErrorReload';
@@ -0,0 +1,37 @@
1
+ export interface ChatTab {
2
+ id: string;
3
+ title: string;
4
+ /** Whether this chat has ever had a message sent. Empty/untouched "New
5
+ * Chat" tabs must be re-armed via `startNewChat` rather than
6
+ * `switchToChat` — the latter fetches the chat and would incorrectly
7
+ * report it "not found" since nothing was ever persisted for it. */
8
+ hasMessages?: boolean;
9
+ }
10
+ /**
11
+ * Tracks which chat conversations are "open" (shown as tabs in the
12
+ * workspace's Open Tabs sidebar panel) alongside REASON's document tabs.
13
+ * The active chat itself is owned by `ChatProvider`/`useChat()` — this hook
14
+ * only tracks the open-tab list and titles, and exposes helpers for
15
+ * switching, closing, and creating chat tabs without navigating away from
16
+ * the workspace route.
17
+ */
18
+ export declare function useChatTabs(): {
19
+ chatTabs: ChatTab[];
20
+ activeChatId: string;
21
+ openChat: (id: string) => void;
22
+ newChat: () => `${string}-${string}-${string}-${string}-${string}`;
23
+ closeChat: (id: string) => {
24
+ closedWasActive: boolean;
25
+ nextActiveId: any;
26
+ } | {
27
+ closedWasActive: true;
28
+ nextActiveId: string;
29
+ };
30
+ closeChats: (ids: string[]) => {
31
+ closedWasActive: boolean;
32
+ nextActiveId: any;
33
+ } | {
34
+ closedWasActive: true;
35
+ nextActiveId: string;
36
+ };
37
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Deployments replace `_next/static` chunks with newly-hashed filenames, so a
3
+ * browser tab left open from before a deploy will 404 when it tries to fetch
4
+ * an old chunk (e.g. on a client-side navigation). Reload once to pick up the
5
+ * new build instead of leaving the user stuck on a broken page; the
6
+ * sessionStorage flag stops a reload loop if the new build somehow fails too.
7
+ */
8
+ export declare function useChunkErrorReload(): void;
@@ -0,0 +1,17 @@
1
+ interface RandomLoadingAnimationProps {
2
+ /** Width and height of the SVG spinners, in pixels. */
3
+ size?: number;
4
+ /** Extra classes for the centering wrapper. */
5
+ className?: string;
6
+ }
7
+ /**
8
+ * Renders one randomly chosen loading animation.
9
+ *
10
+ * The pick happens in an effect rather than during render so server and client
11
+ * markup match; until it lands the wrapper renders empty at its final height,
12
+ * which keeps the surrounding layout from shifting.
13
+ *
14
+ * @returns {JSX.Element} The rendered loader
15
+ */
16
+ declare const RandomLoadingAnimation: ({ size, className, }: RandomLoadingAnimationProps) => import("react").JSX.Element;
17
+ export default RandomLoadingAnimation;
@@ -0,0 +1,16 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import RandomLoadingAnimation from './RandomLoadingAnimation';
3
+ /**
4
+ * `RandomLoadingAnimation` is the chat's "thinking" loader. Each mount picks a
5
+ * different animation from the GRAB-URL spinner set (`grab-url/animations`)
6
+ * plus the quantum orbital sphere, so a new one appears on every response.
7
+ */
8
+ declare const meta: Meta<typeof RandomLoadingAnimation>;
9
+ export default meta;
10
+ type Story = StoryObj<typeof RandomLoadingAnimation>;
11
+ /** One random pick — reload the story to draw another. */
12
+ export declare const Default: Story;
13
+ /** Four mounts side by side, each picking independently. */
14
+ export declare const Several: Story;
15
+ /** Every SVG spinner in the pool, for reviewing the set as a whole. */
16
+ export declare const AllVariants: Story;
@@ -0,0 +1,42 @@
1
+ /** Options accepted by every `grab-url/animations` spinner factory. */
2
+ export interface LoadingAnimationOptions {
3
+ /** Hex colors substituted into the SVG, in order of appearance. */
4
+ colors?: string[];
5
+ /** Width of the SVG. */
6
+ width?: number;
7
+ /** Height of the SVG. */
8
+ height?: number;
9
+ /** Shorthand setting both width and height. */
10
+ size?: number;
11
+ /** Return the raw `<svg>` string instead of an `<img>` data-URI tag. */
12
+ raw?: boolean;
13
+ }
14
+ /** A `grab-url/animations` factory: options in, SVG markup out. */
15
+ export type SvgLoadingAnimation = (options?: LoadingAnimationOptions) => string;
16
+ /**
17
+ * Name of the quantum orbital sphere, which is rendered by a React component
18
+ * rather than an SVG string and so is kept out of {@link SVG_LOADING_ANIMATIONS}.
19
+ */
20
+ export declare const QUANTUM_SPHERE_ANIMATION = "quantumSphere";
21
+ /**
22
+ * Every animated SVG spinner exported by `grab-url/animations`, keyed by its
23
+ * export name. Read off the module namespace rather than listed by hand, so
24
+ * spinners added in a later `grab-url` release join the rotation on upgrade.
25
+ */
26
+ export declare const SVG_LOADING_ANIMATIONS: Record<string, SvgLoadingAnimation>;
27
+ /** Every loader the chat can show, sorted so the order is deterministic. */
28
+ export declare const LOADING_ANIMATION_NAMES: string[];
29
+ /**
30
+ * Picks one loader at random, never the same one twice in a row.
31
+ *
32
+ * @returns {string} A name from {@link LOADING_ANIMATION_NAMES}
33
+ */
34
+ export declare function getRandomLoadingAnimation(): string;
35
+ /**
36
+ * Renders one spinner to a raw `<svg>` string.
37
+ *
38
+ * @param name - A name from {@link SVG_LOADING_ANIMATIONS}
39
+ * @param size - Width and height in pixels
40
+ * @returns {string} SVG markup, or an empty string for an unknown name
41
+ */
42
+ export declare function renderLoadingAnimation(name: string, size: number): string;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Central `Icons` export map of Lucide icons and custom SVGs used across the message composer.
3
+ *
4
+ * Re-exports commonly used Lucide icons under short names and adds custom inline SVGs (Thinking clock,
5
+ * SelectArrow) shared by MessageComposer and related components.
6
+ */
7
+ import React from "react";
8
+ export declare const Icons: {
9
+ Plus: import("lucide-react").LucideIcon;
10
+ Paperclip: import("lucide-react").LucideIcon;
11
+ Cloud: import("lucide-react").LucideIcon;
12
+ Upload: import("lucide-react").LucideIcon;
13
+ File: import("lucide-react").LucideIcon;
14
+ SquarePen: import("lucide-react").LucideIcon;
15
+ Thinking: (props: React.SVGProps<SVGSVGElement>) => React.JSX.Element;
16
+ SelectArrow: import("lucide-react").LucideIcon;
17
+ ArrowUp: import("lucide-react").LucideIcon;
18
+ X: import("lucide-react").LucideIcon;
19
+ FileText: import("lucide-react").LucideIcon;
20
+ Loader2: import("lucide-react").LucideIcon;
21
+ Check: import("lucide-react").LucideIcon;
22
+ Archive: import("lucide-react").LucideIcon;
23
+ Mic: import("lucide-react").LucideIcon;
24
+ Stop: import("lucide-react").LucideIcon;
25
+ Clock: (props: React.SVGProps<SVGSVGElement>) => React.JSX.Element;
26
+ };
package/dist/config.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={appName:`QwkSearch`,defaultSummarizePrompt:`Summarize in bullet points and bold topics`,maxArticleLength:1500,downloadChromeUrl:`https://chromewebstore.google.com/detail/tab-manager-ai/manhemnhmipdhdpabojcplebckhckeko`,downloadWindowsStoreId:`9PCGF9GNK460`,footerLinks:[],googleApiKey:``,getAutoMediaSearch:()=>!0};function t(t){Object.assign(e,t)}exports.configureResearchAgentUI=t,exports.researchAgentUIConfig=e;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={appName:`QwkSearch`,defaultSummarizePrompt:`Summarize in bullet points and bold topics`,maxArticleLength:1500,downloadChromeUrl:`https://chromewebstore.google.com/detail/tab-manager-ai/manhemnhmipdhdpabojcplebckhckeko`,downloadWindowsStoreId:`9PCGF9GNK460`,footerLinks:[],googleApiKey:``,googleAppId:``,getAutoMediaSearch:()=>!0,appIconUrl:`/apple-touch-icon.png`};function t(t){Object.assign(e,t)}exports.configureResearchAgentUI=t,exports.researchAgentUIConfig=e;
package/dist/config.d.ts CHANGED
@@ -46,8 +46,22 @@ export interface ResearchAgentUIConfig {
46
46
  footerLinks: FooterLink[];
47
47
  /** Google API key used by the Google Drive file picker. */
48
48
  googleApiKey: string;
49
+ /**
50
+ * Google Cloud project number, passed to the Drive picker as its app ID.
51
+ * The connector holds the per-file `drive.file` scope rather than blanket
52
+ * Drive access, and Google only grants the app a picked file when the
53
+ * picker knows which app is asking — so leaving this empty means picked
54
+ * files come back but downloading them 403s.
55
+ */
56
+ googleAppId: string;
49
57
  /** Whether to auto-trigger image/video media search after a response completes. */
50
58
  getAutoMediaSearch: () => boolean;
59
+ /**
60
+ * URL of the app's own icon, shown as the "Research" entry in the app dock.
61
+ * Served by the consuming app (it is a static asset, not a bundled one) so
62
+ * that a host with different branding can point at its own file.
63
+ */
64
+ appIconUrl: string;
51
65
  /**
52
66
  * Requests that the settings UI be opened. Lets the consuming app render
53
67
  * settings in a modal (e.g. on large desktop screens) instead of navigating
package/dist/config.mjs CHANGED
@@ -7,7 +7,9 @@ var e = {
7
7
  downloadWindowsStoreId: "9PCGF9GNK460",
8
8
  footerLinks: [],
9
9
  googleApiKey: "",
10
- getAutoMediaSearch: () => !0
10
+ googleAppId: "",
11
+ getAutoMediaSearch: () => !0,
12
+ appIconUrl: "/apple-touch-icon.png"
11
13
  };
12
14
  function t(t) {
13
15
  Object.assign(e, t);