research-agent-ui
v0.1.93
Published
Chat research agent UI: conversation window, article reader, search config, file uploads, and chat history
Maintainers
Readme
research-agent-ui
Chat research agent UI: conversation window, article reader, search config, file uploads, and chat history for QwkSearch-style apps. Includes the shadcn primitives and icons the components depend on, so it can be dropped into a Next.js app with a single dependency.

Usage
import {
ChatProvider,
SessionProvider,
ExtractPanelProvider,
ChatWindow,
configureResearchAgentUI,
} from 'research-agent-ui';
configureResearchAgentUI({
appName: 'MyApp',
getAutoMediaSearch: () => true,
// ...see ResearchAgentUIConfig for the full list of overridable values
});
function App() {
return (
<SessionProvider authClient={myAuthClient}>
<ExtractPanelProvider>
<ChatProvider>
<ChatWindow />
</ChatProvider>
</ExtractPanelProvider>
</SessionProvider>
);
}Storybook
Individual UI pieces can be browsed in isolation with Storybook.
Stories render against mock data only (see src/stories/mocks.ts) — no API,
auth session, or chat backend is required, so you can develop and review the
chat components (message header with timestamp/copy/edit actions, search
progress, follow-up suggestions, file & pasted-content cards) on their own.
# from packages/research-agent-ui
bun run storybook # dev server on http://localhost:6006
bun run build-storybook # static build → storybook-static/A light/dark toggle in the toolbar switches between the two token palettes
(mirrored from the host app's globals.css in .storybook/preview.css). Add
new stories next to their component as *.stories.tsx.
API Routes (research-agent-ui/api)
All 25 Next.js route handlers are exported from the research-agent-ui/api
subpath as factory functions. Each factory accepts a deps object that
injects your app's database, auth helpers, and other services, so the same
handler logic works in any Next.js project without hard-coding any imports.
How it works
The route logic lives in packages/research-agent-ui/src/api/handlers/.
Your app's app/api/agent/*/route.ts files become thin wrappers that call
the factory and re-export the HTTP method handlers.
Step 1 — install / workspace link
If you are in this monorepo, research-agent-ui is already linked via the
workspace:* protocol. For an external project, install the published
package:
npm install research-agent-ui
# or
bun add research-agent-uiStep 2 — create your route files
For each API path, create a route.ts that calls the matching factory and
passes in your app's dependencies. Every factory is named
create<RouteName>Handler and is exported from research-agent-ui/api.
Example: app/api/agent/chats/route.ts
import { createChatsHandler } from "research-agent-ui/api";
import { getDB } from "@/lib/database";
import { chats, messages } from "@/lib/database/schema";
import { requireUserId } from "@/lib/auth/session";
const handler = createChatsHandler({
getDB,
requireUserId,
schema: { chats, messages },
});
export const { GET, DELETE } = handler;Example: app/api/agent/article-followups/route.ts
import { createArticleFollowupsHandler } from "research-agent-ui/api";
import { getUserId } from "@/lib/auth/session";
import { getDB } from "@/lib/database";
import { user as userSchema } from "@/lib/database/schema";
import { getEnv } from "@/lib/env";
export const POST = createArticleFollowupsHandler({
getUserId,
requireUserId: async () => {
const id = await getUserId();
if (!id) throw new Error("Unauthorized");
return id;
},
getDB,
userSchema,
getEnv,
});All available factories and their dep shapes
| Factory | File | Required deps |
|---|---|---|
| createArticleFollowupsHandler | article-followups | getUserId, requireUserId, getDB, userSchema, getEnv |
| createArticleQAHandler | article-qa | getUserId, requireUserId, getDB, userSchema, getEnv |
| createChatsHandler | chats | getDB, requireUserId, schema.chats, schema.messages |
| createChatByIdHandler | chats/[id] | getDB, requireUserId, schema.chats, schema.messages |
| createChatsSearchHandler | chats/search | getDB, requireUserId, schema.chats, schema.messages |
| createChatsShareHandler | chats/share | getDB, requireUserId, schema.chats, schema.messages |
| createMessagesHandler | messages | getDB, requireUserId, messagesSchema |
| createProvidersHandler | providers | getSession |
| createProviderByIdHandler | providers/[id] | (none) |
| createProviderModelsHandler | providers/[id]/models | (none) |
| createMCPServersHandler | mcpservers | configManager, getConfiguredMCPServers |
| createMCPServerByIdHandler | mcpservers/[id] | configManager, getConfiguredMCPServers |
| createMCPServerToggleHandler | mcpservers/[id]/toggle | configManager, getConfiguredMCPServers |
| createSearchHandler | search | searxngDomain? (default: https://search.qwksearch.com) |
| createDiscoverHandler | discover | (none) |
| createAutocompleteHandler | autocomplete | (none) |
| createSuggestionsHandler | suggestions | (none) |
| createAgentsHandler | agents | getUserId, requireUserId, getDB, userSchema, getEnv |
| createRewriteHandler | rewrite | getEnv, generateText, createGroq |
| createVoiceHandler | voice | getUserId, checkTTSRateLimit, generateSpeech |
| createTranscriptHandler | transcript | getCloudflareContext |
| createTestModelsHandler | test-models | (none) |
| createValidateOpenRouterHandler | validate-openrouter | validateOpenRouterModels |
Dep type definitions
All dep interfaces are exported from research-agent-ui/api:
import type {
ArticleDeps,
ChatsDeps,
MessagesDeps,
ProvidersDeps,
MCPServersDeps,
SearchDeps,
VoiceDeps,
TranscriptDeps,
RewriteDeps,
ValidateOpenRouterDeps,
AgentsDeps,
} from "research-agent-ui/api";The chat route
POST /api/agent/chat is not migrated into this package because it delegates
to a full handleChatRequest orchestrator that is app-specific (streaming,
search integration, database writes). Keep it directly in your app:
// app/api/agent/chat/route.ts
import { handleChatRequest } from "@/lib/chat";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export const POST = handleChatRequest;Configuration
configureResearchAgentUI overrides app-specific values (branding strings,
the Google API key used by the Drive picker, and the auto-media-search
toggle) that would otherwise couple this package to a specific app. See
ResearchAgentUIConfig in src/config.ts for the full list.
