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

@e-llm-studio/requirement-ai

v0.0.17

Published

---

Readme

RequirementAIWrapper


Props

| Prop | Type | | -------------- | ------------------------------------------------------------ | | userStory | IUserStory \| null | | setUserStory | React.Dispatch<React.SetStateAction<IUserStory \| null>> | | aiReasoning | TAIReasoning[] | | setAiReasoning | React.Dispatch<React.SetStateAction<TAIReasoning[]>> | | citations | UserStoryCitations[] | | setCitations | React.Dispatch<React.SetStateAction<UserStoryCitations[]>> | | children | ReactNode |


Data Structures

IUserStory

export interface IUserStory {
  artifactId: string;
  title: string;
  value: string;
  sub_features?: IUserStory[];

  storyNumber?: string;
  description?: string;
  section?: "intro" | "subfeature";
  blockNumber?: string;

  citation?: UserStoryCitations;
  aiReasoning?: TAIReasoning[];

  isEnd?: boolean;
}

TAIReasoning

export type TAIReasoning = {
  id: string;
  reason: string;
  gap: string;
  relevance_score: number;
  reasonSource?: string;
  gapSource?: string;
  isOpen?: boolean;
  relevance_score_diff?: number;
  explanation?: string;
};

UserStoryCitations

export type UserStoryCitations = {
  image_citations?: ImageCitation[];
  appmod_citations?: TextCitation[];
  audio_citations?: AudioCitation[];
  file_citations?: FileCitation[];
  file_image_data?: ImageCitationsForFile | null;
  rca_citations?: RcaCitation[];
  web_citations?: WebCitation[];
  code_generated?: GeneratedCodeCitation[];
};

ImageCitationsForFile

interface ImageCitationsForFile {
  [key: string]: FileImageData;
}

FileImageData

interface FileImageData {
  filename: string;
  signed_url: string;
  gs_url: string;
}

Usage

Create State

const [userStory, setUserStory] = useState<IUserStory | null>(null);

const [aiReasoning, setAiReasoning] = useState<TAIReasoning[]>([]);

const [citations, setCitations] = useState<UserStoryCitations[]>([]);

Wrap Components

<RequirementAIWrapper
  userStory={userStory}
  setUserStory={setUserStory}
  aiReasoning={aiReasoning}
  setAiReasoning={setAiReasoning}
  citations={citations}
  setCitations={setCitations}
>
  {children}
</RequirementAIWrapper>

Context Value

The wrapper provides the following context value:

{
  (userStory,
    setUserStory,
    aiReasoning,
    setAiReasoning,
    citations,
    setCitations);
}

Usage

import { useStreamContentHandler } from "@e-llm-studio/requirement-ai";

function StreamingComponent() {
  const { processStreamContent } = useStreamContentHandler({
    setUserStory,
    setAiReasoning,
    setUserStoryCitations,
    onStreamingCompleted,
    isGraphQLCallInProgress,
    knowledgeBase,
    projectAnalyserSummary,
    dataSourceFiles,
    aiReasoning,
    setRedisValue,
    fetchCitationByBlock,
  });

  const handleStream = (blockList) => {
    processStreamContent(blockList, metadata, "story-artifact-id");
  };

  return <div>Streaming...</div>;
}

Hook API

useStreamContentHandler(props)

Returns

{
  processStreamContent: (
    blockList: BlockStreamItem[],
    customMetadata: any,
    storyArtifactID: string
  ) => void
}

Props

setUserStory

React state setter used to store generated user stories.

React.Dispatch<React.SetStateAction<IUserStory[]>>;

setAiReasoning

State setter used to store extracted AI reasoning.

React.Dispatch<React.SetStateAction<TAIReasoning[]>>;

setUserStoryCitations

State setter used to store citations generated from blocks.

React.Dispatch<React.SetStateAction<UserStoryCitations>>;

onStreamingCompleted

Callback executed once all citation generation tasks have completed.

(gap_items_prompt?: string, risk_items_prompt?: string) => Promise<void>;

isGraphQLCallInProgress

Boolean flag indicating whether a GraphQL call is currently running.

This prevents the completion callback from firing prematurely.

boolean;

knowledgeBase

Knowledge base metadata associated with the current stream.

any;

projectAnalyserSummary

Project analysis summary used during processing.

any;

dataSourceFiles

Information about uploaded data source files.

{
  document: string[];
  image: string[];
  meetingRecording: string[];
}

aiReasoning

Existing AI reasoning state.

TAIReasoning[]

setRedisValue

Function responsible for storing streaming state in Redis.

<T>({ key, value }: SetRedisParams<T>) => Promise<any>;

Example implementation:

async function setRedisValue({ key, value }) {
  await redis.set(key, JSON.stringify(value));
}

fetchCitationByBlock

Async function responsible for generating citations for a given block.

(block: BlockStreamItem, metadata: any, ai_reasonings: TAIReasoning[]) =>
  Promise<IGetCitationsByBlock>;

Types

BlockStreamItem

Represents a streamed AI block.

type BlockStreamItem = {
  title: string;
  section: "intro" | "subfeature";
  description?: string;
  blockNumber: string;
  value: string;
  storyNumber: string;
};

Streaming Flow

AI Stream
   ↓
Block List
   ↓
processStreamContent
   ↓
Reasoning Extraction
   ↓
User Story Generation
   ↓
Citation Generation
   ↓
Redis Persistence
   ↓
Streaming Completed Callback

Internal Safeguards

The hook includes several mechanisms to optimize streaming performance:

| Mechanism | Purpose | | ---------------------------------- | ----------------------------------------- | | updateCitationCallKeysRef | Prevents duplicate citation requests | | citationGenerationCompletedKeysRef | Tracks completed citation tasks | | lastProcessedBlockListHashRef | Avoids reprocessing identical block lists | | lastRedisValueRef | Prevents redundant Redis writes |


Best Practices

  • Ensure fetchCitationByBlock is idempotent.
  • Avoid extremely frequent calls to processStreamContent without batching.
  • Always serialize Redis values using JSON.

UserStoryScreen Component

A composable UI component system for displaying and editing user stories with AI reasoning citations. It must be wrapped within RequirementAIWrapper to access shared state.

Quick Demo

import React, { useState } from "react";
import RequirementAIWrapper from "@e-llm-studio/requirement-ai";
import UserStoryScreen from "@e-llm-studio/requirement-ai";
import UserStoryWithCitations from "@e-llm-studio/requirement-ai";
import RichTextEditor from "@e-llm-studio/requirement-ai";
import AtomicButton from "@e-llm-studio/requirement-ai";

function MyStoryApp() {
  const [userStory, setUserStory] = useState<IUserStory[]>([
    {
      artifactId: "story-1",
      title: "User Login",
      value: "As a user, I want to login to the system",
    },
  ]);

  const [aiReasoning, setAiReasoning] = useState<TAIReasoning[]>([]);
  const [citations, setCitations] = useState<UserStoryCitations[]>([]);

  return (
    <RequirementAIWrapper
      userStory={userStory}
      setUserStory={setUserStory}
      aiReasoning={aiReasoning}
      setAiReasoning={setAiReasoning}
      citations={citations}
      setCitations={setCitations}
      apiCachingConfig={{
        queryClient: null, // replace with original tanstack-query client
        useGetSignedUrlQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
        useGetSignedUrlMutation: (gsUtilPath: string) => null, // replace with original hook
        useExtractAudioPeaksMutation: () => null, // replace with original hook
        useExtractAudioPeaksQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
        useGetVideoSignedUrlMutation: (gsUtilPath: string) => null, // replace with original hook
        useGetVideoSignedUrlQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
      }}
    >
      <UserStoryScreen scenario="ready" isCitationLoadingNotStarted={true}>
        <UserStoryScreen.Content AtomicButton={AtomicButton}>
          <UserStoryScreen.Body>
            <UserStoryScreen.Story Component={UserStoryWithCitations} />
            <UserStoryScreen.Editor Component={RichTextEditor} />
            <UserStoryScreen.SubFeatures />
          </UserStoryScreen.Body>
          <UserStoryScreen.ScrollButton />
        </UserStoryScreen.Content>
      </UserStoryScreen>
    </RequirementAIWrapper>
  );
}

Core Sub-Components

| Component | Purpose | | ------------------------------- | ----------------------------------------------------- | | UserStoryScreen.Sidebar | Navigation sidebar for story selection | | UserStoryScreen.SidebarToggle | Collapse/expand sidebar button | | UserStoryScreen.Content | Main content wrapper with header and controls | | UserStoryScreen.Body | Container for Story/Editor (handles mutual exclusion) | | UserStoryScreen.Story | Displays selected story with citations (read mode) | | UserStoryScreen.Editor | Rich text editor for editing stories (edit mode) | | UserStoryScreen.SubFeatures | Accordion for nested sub-features | | UserStoryScreen.ScrollButton | Floating button to scroll to bottom |

Root Props

interface UserStoryScreenProps {
  scenario: "streaming" | "ready"; // "streaming" for real-time, "ready" for static
  isCitationLoadingNotStarted: boolean; // Flag for citation loading state
  children: React.ReactNode; // Sub-components
  classNames?: UserStoryScreenClassNames; // Optional custom CSS classes
}

Key Features

  • Automatic State Management: All components pull from RequirementAIWrapper context
  • Mutual Exclusion: Story and Editor views are automatically exclusive
  • Streaming Support: Real-time updates with skeleton loaders
  • Citation Integration: AI reasoning and citations highlighted in stories
  • Markdown Support: Stories support markdown formatting with custom tags

Usage Notes

  1. Always wrap with RequirementAIWrapper - Provides context for all sub-components
  2. Use Body wrapper - Ensures proper Story/Editor mutual exclusion
  3. Pass custom components - Story and Editor expect React components as props
  4. Set scenario correctly - "streaming" for real-time data, "ready" for static content

UserStoryApprovalScreen Component

A dedicated approval workflow component for reviewing and approving AI-generated user stories. It must be wrapped within RequirementAIWrapper to access shared state.

Overview

UserStoryApprovalScreen provides a structured interface for:

  • Reviewing generated user stories segment-by-segment
  • Editing individual content segments
  • Approving content progressively
  • Managing sub-features with delete capabilities
  • Bulk approval with "Approve All" functionality

Quick Demo

import React, { useState } from "react";
import RequirementAIWrapper from "@e-llm-studio/requirement-ai";
import UserStoryApprovalScreen from "@e-llm-studio/requirement-ai";
import UserStoryWithCitations from "@e-llm-studio/requirement-ai";
import RichTextEditor from "@e-llm-studio/requirement-ai";
import AtomicButton from "@e-llm-studio/requirement-ai";

function MyApprovalFlow() {
  const [userStory, setUserStory] = useState<IUserStory[]>([]);
  const [aiReasoning, setAiReasoning] = useState<TAIReasoning[]>([]);
  const [citations, setCitations] = useState<UserStoryCitations[]>([]);

  return (
    <RequirementAIWrapper
      userStory={userStory}
      setUserStory={setUserStory}
      aiReasoning={aiReasoning}
      setAiReasoning={setAiReasoning}
      citations={citations}
      setCitations={setCitations}
      apiCachingConfig={{
        queryClient: null, // replace with original tanstack-query client
        useGetSignedUrlQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
        useGetSignedUrlMutation: (gsUtilPath: string) => null, // replace with original hook
        useExtractAudioPeaksMutation: () => null, // replace with original hook
        useExtractAudioPeaksQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
        useGetVideoSignedUrlMutation: (gsUtilPath: string) => null, // replace with original hook
        useGetVideoSignedUrlQuery: (gsUtilPath: string, cacheKey: string[]) => null, // replace with original hook
      }}
    >
      <UserStoryApprovalScreen
        artifactTitleIds={["STORY-001"]}
        config={{
          showApproveAllButton: true,
          allowEditing: true,
          allowDeletion: true,
          containerHeight: "calc(100vh - 100px)",
        }}
        callbacks={{
          onShowNotification: (type, title, message) => {
            console.log(`[${type}] ${title}: ${message}`);
          },
          onInitialize: () => {
            console.log("Approval screen initialized");
          },
          onSaveState: async (stories) => {
            await saveToRedis(stories);
          },
          onAllApproved: async (stories) => {
            await submitApprovedStories(stories);
          },
        }}
      >
        <UserStoryApprovalScreen.Content ButtonComponent={AtomicButton}>
          <UserStoryApprovalScreen.Body
            EditorComponent={RichTextEditor}
            ViewerComponent={UserStoryWithCitations}
          />
        </UserStoryApprovalScreen.Content>
      </UserStoryApprovalScreen>
    </RequirementAIWrapper>
  );
}

Component Structure

| Component | Purpose | | --------------------------------- | --------------------------------------------------------- | | UserStoryApprovalScreen (Root) | Container with approval state management | | UserStoryApprovalScreen.Content | Header with title, metadata, and "Approve All" button | | UserStoryApprovalScreen.Body | Renders content segments with edit/approve/delete actions |


Root Component Props

interface UserStoryApprovalScreenProps {
  artifactTitleIds: string[]; // Display IDs for each story
  config?: UserStoryApprovalScreenConfig; // Feature toggles
  callbacks?: UserStoryApprovalScreenCallbacks; // Platform-specific handlers
  labels?: Partial<UserStoryApprovalScreenLabels>; // Custom text labels
  isDisabled?: boolean; // Disable all interactions
  children: React.ReactNode; // Sub-components
  classNames?: UserStoryApprovalScreenClassNames; // Custom CSS classes
}

Configuration Options

interface UserStoryApprovalScreenConfig {
  showApproveAllButton?: boolean; // Show/hide "Approve All" button (default: true)
  allowEditing?: boolean; // Enable editing segments (default: true)
  allowDeletion?: boolean; // Enable deleting sub-features (default: true)
  showSidebar?: boolean; // Show sidebar for multi-story navigation (default: false)
  containerHeight?: string; // CSS height value (default: "calc(100vh - 100px)")
  useFullWidth?: boolean; // Use 100% width without margins (default: false)
}

Callback Handlers

interface UserStoryApprovalScreenCallbacks {
  /** Show toast/notification - required for user feedback */
  onShowNotification?: (
    type: "success" | "error" | "warn" | "info",
    title: string,
    message: string,
    duration?: number,
  ) => void;

  /** Initialize platform (e.g., adjust chat width in web app) */
  onInitialize?: () => void;

  /** Persist approval state to Redis/storage */
  onSaveState?: (stories: IUserStoryForApproval[]) => Promise<void>;

  /** Triggered when all stories are approved */
  onAllApproved?: (stories: IUserStoryForApproval[]) => Promise<void>;

  /** Custom scroll behavior for focusing segments */
  onScrollToElement?: (element: HTMLElement) => void;
}

Accessing Context

Use useRequirementAI() hook within UserStoryScreen components:

import { useRequirementAI } from "@e-llm-studio/requirement-ai";

function MyCustomComponent() {
  const { userStory, setUserStory, aiReasoning } = useRequirementAI();
  return <div>{userStory?.[0]?.title}</div>;
}

🧩 Diff Edit Utilities – Usage Guide

1. handleDiffEditApplied

Applies AI diff response to user stories, reasoning, and chat history.

handleDiffEditApplied(
  aiResponseString,
  selectedUserStoryType,
  username,
  setUserStory,
  setAiReasoning,
  setAiReasoningDiffView,
  setChatHistory,
  setUserStoryDiffView
);

Use when:

  • You receive AI diff-edit response
  • You want to update UI with new stories, reasoning, and chat

2. handleDiscardAll

Rejects all diff changes and restores original state.

handleDiscardAll(
  userStory,
  setUserStory,
  setAiReasoning,
  setUserStoryDiffView
);

Use when:

  • User clicks “Discard All Changes”
  • You want to revert to original content

3. handleAcceptAllChanges

Accepts all diff changes and updates user stories.

handleAcceptAllChanges(
  userStory,
  setUserStory,
  setIsAutoSaveEnabled
);

License

This library is released under the MIT License. See the LICENSE file for more details.

The license information is also available in the citation/package.json file under the 'license' field.