research-agent-ui 0.1.93 → 0.1.95
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 +0 -3
- package/dist/api/handlers/autocomplete.d.ts +1 -0
- package/dist/api/handlers/mcpservers.d.ts +7 -0
- package/dist/api/handlers/messages.d.ts +7 -0
- package/dist/api/handlers/rewrite.d.ts +3 -0
- package/dist/api/handlers/transcript.d.ts +7 -0
- package/dist/api/handlers/validate-openrouter.d.ts +3 -0
- package/dist/api/handlers/voice.d.ts +7 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/types.d.ts +1 -1
- package/dist/api.cjs +3 -3
- package/dist/api.mjs +5 -4
- package/dist/cloudflare/worker.d.ts +1 -0
- package/dist/components/ArticleReader/ArticleAIResponse.d.ts +1 -1
- package/dist/components/ArticleReader/ArticleActionButtons.d.ts +1 -2
- package/dist/components/ArticleReader/ArticleFollowupQuestions.d.ts +1 -1
- package/dist/components/ArticleReader/ArticlePanelHeader.d.ts +1 -1
- package/dist/components/ArticleReader/ArticlePromptInput.d.ts +1 -1
- package/dist/components/ArticleReader/index.d.ts +1 -1
- package/dist/components/ChatConversation/DownloadsDialog.d.ts +6 -0
- package/dist/components/ChatConversation/background-art.d.ts +3 -0
- package/dist/components/ChatHistoryDropdown/HistorySubmenu.d.ts +7 -0
- package/dist/components/ChatHistoryDropdown/index.d.ts +1 -1
- package/dist/components/ChatHistoryDropdown/useRecentChats.d.ts +13 -0
- package/dist/components/FileUpload/FilePreviewCard.d.ts +4 -2
- package/dist/components/Footer.d.ts +2 -0
- package/dist/components/MessageComposer/ActionBar.d.ts +4 -2
- package/dist/components/MessageComposer/MessageInputIconSet.d.ts +4 -2
- package/dist/components/MessageComposer/PastedContentCard.d.ts +4 -2
- package/dist/config.d.ts +12 -0
- package/dist/hooks/useChat/ChatContext.d.ts +14 -0
- package/dist/hooks/voice/useSpeechToTranscript.d.ts +17 -2
- package/dist/hooks/voice/useVoiceAutoStart.d.ts +6 -0
- package/dist/icons/IconSearchAcademic.d.ts +3 -0
- package/dist/icons/IconSearchFiles.d.ts +3 -0
- package/dist/icons/IconSearchImages.d.ts +3 -0
- package/dist/icons/IconSearchNews.d.ts +3 -0
- package/dist/icons/IconSearchTech.d.ts +3 -0
- package/dist/icons/IconSearchVideos.d.ts +3 -0
- package/dist/icons/IconSearchWeb.d.ts +3 -0
- package/dist/index.cjs +15 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +1237 -939
- package/dist/lib/composer.d.ts +1 -1
- package/dist/lib/file-sources.d.ts +7 -0
- package/dist/lib/guest.d.ts +6 -0
- package/dist/lib/relative-time.d.ts +1 -1
- package/dist/lib/url-autolink.d.ts +1 -2
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/voiceAutoStart.d.ts +22 -0
- package/dist/{rolldown-runtime-CMqjfN_6.cjs → rolldown-runtime-BocRIvOZ.cjs} +1 -1
- package/dist/settings/index.d.ts +57 -0
- package/dist/stories/mocks.d.ts +1 -1
- package/dist/types/fileSource.d.ts +3 -0
- package/dist/types/research.d.ts +1 -1
- package/package.json +29 -11
- package/src/settings/index.ts +96 -0
- package/src/settings/search.json +161 -0
- package/src/settings/sections.json +65 -0
package/README.md
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
<br />
|
|
3
3
|
<a href="https://www.npmjs.com/package/research-agent-ui"><img src="https://img.shields.io/npm/dm/research-agent-ui.svg" alt="NPM Monthly Downloads"></a>
|
|
4
4
|
<a href="https://www.npmjs.com/package/research-agent-ui"><img src="https://img.shields.io/npm/v/research-agent-ui.svg" alt="npm version"></a>
|
|
5
|
-
<a href="https://packagephobia.com/result?p=research-agent-ui" target="_blank" rel="noopener noreferrer">
|
|
6
|
-
<img src="https://packagephobia.com/badge?p=research-agent-ui" alt="install size" />
|
|
7
|
-
</a>
|
|
8
5
|
<a href="https://discord.gg/SJdBqBz3tV">
|
|
9
6
|
<img src="https://img.shields.io/discord/1110227955554209923.svg?label=Chat&logo=Discord&colorB=7289da&style=flat"
|
|
10
7
|
alt="Join Discord" />
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CRUD handlers for configured MCP (Model Context Protocol) servers.
|
|
3
|
+
*
|
|
4
|
+
* Provides list/add (createMCPServersHandler), update/delete
|
|
5
|
+
* (createMCPServerByIdHandler), and enable/disable
|
|
6
|
+
* (createMCPServerToggleHandler) operations backed by deps.configManager.
|
|
7
|
+
*/
|
|
1
8
|
import type { MCPServersDeps } from "../types";
|
|
2
9
|
export declare function createMCPServersHandler(deps: MCPServersDeps): {
|
|
3
10
|
GET: (_req: Request) => Promise<Response>;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handler for persisting a chat message, plus a driver-error formatting utility.
|
|
3
|
+
*
|
|
4
|
+
* createMessagesHandler validates and inserts a message row scoped to the
|
|
5
|
+
* authenticated user; describeError flattens Drizzle/D1's wrapped `cause`
|
|
6
|
+
* chain into a single readable string for logging.
|
|
7
|
+
*/
|
|
1
8
|
import type { MessagesDeps } from "../types";
|
|
2
9
|
/**
|
|
3
10
|
* Flattens an error's `cause` chain into one string. Drizzle wraps driver
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handler that transcribes an uploaded audio file via Cloudflare Workers AI Whisper.
|
|
3
|
+
*
|
|
4
|
+
* Maps a requested quality tier ("small"/"medium"/"large"/etc.) to a
|
|
5
|
+
* specific Whisper model binding and requires the Cloudflare AI binding to
|
|
6
|
+
* be present (unavailable in local dev without CF bindings).
|
|
7
|
+
*/
|
|
1
8
|
import type { TranscriptDeps } from "../types";
|
|
2
9
|
export declare function createTranscriptHandler(deps: TranscriptDeps): {
|
|
3
10
|
POST: (req: Request) => Promise<Response>;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handler that validates OpenRouter model availability, with a 24-hour in-memory cache.
|
|
3
|
+
*/
|
|
1
4
|
import type { ValidateOpenRouterDeps } from "../types";
|
|
2
5
|
export declare function createValidateOpenRouterHandler(deps: ValidateOpenRouterDeps): {
|
|
3
6
|
GET: (_req: Request) => Promise<Response>;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Handler that generates text-to-speech audio, rate-limited per user/IP.
|
|
3
|
+
*
|
|
4
|
+
* Accepts text plus a voice/provider (defaulting to Kokoro), enforces a
|
|
5
|
+
* 10/day TTS rate limit keyed by user id or forwarded IP, and streams back
|
|
6
|
+
* the generated audio with an appropriate content type.
|
|
7
|
+
*/
|
|
1
8
|
import type { VoiceDeps } from "../types";
|
|
2
9
|
export declare function createVoiceHandler(deps: VoiceDeps): {
|
|
3
10
|
POST: (req: Request) => Promise<Response>;
|
package/dist/api/index.d.ts
CHANGED
package/dist/api/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dependency injection types for research-agent-ui API route handlers.
|
|
2
|
+
* @fileoverview Dependency injection types for research-agent-ui API route handlers.
|
|
3
3
|
*
|
|
4
4
|
* Each factory function accepts a narrow `deps` object containing only the
|
|
5
5
|
* app-specific implementations it needs (db, auth helpers, env accessor, etc.)
|
package/dist/api.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rolldown-runtime-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rolldown-runtime-BocRIvOZ.cjs");let t=require("ai"),n=require("chat-agent-toolkit/models/registry");n=e.t(n,1);let r=require("drizzle-orm"),i=require("chat-agent-toolkit/tools/search/titleGeneratorAgent");i=e.t(i,1);let a=require("search-web-api/search/public-searxng"),o=require("extract-webpage/suggest-next-words/autocomplete-search-engines"),s=require("fuse.js");s=e.t(s,1);let c=require("domain-rank/data/domain-rank-merged.json");c=e.t(c,1);let l=require("chat-agent-toolkit/tools/search/suggestionGeneratorAgent");l=e.t(l,1);let u=require("chat-agent-toolkit/config/model-tester"),d=require("chat-agent-toolkit/config/language-models-database");function f(e){return async e=>{try{let{article:r,chatHistory:i=[],maxQuestions:a=5,chatModel:o}=await e.json();if(!r)return Response.json({error:`Article is required`},{status:400});let s=new n.default,c;try{c=await s.loadChatModel(o?.providerId,o?.key)}catch(e){return Response.json({error:e instanceof Error?e.message:`Failed to load language model`},{status:500})}let l=i.length>0?`\n\nPrevious questions asked:\n${i.filter(e=>e.role===`user`).map(e=>`- ${e.content}`).join(`
|
|
2
2
|
`)}`:``,u=`You are a helpful AI that generates insightful follow-up questions about articles.
|
|
3
3
|
Generate ${a} thought-provoking questions that would help readers understand the article better.
|
|
4
4
|
Return ONLY the questions, one per line, without numbering or bullet points.`,d=`Article content:
|
|
@@ -17,6 +17,6 @@ User question: ${i}
|
|
|
17
17
|
|
|
18
18
|
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.
|
|
19
19
|
Provide clear, concise, and accurate answers based on the article content provided.
|
|
20
|
-
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 m(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 h(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 g(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 _(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 v(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 y=[`assistant`,`user`,`source`,`suggestion`];function b(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 x(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}):y.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:`,b(e)),Response.json({message:`Failed to save message`},{status:500})}}}}function S(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 C(){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 w(){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 T(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 E(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 D(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 O={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 k(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 A(){return{GET:async e=>{try{let t=new URL(e.url).searchParams,n=t.get(`mode`)||`normal`,r=O[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)?!1:(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 j=[`google`,`duckduckgo`,`wikipedia`],M=3,N=3,P=Object.entries(c.default).map(([e,t])=>({domain:e,name:typeof t?.[0]==`string`?t[0]:``,rank:typeof t?.[1]==`number`?t[1]:2**53-1})),F=null;function I(){return F||=new s.default(P,{keys:[{name:`name`,weight:.6},{name:`domain`,weight:.4}],threshold:.1,ignoreLocation:!0,includeScore:!0,minMatchCharLength:3}),F}function L(e){let t=e.split(/\s+/).filter(Boolean),n=t[t.length-1]||``;return n.length<3?[]:I().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).slice(0,N).map(({item:e})=>({domain:e.domain,name:e.name,favicon:`https://www.google.com/s2/favicons?domain=${encodeURIComponent(e.domain)}&sz=64`}))}async function R(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(M,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 z(){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):j;try{let e=await R(o,n,r),t=L(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 B(){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,l.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 V(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.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 H(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:
|
|
20
|
+
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 m(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 h(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 g(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 _(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 v(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 y=[`assistant`,`user`,`source`,`suggestion`];function b(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 x(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}):y.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:`,b(e)),Response.json({message:`Failed to save message`},{status:500})}}}}function S(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 C(){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 w(){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 T(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 E(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 D(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 O={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 k(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 A(){return{GET:async e=>{try{let t=new URL(e.url).searchParams,n=t.get(`mode`)||`normal`,r=O[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 j=[`google`,`duckduckgo`,`wikipedia`],M=3,N=3,P=Object.entries(c.default).map(([e,t])=>({domain:e,name:typeof t?.[0]==`string`?t[0]:``,rank:typeof t?.[1]==`number`?t[1]:2**53-1})),F=null;function I(){return F||=new s.default(P,{keys:[{name:`name`,weight:.6},{name:`domain`,weight:.4}],threshold:.1,ignoreLocation:!0,includeScore:!0,minMatchCharLength:3}),F}function L(e){let t=e.split(/\s+/).filter(Boolean),n=t[t.length-1]||``;return n.length<3?[]:I().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).slice(0,N).map(({item:e})=>({domain:e.domain,name:e.name,favicon:`https://www.google.com/s2/favicons?domain=${encodeURIComponent(e.domain)}&sz=64`,rank:e.rank}))}async function R(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(M,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 z(){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):j;try{let e=await R(o,n,r),t=L(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 B(){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,l.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 V(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 H(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:
|
|
21
21
|
|
|
22
|
-
${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 U(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 W(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 G(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=W(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 K(){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=d.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,u.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=
|
|
22
|
+
${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 U(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 W(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 G(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=W(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 K(){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=d.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,u.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,J=null;function Y(e){return{GET:async t=>{try{let t=Date.now();if(J&&t-J.timestamp<q)return Response.json({...J.result,cached:!0,cacheAge:t-J.timestamp});let n=await e.validateOpenRouterModels();return J={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 J={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=V,exports.createArticleFollowupsHandler=f,exports.createArticleQAHandler=p,exports.createAutocompleteHandler=z,exports.createChatByIdHandler=h,exports.createChatTitleHandler=v,exports.createChatsHandler=m,exports.createChatsSearchHandler=g,exports.createChatsShareHandler=_,exports.createDiscoverHandler=A,exports.createMCPServerByIdHandler=E,exports.createMCPServerToggleHandler=D,exports.createMCPServersHandler=T,exports.createMessagesHandler=x,exports.createProviderByIdHandler=C,exports.createProviderModelsHandler=w,exports.createProvidersHandler=S,exports.createRewriteHandler=H,exports.createSearchHandler=k,exports.createSuggestionsHandler=B,exports.createTestModelsHandler=K,exports.createTranscriptHandler=G,exports.createValidateOpenRouterHandler=Y,exports.createVoiceHandler=U,exports.describeError=b;
|
package/dist/api.mjs
CHANGED
|
@@ -552,7 +552,7 @@ function I() {
|
|
|
552
552
|
language: "en"
|
|
553
553
|
})).results)))).flat().filter((t) => {
|
|
554
554
|
let n = t.url?.toLowerCase().trim();
|
|
555
|
-
return e.has(n)
|
|
555
|
+
return !e.has(n) && (e.add(n), !0);
|
|
556
556
|
}).sort(() => Math.random() - .5);
|
|
557
557
|
} else i = (await d(`site:${r.links[Math.floor(Math.random() * r.links.length)]} ${r.query[Math.floor(Math.random() * r.query.length)]}`, {
|
|
558
558
|
engines: ["bing news"],
|
|
@@ -596,7 +596,8 @@ function U(e) {
|
|
|
596
596
|
return n.length < 3 ? [] : H().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).slice(0, z).map(({ item: e }) => ({
|
|
597
597
|
domain: e.domain,
|
|
598
598
|
name: e.name,
|
|
599
|
-
favicon: `https://www.google.com/s2/favicons?domain=${encodeURIComponent(e.domain)}&sz=64
|
|
599
|
+
favicon: `https://www.google.com/s2/favicons?domain=${encodeURIComponent(e.domain)}&sz=64`,
|
|
600
|
+
rank: e.rank
|
|
600
601
|
}));
|
|
601
602
|
}
|
|
602
603
|
async function W(e, t, n) {
|
|
@@ -664,7 +665,7 @@ function q(e) {
|
|
|
664
665
|
}
|
|
665
666
|
n.ip = t.headers?.get?.("x-forwarded-for") || t.headers?.get?.("x-real-ip") || "unknown";
|
|
666
667
|
let r = e.getDB(), a = await e.getUserId(), o = null;
|
|
667
|
-
return a && (o = await r.query.user.findFirst({ where: i(e.userSchema.id, a) })), o && (n.apiKey = o.settings?.providerApiKeys?.find((e) => e.provider == n.provider)?.key), n.apiKey
|
|
668
|
+
return a && (o = await r.query.user.findFirst({ where: i(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 });
|
|
668
669
|
} };
|
|
669
670
|
}
|
|
670
671
|
//#endregion
|
|
@@ -772,7 +773,7 @@ function Q() {
|
|
|
772
773
|
}
|
|
773
774
|
//#endregion
|
|
774
775
|
//#region src/api/handlers/validate-openrouter.ts
|
|
775
|
-
var te =
|
|
776
|
+
var te = 864e5, $ = null;
|
|
776
777
|
function ne(e) {
|
|
777
778
|
return {
|
|
778
779
|
GET: async (t) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Displays the AI-generated response to a user's prompt about the current article, with loading spinner and error state.
|
|
2
|
+
* @fileoverview Displays the AI-generated response to a user's prompt about the current article, with loading spinner and error state.
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react';
|
|
5
5
|
interface ArticleAIResponseProps {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Toolbar with Ask AI, Suggest, Copy, Highlight, Favorite, Open-in-new-tab, and Close buttons
|
|
3
|
-
* shown at the top of the article extract panel.
|
|
2
|
+
* @fileoverview Toolbar with Ask AI, Suggest, Copy, Highlight, Favorite, Open-in-new-tab, and Close buttons shown at the top of the article extract panel.
|
|
4
3
|
*
|
|
5
4
|
* Each button has a shadcn/Radix tooltip that appears on hover (zero delay) showing the
|
|
6
5
|
* action label and its keyboard shortcut. The shortcut key definitions are exported as
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Renders a clickable list of AI-generated follow-up question suggestions for the open article.
|
|
2
|
+
* @fileoverview Renders a clickable list of AI-generated follow-up question suggestions for the open article.
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react';
|
|
5
5
|
interface ArticleFollowupQuestionsProps {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Single-line text input for entering AI questions about the current article; submits on Enter.
|
|
2
|
+
* @fileoverview Single-line text input for entering AI questions about the current article; submits on Enter.
|
|
3
3
|
*/
|
|
4
4
|
import React from 'react';
|
|
5
5
|
interface ArticlePromptInputProps {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Barrel module that re-exports all ArticleReader components, the Lexical article viewer, and research types.
|
|
2
|
+
* @fileoverview Barrel module that re-exports all ArticleReader components, the Lexical article viewer, and research types.
|
|
3
3
|
*/
|
|
4
4
|
export { default as ArticlePanelHeader } from "./ArticlePanelHeader";
|
|
5
5
|
export { default as ArticleActionButtons } from "./ArticleActionButtons";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helper functions providing randomized background artwork URLs and time-of-day greeting strings for the chat homepage.
|
|
3
|
+
*/
|
|
1
4
|
/**
|
|
2
5
|
* Gets a random artistic background about graph theory
|
|
3
6
|
* and collective consciousness, 50+ AI-gen artworks found online
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Chat } from "../../types/research";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches the `limit` most recently active chats on demand.
|
|
4
|
+
*
|
|
5
|
+
* @param limit - Maximum number of chats to return.
|
|
6
|
+
* @returns The chats, a loading flag, and a `load` callback that fetches once
|
|
7
|
+
* (pass `true` to force a refetch).
|
|
8
|
+
*/
|
|
9
|
+
export declare function useRecentChats(limit?: number): {
|
|
10
|
+
recentChats: Chat[];
|
|
11
|
+
loading: boolean;
|
|
12
|
+
load: (force?: boolean) => Promise<void>;
|
|
13
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Thumbnail card for a single attached file showing an image preview or
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Thumbnail card for a single attached file, showing an image preview or file-type icon.
|
|
3
|
+
*
|
|
4
|
+
* Renders a square preview tile with name/size for non-image files, or the image itself for image
|
|
5
|
+
* attachments, overlaying upload status (spinner/error) and a hover-revealed remove button.
|
|
4
6
|
*/
|
|
5
7
|
import React from "react";
|
|
6
8
|
import { AttachedFile } from "../../types/research";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bottom action row
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Bottom action row of the message composer.
|
|
3
|
+
*
|
|
4
|
+
* Renders the file-upload/settings dropdown, an optional microphone button when speech input is supported,
|
|
5
|
+
* and a send/stop-streaming button.
|
|
4
6
|
*/
|
|
5
7
|
import React from "react";
|
|
6
8
|
interface ActionBarProps {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Central `Icons` export map of Lucide icons and custom SVGs
|
|
3
|
-
*
|
|
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.
|
|
4
6
|
*/
|
|
5
7
|
import React from "react";
|
|
6
8
|
export declare const Icons: {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Card shown in the attachment tray for large pasted text blocks.
|
|
3
|
+
*
|
|
4
|
+
* Renders a truncated preview of clipboard text pasted into the input (>300 chars) with a "PASTED" label
|
|
5
|
+
* and a hover-revealed remove button.
|
|
4
6
|
*/
|
|
5
7
|
import React from "react";
|
|
6
8
|
import { PastedContent } from "../../types/research";
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Package-wide configuration: app branding, defaults, and the auth client contract, overridable via configureResearchAgentUI.
|
|
3
|
+
*/
|
|
1
4
|
export interface FooterLink {
|
|
2
5
|
url: string;
|
|
3
6
|
text: string;
|
|
@@ -54,6 +57,15 @@ export interface ResearchAgentUIConfig {
|
|
|
54
57
|
* optionally deep-links to a specific settings tab.
|
|
55
58
|
*/
|
|
56
59
|
onOpenSettings?: (section?: string) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Requests that a chat from history be opened in place. Lets the consuming
|
|
62
|
+
* app switch to the chat as a tab within its current workspace (e.g. on
|
|
63
|
+
* the homepage) instead of navigating to the `/c/<chatId>` route. Return
|
|
64
|
+
* `true` when handled — the caller then skips route navigation; return
|
|
65
|
+
* `false`/`undefined` (or leave unset) to fall back to navigating to
|
|
66
|
+
* `/c/<chatId>`.
|
|
67
|
+
*/
|
|
68
|
+
onOpenChat?: (chatId: string) => boolean;
|
|
57
69
|
}
|
|
58
70
|
export declare const researchAgentUIConfig: ResearchAgentUIConfig;
|
|
59
71
|
/**
|
|
@@ -104,6 +104,20 @@ export interface ChatContextValue {
|
|
|
104
104
|
* Resets all messages, history, and generates a new chat ID.
|
|
105
105
|
*/
|
|
106
106
|
newChat: () => void;
|
|
107
|
+
/**
|
|
108
|
+
* Starts a brand-new chat session with a caller-supplied ID, without
|
|
109
|
+
* navigating away from the current route. Unlike `newChat`, this does not
|
|
110
|
+
* push to `/` — useful for hosts that render `ChatWindow` inline (e.g. as
|
|
111
|
+
* a tab) and want to control the new chat's ID up front.
|
|
112
|
+
* @param id - The ID to assign to the new chat session.
|
|
113
|
+
*/
|
|
114
|
+
startNewChat: (id: string) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Switches the active chat to an existing chat ID and loads its messages,
|
|
117
|
+
* without navigating away from the current route.
|
|
118
|
+
* @param id - The ID of the existing chat session to switch to.
|
|
119
|
+
*/
|
|
120
|
+
switchToChat: (id: string) => void;
|
|
107
121
|
/**
|
|
108
122
|
* Whether incognito mode is active.
|
|
109
123
|
* When true, messages are not saved to history or cloud.
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface SpeechInputOptions {
|
|
2
|
+
/** The in-progress phrase, replacing whatever was reported before it. */
|
|
3
|
+
onPartial?: (text: string) => void;
|
|
4
|
+
/** BCP-47 language tag for the browser recognizer. */
|
|
5
|
+
language?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SpeechInputReturn {
|
|
2
8
|
isListening: boolean;
|
|
3
9
|
toggleSpeech: () => Promise<void>;
|
|
4
10
|
isSpeechSupported: boolean;
|
|
5
|
-
|
|
11
|
+
/** The phrase currently being spoken; empty between phrases. */
|
|
12
|
+
partial: string;
|
|
13
|
+
/** The most recent thing heard, partial or settled. */
|
|
14
|
+
lastPhrase: string;
|
|
15
|
+
/** Increments on every `lastPhrase` update, repeats included. */
|
|
16
|
+
phraseId: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function useSpeechInput(
|
|
19
|
+
/** Called once per settled phrase — append it, do not replace the input. */
|
|
20
|
+
onTranscript: (transcript: string) => void, onEnd: () => void, options?: SpeechInputOptions): SpeechInputReturn;
|