Lore Headless
A headless library for chat streaming functionality that works with React and other frontend frameworks.

Installation
npm install @mencraft/lore-headless
Configuration
When using this library, you'll need to provide the following authentication values:
const auth = {
clientId: 'your-client-id',
token: 'your-firebase-id-token'
};
const graphId = 'your-graph-id';
API URLs
By default, the API client is configured for production URLs. You can specify different modes or use custom URLs:
const apiClient = new LoreClient();
const stagingClient = new LoreClient('staging');
const devClient = new LoreClient('development');
const customClient = new LoreClient(
'production',
'https://your-custom-chat-url.com',
'https://your-custom-validation-url.com'
);
Available modes:
'production' (default): Uses production API URLs
'staging': Uses staging API URLs
'development': Uses local development URLs (localhost)
Custom URLs:
When providing custom URLs, both customChatUrl and customValidationUrl must be provided together. If custom URLs are specified, the mode parameter will be ignored.
In a real application, you would typically set the authentication values using environment variables:
const auth = {
clientId: process.env.CLIENT_ID,
token: process.env.FIREBASE_TOKEN
};
const graphId = process.env.GRAPH_ID;
Usage
Core API (Framework Agnostic)
import { LoreClient, ChatStreamManager, ChatRequest, ChatStreamCallbacks } from 'lore-headless';
const apiClient = new LoreClient();
const auth = {
clientId: 'your-client-id',
token: 'your-firebase-id-token'
};
const callbacks: ChatStreamCallbacks = {
onTextStream: (text) => {
console.log('Received text:', text);
},
onBatchData: (data) => {
console.log('Received batch data:', data);
},
onError: (error) => {
console.error('Error:', error);
},
onEnd: () => {
console.log('Stream ended');
}
};
const chatManager = new ChatStreamManager(apiClient, auth, callbacks);
const request: ChatRequest = {
user: 'user-id',
session: 'session-id',
query: 'Hello, how are you?'
};
chatManager.startChatStream(request, 'graph-id');
chatManager.cancelStream();
chatManager.sendFeedback({
user: 'user-id',
session: 'session-id',
query: 'Hello, how are you?',
response: 'I am doing well, thank you!',
feedback: 'Great response!',
feedback_value: 1
}, 'graph-id');
React Hooks
To use the React hooks, you need to import them from the React entry point:
Callback-based Approach (Original)
import React, { useState } from 'react';
import { LoreClient, ChatRequest } from 'lore-headless';
import { useChatStream, useFeedback } from 'lore-headless/react';
interface Message {
id: string;
text: string;
sender: 'user' | 'assistant' | 'system' | 'error';
timestamp: Date;
}
interface FeedbackModalState {
isOpen: boolean;
messageId: string;
assistantMessage: string;
userMessage: string;
}
const ChatComponent = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [feedbackModal, setFeedbackModal] = useState<FeedbackModalState>({
isOpen: false,
messageId: '',
assistantMessage: '',
userMessage: ''
});
const [feedbackText, setFeedbackText] = useState('');
const [feedbackRating, setFeedbackRating] = useState<number | null>(null);
const apiClient = new LoreClient();
const auth = {
clientId: 'your-client-id',
token: 'your-firebase-id-token'
};
const { startChatStream, cancelStream } = useChatStream(apiClient, auth, {
onTextStream: (text) => {
setMessages((prev) => {
const lastMessage = prev[prev.length - 1];
if (lastMessage?.sender === 'assistant') {
return [
...prev.slice(0, -1),
{ ...lastMessage, text: lastMessage.text + text }
];
}
return [
...prev,
{
id: Date.now().toString(),
text,
sender: 'assistant',
timestamp: new Date()
}
];
});
},
onError: (error) => {
console.error('Stream error:', error);
},
onEnd: () => {
console.log('Stream ended');
}
});
const { sendFeedback, loading } = useFeedback(apiClient, auth, 'graph-id');
const handleSendMessage = (message: string) => {
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
text: message,
sender: 'user',
timestamp: new Date()
}
]);
const request: ChatRequest = {
user: 'user-id',
session: 'session-id',
query: message
};
startChatStream(request, 'graph-id');
};
const openFeedbackModal = (messageId: string) => {
const assistantMessage = messages.find(m => m.id === messageId && m.sender === 'assistant');
if (!assistantMessage) return;
const userMessageIndex = messages.findIndex(m => m.id === messageId) - 1;
if (userMessageIndex < 0) return;
const userMessage = messages[userMessageIndex];
if (userMessage.sender !== 'user') return;
setFeedbackModal({
isOpen: true,
messageId,
assistantMessage: assistantMessage.text,
userMessage: userMessage.text
});
setFeedbackText('');
setFeedbackRating(null);
};
const closeFeedbackModal = () => {
setFeedbackModal(prev => ({ ...prev, isOpen: false }));
};
const submitFeedback = () => {
if (feedbackRating === null) {
alert('Please select a rating before submitting.');
return;
}
sendFeedback({
user: 'user-id',
session: 'session-id',
query: feedbackModal.userMessage,
response: feedbackModal.assistantMessage,
feedback: feedbackText || (feedbackRating > 0 ? 'Helpful response' : 'Not helpful response'),
feedback_value: feedbackRating
});
closeFeedbackModal();
};
return (
<div className="chat-container">
{/* Messages list */}
<div className="messages">
{messages.map((message) => (
<div key={message.id} className={`message ${message.sender}`}>
<div className="message-content">{message.text}</div>
<div className="message-timestamp">
{message.timestamp.toLocaleTimeString()}
</div>
{/* Add feedback button to assistant messages */}
{message.sender === 'assistant' && (
<button
className="feedback-button"
onClick={() => openFeedbackModal(message.id)}
>
Give Feedback
</button>
)}
</div>
))}
</div>
{/* Message input form */}
<form
className="input-form"
onSubmit={(e) => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem('message') as HTMLInputElement;
if (input && input.value.trim()) {
handleSendMessage(input.value.trim());
input.value = '';
}
}}
>
<input
type="text"
name="message"
placeholder="Type your message..."
disabled={loading}
/>
<button type="submit" disabled={loading}>Send</button>
</form>
{/* Feedback modal */}
{feedbackModal.isOpen && (
<div className="modal-overlay">
<div className="modal-content">
<div className="modal-header">
<h3>Provide Feedback</h3>
<button className="close-button" onClick={closeFeedbackModal}>×</button>
</div>
<div className="modal-body">
<div className="message-context">
<div className="context-label">Assistant's response:</div>
<div className="context-text">{feedbackModal.assistantMessage}</div>
</div>
<textarea
className="feedback-textarea"
placeholder="Enter your feedback here..."
value={feedbackText}
onChange={(e) => setFeedbackText(e.target.value)}
/>
<div className="rating-container">
<div className="rating-label">How would you rate this response?</div>
<button
className={`rating-button positive ${feedbackRating === 1 ? 'selected' : ''}`}
onClick={() => setFeedbackRating(1)}
>
👍 Helpful
</button>
<button
className={`rating-button negative ${feedbackRating === -1 ? 'selected' : ''}`}
onClick={() => setFeedbackRating(-1)}
>
👎 Not Helpful
</button>
</div>
<button
className="submit-button"
onClick={submitFeedback}
disabled={loading}
>
Submit Feedback
</button>
</div>
</div>
</div>
)}
</div>
);
};
Running the Examples
Vanilla JavaScript Example
To run the vanilla JavaScript example:
-
First, build the package:
npm run build
-
Start the example server:
node examples/vanilla/server.js
-
Open your browser and navigate to the URL shown in the console.
The server will automatically find an available port, starting from 3000.
Note: The vanilla example uses ES modules, which require a web server to work properly due to CORS restrictions when loading modules from the file system.
React Example
The React example is provided as a reference implementation. To use it in a real React application:
Package Structure
The package is structured to be truly headless, with React-specific functionality separated into a dedicated entry point:
- Main Entry Point: Core functionality that works with any framework
- React Entry Point: React-specific hooks that depend on React
This structure allows you to use the package with or without React, depending on your needs.
Development
To develop this package:
-
Install dependencies:
npm install
-
Build the package:
npm run build
-
Run tests:
npm test
API Reference
Core
LoreClient
A client for making API requests with support for different environments.
const apiClient = new LoreClient();
const stagingClient = new LoreClient('staging');
const devClient = new LoreClient('development');
const customClient = new LoreClient(
'production',
'https://your-custom-chat-url.com',
'https://your-custom-validation-url.com'
);
Constructor Parameters:
mode (string, optional): Environment mode. Options: 'production' (default), 'staging', 'development'
customChatUrl (string, optional): Custom chat URL. Must be provided together with customValidationUrl
customValidationUrl (string, optional): Custom validation URL. Must be provided together with customChatUrl
Environment Modes:
- Production (
'production'): Uses production API endpoints
- Staging (
'staging'): Uses staging API endpoints for testing
- Development (
'development'): Uses local development endpoints (localhost)
Custom URLs:
When custom URLs are provided, both customChatUrl and customValidationUrl must be specified together. The mode parameter will be ignored in this case.
ChatStreamManager
A manager for handling chat streams.
const chatManager = new ChatStreamManager(apiClient, auth, callbacks);
React Hooks
Message-based Approach (Enhanced)
The library now provides an enhanced version of the useChatStream hook that manages messages internally and returns them as part of the hook's return value. This simplifies message management and provides a more streamlined API.
import React from 'react';
import { LoreClient, LLMRole } from 'lore-headless';
import { useChatStream, useFeedback, ChatMessage } from 'lore-headless/react';
const ChatComponent = () => {
const [inputText, setInputText] = useState('');
const {
messages,
isStreaming,
startChatStream,
cancelStream,
clearMessages
} = useChatStream(
apiClient,
auth,
{
onSystemMessage: (text) => {
console.log('System message:', text);
},
}
);
const handleSendMessage = async () => {
if (!inputText.trim() || isStreaming) return;
setInputText('');
try {
await startChatStream(
{
user: 'user-id',
session: 'session-id',
query: inputText,
},
'graph-id'
);
} catch (error) {
console.error('Failed to start chat stream:', error);
}
};
return (
<div className="chat-container">
{/* Messages list */}
<div className="messages">
{messages.map((message) => (
<div
key={message.id}
className={`message ${message.role}`}
>
<div className="message-header">
{message.role.charAt(0).toUpperCase() + message.role.slice(1)}
{message.isStreaming && <span className="streaming-indicator" />}
</div>
<div className="message-content">{message.content}</div>
<div className="message-timestamp">
{message.timestamp.toLocaleTimeString()}
</div>
</div>
))}
</div>
{/* Message input form */}
<div className="input-container">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Type your message..."
disabled={isStreaming}
/>
<button
onClick={handleSendMessage}
disabled={isStreaming || !inputText.trim()}
>
Send
</button>
{isStreaming && (
<button onClick={cancelStream}>
Cancel
</button>
)}
<button onClick={clearMessages}>
Clear Chat
</button>
</div>
</div>
);
};
The ChatMessage interface provides a structured way to represent different types of messages:
interface ChatMessage {
id: string;
role: LLMRole | 'error';
content: string;
timestamp: Date;
isStreaming?: boolean;
}
Benefits of the message-based approach:
- Automatic message management (no need to manually track messages)
- Built-in support for streaming indicators
- Consistent message structure with timestamps and IDs
- Support for different message types (user, assistant, system, error)
- Simplified API with fewer lines of code
useChatStream
A hook for using the chat stream in React.
import { useChatStream } from 'lore-headless/react';
const { startChatStream, cancelStream } = useChatStream(apiClient, auth, callbacks);
const { messages, isStreaming, startChatStream, cancelStream, clearMessages } = useChatStream(apiClient, auth, callbacks);
useFeedback
A hook for sending feedback in React.
import { useFeedback } from 'lore-headless/react';
const { sendFeedback, loading, error, data } = useFeedback(apiClient, auth, graphId);
License
MIT