@optilogic/chat
v1.5.0
Published
Chat UI components for Optilogic - AgentResponse and related components for LLM interactions
Readme
@optilogic/chat
Chat UI components for opti-ui - Components for displaying LLM/AI agent interactions.
Installation
npm install @optilogic/chat @optilogic/core @optilogic/editor slate slate-react slate-historySetup
Make sure you have configured @optilogic/core with the Tailwind preset and CSS variables.
Usage
AgentResponse
Display AI agent responses with thinking indicators, tool calls, and feedback actions:
import { AgentResponse, useAgentResponseAccumulator, type FeedbackValue } from '@optilogic/chat';
import { useState } from 'react';
function ChatView({ socket }) {
const { state, handleMessage } = useAgentResponseAccumulator();
const [feedback, setFeedback] = useState<FeedbackValue>(null);
socket.onmessage = (e) => handleMessage(JSON.parse(e.data));
return (
<AgentResponse
state={state}
feedback={feedback}
onFeedbackChange={setFeedback}
onResponseCopy={(response) => console.log('Copied:', response)}
/>
);
}Feedback follow-up (thumbs up/down)
The action bar always renders thumbs up/down controls, and by default a thumbs-down opens our follow-up popover (category + free-text) — this works out of the box with no state wiring, because vote and popover state are uncontrolled by default. To use the default, just hook up persistence:
// Thumbs + follow-up popover work as-is; wire only what you want to store.
<AgentResponse
state={state}
onFeedbackChange={(vote) => saveVote(vote)}
onFeedbackDetailsSubmit={(details) => saveFeedbackDetails(details)}
/>Turn the follow-up off with feedbackPrompt="none", or customize it via
feedbackCategories, feedbackPromptTitle, etc. Take control of any piece by
passing its controlled prop (feedback, feedbackDetails, feedbackPromptOpen).
A vote records instantly and is never blocked on the follow-up. The library owns
the UI and accessibility; persistence and any "send to support" routing stay
with you via typed callbacks.
import {
AgentResponse,
emptyFeedbackDetails,
type FeedbackValue,
type FeedbackDetails,
type SupportFeedbackPayload,
} from '@optilogic/chat';
import { useState } from 'react';
function ResponseWithFeedback({ state, messageId, conversationId }) {
const [feedback, setFeedback] = useState<FeedbackValue>(null);
const [details, setDetails] = useState<FeedbackDetails>(emptyFeedbackDetails);
return (
<AgentResponse
state={state}
feedback={feedback}
onFeedbackChange={(vote) => {
setFeedback(vote);
// Attribute to your account/message/conversation and upsert (not insert)
// so vote changes update the same record.
void saveVote({ messageId, conversationId, vote });
}}
// "down" (default) | "up" | "both" | "none"
feedbackPrompt="down"
feedbackPromptTitle="Help us improve Ada — what went wrong?"
feedbackDetails={details}
onFeedbackDetailsChange={setDetails}
onFeedbackDetailsSubmit={(d) => {
void saveFeedbackDetails({ messageId, conversationId, ...d });
}}
// Opt-in: render a "send to support" checkbox. Its state is returned in
// FeedbackDetails.sendToSupport — you assemble + route the payload.
sendToSupportLabel="Also send this to Support"
/>
);
}SupportFeedbackPayload is exported as the recommended shape for routing feedback
to a support system. The library only fills vote, category, and comment; the
context (conversation URL, message/conversation IDs, user environment details)
is yours to supply, since only your app knows its own routing and environment:
function toSupportPayload(
vote: FeedbackValue,
details: FeedbackDetails,
conversationUrl: string,
): SupportFeedbackPayload {
return {
vote,
category: details.category,
comment: details.comment,
context: { conversationUrl, env: collectEnvironmentDetails() },
};
}Feedback state is controlled-or-uncontrolled throughout: omit feedbackDetails /
feedbackPromptOpen to let the component manage them, or pass them to take full
control. Categories default to a built-in set (defaultFeedbackCategories) and can
be replaced via feedbackCategories.
UserPrompt
Display user messages in the chat:
import { UserPrompt } from '@optilogic/chat';
function ChatMessage() {
return (
<UserPrompt
content="What is the weather today?"
timestamp={new Date()}
/>
);
}UserPromptInput
Input component for user messages:
import { UserPromptInput } from '@optilogic/chat';
function ChatInput() {
return (
<UserPromptInput
onSubmit={(text) => console.log('Submitted:', text)}
placeholder="Type your message..."
/>
);
}HITLQuestionPanel
Display an interactive panel for human-in-the-loop clarifying questions. Replaces the input area when the agent needs user clarification:
import { HITLQuestionPanel, type HITLQuestion } from '@optilogic/chat';
const question: HITLQuestion = {
reason: "I need clarification before proceeding with the data mapping.",
questions: [
"Which table should I load the shipment data into?",
"Should I overwrite existing rows or append?",
],
options: {
"Which table should I load the shipment data into?": ["Customers", "Demand", "Shipments"],
"Should I overwrite existing rows or append?": ["Overwrite", "Append"],
},
context: "Source file has 1,200 rows with columns: origin, destination, quantity, date",
timeoutSeconds: 300,
receivedAt: Date.now(),
};
function ChatInput() {
return (
<HITLQuestionPanel
question={question}
onSubmit={(response) => console.log('Response:', response)}
onStop={() => console.log('Agent stopped')}
/>
);
}HITLInteractionRecord
Display a completed HITL Q&A interaction in the chat history:
import { HITLInteractionRecord, type HITLInteraction } from '@optilogic/chat';
const interaction: HITLInteraction = {
question: { /* HITLQuestion object */ },
response: "Q: Which table?\nA: Shipments",
respondedAt: Date.now(),
};
function CompletedInteraction() {
return <HITLInteractionRecord interaction={interaction} />;
}AgentResponse with HITL Interactions
HITL interactions can also be displayed as a collapsible section within AgentResponse:
import { AgentResponse, type HITLInteraction } from '@optilogic/chat';
function ChatView() {
const hitlInteractions: HITLInteraction[] = [/* completed interactions */];
return (
<AgentResponse
state={state}
hitlInteractions={hitlInteractions}
defaultHITLExpanded={false}
/>
);
}AgentResponse with Status Content
Display ephemeral status messages in the metadata row during agent processing:
import { AgentResponse, TruncatedMessage } from '@optilogic/chat';
function ChatView() {
const [statusMessage, setStatusMessage] = useState<string>();
// Parent controls when to show/clear status content
// e.g., set during processing, clear on completion
return (
<AgentResponse
state={state}
statusContent={
statusMessage
? <TruncatedMessage message={statusMessage} />
: undefined
}
/>
);
}TruncatedMessage
Standalone component that renders a single-line string with CSS-based truncation:
import { TruncatedMessage } from '@optilogic/chat';
<TruncatedMessage message="Searching the knowledge base for relevant documents..." />Components
AgentResponse
Main component for displaying AI agent responses with:
- Streaming content support
- Thinking/reasoning indicators
- Tool call visualization
- Copy and feedback actions
- Metadata display (tokens, timing)
- Ephemeral status content slot in metadata row
- Optional collapsible HITL interaction history
UserPrompt
Component for displaying user messages in the chat interface.
UserPromptInput
Input component for composing user messages with tag support.
HITLQuestionPanel
Interactive panel for displaying agent clarifying questions with:
- Predefined option buttons (toggleable selection)
- Free-form text input
- Countdown timer with visual warning state
- Keyboard support (Enter to submit, Shift+Enter for newlines)
HITLInteractionRecord
Read-only display of a completed HITL Q&A interaction with:
- Per-question inline answers
- Context display
- Fallback for non-structured response formats
HITLSection
Collapsible sub-component for rendering HITL interactions within AgentResponse. Follows the same expand/collapse pattern as ThinkingSection.
TruncatedMessage
Standalone utility component for single-line text truncation with:
- CSS-based ellipsis truncation
- Native title tooltip for full text on hover
- Designed for use in MetadataRow status slot or anywhere else
Hooks
useAgentResponseAccumulator
Manages state for streaming agent responses, handling incremental updates and message accumulation.
useThinkingTimer
Tracks elapsed time during agent thinking phases.
Utilities
buildResponseString
Combines selected options and free-form text into a single formatted string for the backend. Used internally by HITLQuestionPanel and can be used to construct responses programmatically.
License
MIT
