npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

research-agent-ui

v0.1.261

Published

The QwkSearch app UI: conversation window, article reader, search config, file uploads, chat history, and the app shell — exported both with and without the REASON editor and its sidebar

Readme

research-agent-ui

The QwkSearch app UI: conversation window, article reader, search config, file uploads, chat history — plus the app shell (providers, app dock, cookie banner, the research/docs view switch) that assembles them into a whole app. Includes the shadcn primitives and icons the components depend on, so it can be dropped into a Next.js app with a single dependency.

img1

Two entry points: with and without the editor

The package ships the same app twice, and the only difference is whether the REASON document editor and its file sidebar come along:

| Import from | You get | Extra dependencies | | --- | --- | --- | | research-agent-ui | Chat, search, article reader, app shell | none | | research-agent-ui/workspace | All of the above plus the REASON editor, document tree, and file sidebar | react-reason-editor, react-reason-editor-sidebar |

research-agent-ui/workspace re-exports everything the root entry does, so a host that wants documents imports from that one path rather than mixing the two. Going the other way, the root entry's import graph never reaches react-reason-editor — the editor's (large) dependency tree stays out of a chat-only consumer's bundle entirely. test/entryBoundaries.test.ts enforces that in both directions.

The two editor packages are declared as optional peer dependencies: installing research-agent-ui on its own is enough for the chat-only build, and package managers will not warn about the missing peers.

Spotlight search

QwkSearchProviders mounts a macOS-Spotlight-style command palette over the whole app. Ctrl Space opens it (Cmd-Space belongs to macOS), or call openSpotlight() from your own chrome. Typing searches past chats, app pages, settings sections and actions at once, with "ask the research agent this" pinned to the top; a leading letter scopes the search to one source — c chats, t pages, s settings, a actions, w ask — and Tab cycles between them.

import { QwkSearchProviders, openSpotlight } from 'research-agent-ui';

<QwkSearchProviders authClient={authClient} showSpotlight>   {/* the default */}
  <YourApp />
</QwkSearchProviders>;

// …and from a button somewhere in your own chrome:
<button onClick={() => openSpotlight()}>Search everything</button>;

Rows that open a chat or a settings section go through onOpenChat / onOpenSettings first, so a host rendering chats inline handles them without navigating; returning anything but true falls back to /c/<id> and /settings/<section>.

Usage

The whole app in one component

// Chat only — no editor, no sidebar.
import { QwkSearchApp } from 'research-agent-ui';

export default function Page() {
  return (
    <QwkSearchApp
      authClient={myAuthClient}
      config={{ appName: 'MyApp', footerLinks: myLinks }}
    />
  );
}
// The same app, with documents.
import { QwkSearchWorkspaceApp } from 'research-agent-ui/workspace';

export default function Page() {
  return (
    <QwkSearchWorkspaceApp
      authClient={myAuthClient}
      config={{ appName: 'MyApp', footerLinks: myLinks }}
    />
  );
}

QwkSearchProviders is the same shell without a page inside it, for hosts that render their own routes within the app chrome. It accepts a ChromeProvider to mount app-owned context (a settings modal, say) inside the stack, and showDock / showCookieConsent / showToaster to opt out of individual pieces.

Composing the pieces yourself

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-ui

Step 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.