@e-llm-studio/requirement-ai
v0.0.17
Published
---
Maintainers
Keywords
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 CallbackInternal 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
fetchCitationByBlockis idempotent. - Avoid extremely frequent calls to
processStreamContentwithout 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
RequirementAIWrappercontext - 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
- Always wrap with
RequirementAIWrapper- Provides context for all sub-components - Use
Bodywrapper - Ensures proper Story/Editor mutual exclusion - Pass custom components - Story and Editor expect React components as props
- 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.
