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 +65 -4
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +14 -13
- package/dist/app/CategoryDock.d.ts +1 -0
- package/dist/app/CookieConsent.d.ts +1 -0
- package/dist/app/MainViewProvider.d.ts +24 -0
- package/dist/app/QwkSearchApp.d.ts +4 -0
- package/dist/app/QwkSearchProviders.d.ts +49 -0
- package/dist/app/index.d.ts +16 -0
- package/dist/app/useChatTabs.d.ts +37 -0
- package/dist/app/useChunkErrorReload.d.ts +8 -0
- package/dist/components/ChatConversation/RandomLoadingAnimation.d.ts +17 -0
- package/dist/components/ChatConversation/RandomLoadingAnimation.stories.d.ts +16 -0
- package/dist/components/ChatConversation/loading-animations.d.ts +42 -0
- package/dist/components/MessageComposer/MessageInputIconSet.d.ts +26 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.mjs +3 -1
- package/dist/index.cjs +1 -51
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +2 -6957
- package/dist/lib/apiError.d.ts +28 -0
- package/dist/src-BgiTkBp6.cjs +51 -0
- package/dist/src-CsHfnztO.js +7425 -0
- package/dist/workspace/QwkSearchWorkspaceApp.d.ts +9 -0
- package/dist/workspace/ResearchWorkspaceView.d.ts +4 -0
- package/dist/workspace/page-tips.d.ts +8 -0
- package/dist/workspace/topic-searches.d.ts +2 -0
- package/dist/workspace.cjs +1 -0
- package/dist/workspace.d.ts +5 -0
- package/dist/workspace.mjs +137 -0
- package/package.json +44 -19
- package/src/settings/search.json +85 -1
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
|
-
|
|
34
|
-
file uploads,
|
|
35
|
-
|
|
36
|
-
|
|
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
|

|
|
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",
|
|
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
|
|
586
|
+
let d = Date.now();
|
|
587
587
|
try {
|
|
588
588
|
let e = await f(r, {
|
|
589
589
|
category: i,
|
|
590
|
-
recency:
|
|
591
|
-
safesearch:
|
|
590
|
+
recency: l,
|
|
591
|
+
safesearch: c,
|
|
592
592
|
maxRetries: 6,
|
|
593
|
-
privateSearxng: !
|
|
593
|
+
privateSearxng: !u && t,
|
|
594
594
|
proxy: "",
|
|
595
|
-
lang:
|
|
596
|
-
page:
|
|
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:
|
|
601
|
-
safesearch:
|
|
600
|
+
recency: l,
|
|
601
|
+
safesearch: c,
|
|
602
602
|
maxRetries: 6,
|
|
603
603
|
privateSearxng: !1,
|
|
604
604
|
proxy: "",
|
|
605
|
-
lang:
|
|
606
|
-
page:
|
|
605
|
+
lang: s,
|
|
606
|
+
page: o
|
|
607
607
|
}));
|
|
608
|
-
let n = Date.now() -
|
|
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
|
-
|
|
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
|
-
|
|
10
|
+
googleAppId: "",
|
|
11
|
+
getAutoMediaSearch: () => !0,
|
|
12
|
+
appIconUrl: "/apple-touch-icon.png"
|
|
11
13
|
};
|
|
12
14
|
function t(t) {
|
|
13
15
|
Object.assign(e, t);
|