TypeScript Support
@termix-it/react-tool provides comprehensive TypeScript support with full type definitions for all components, props, and data structures.Type Imports
Import types directly from the library:Copy
import type {
Message,
ToolCall,
KnowledgeContext,
FunctionCall,
ExecutionResult,
ChatInterfaceProps,
ChatWidgetProps,
APIConfig
} from '@termix-it/react-tool';
Core Interfaces
Message Interface
TheMessage
interface represents all messages in the chat:
Copy
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
usage?: UsageInfo;
knowledgeContext?: KnowledgeContext[];
toolCalls?: ToolCall[];
}
interface UsageInfo {
promptTokens: number;
completionTokens: number;
totalTokens: number;
cost: number;
}
Function Call Types
Define function calls and their execution results:Copy
interface FunctionCall {
name: string;
parameters: Record<string, any>;
metadata?: FunctionCallMetadata;
}
interface FunctionCallMetadata {
type?: 'api' | 'contract' | 'unknown';
description?: string;
method?: string;
path?: string;
contractName?: string;
contractAddress?: string;
chainName?: string;
}
interface ExecutionResult {
success: boolean;
type: 'api' | 'contract' | 'error';
data?: any;
error?: string;
metadata?: Record<string, any>;
}
Knowledge Base Types
Handle knowledge base search and context:Copy
interface KnowledgeContext {
id: string;
title: string;
content: string;
source: string;
relevanceScore: number;
metadata?: Record<string, any>;
}
interface ToolCall {
id: string;
name: string;
parameters: Record<string, any>;
result?: any;
}
Component Props
Full type definition for the main component:Copy
interface ChatInterfaceProps {
// Required props
projectId: string;
aiConfigId: string;
// API Configuration
authorization?: string;
apiHeaders?: Record<string, string>;
restExecuteHeader?: Record<string, string>;
// UI Customization
placeholder?: string;
sendButtonText?: string;
modelName?: string;
personalityName?: string;
// Feature Toggles
enableStreamingMode?: boolean;
showHeader?: boolean;
showUsageInfo?: boolean;
showTimestamp?: boolean;
maxKnowledgeResults?: number;
// Styling
className?: string;
style?: React.CSSProperties;
messagesClassName?: string;
messagesStyle?: React.CSSProperties;
inputClassName?: string;
inputStyle?: React.CSSProperties;
// Event Callbacks
onMessageSent?: (message: Message) => void;
onResponseReceived?: (message: Message) => void;
onFunctionExecuted?: (functionCall: FunctionCall, result: ExecutionResult) => void;
onError?: (error: any) => void;
}
ChatWidget Props
Interface for the ChatWidget component:Copy
interface ChatWidgetProps {
// Display Configuration
title?: string;
buttonIcon?: string;
// Styling
className?: string;
style?: React.CSSProperties;
buttonClassName?: string;
buttonStyle?: React.CSSProperties;
dialogClassName?: string;
dialogStyle?: React.CSSProperties;
// State Management
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
// Content
children: React.ReactNode;
}
Typed Component Usage
Basic Typed Implementation
Copy
import React, { useState, useCallback } from 'react';
import {
ChatInterface,
ChatWidget,
Message,
FunctionCall,
ExecutionResult
} from '@termix-it/react-tool';
interface ChatProps {
projectId: string;
aiConfigId: string;
userToken: string;
}
const TypedChat: React.FC<ChatProps> = ({
projectId,
aiConfigId,
userToken
}) => {
const [messages, setMessages] = useState<Message[]>([]);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const handleMessageSent = useCallback((message: Message): void => {
setMessages(prev => [...prev, message]);
setIsProcessing(true);
}, []);
const handleResponseReceived = useCallback((message: Message): void => {
setMessages(prev => [...prev, message]);
setIsProcessing(false);
}, []);
const handleFunctionExecuted = useCallback((
functionCall: FunctionCall,
result: ExecutionResult
): void => {
console.log('Function executed:', {
name: functionCall.name,
success: result.success,
type: result.type
});
// Type-safe handling based on result type
if (result.type === 'contract' && result.success) {
const txHash = result.data?.transactionHash as string | undefined;
if (txHash) {
console.log('Transaction hash:', txHash);
}
}
if (result.type === 'api' && result.success) {
const apiResponse = result.data;
// Handle API response with proper typing
}
}, []);
const handleError = useCallback((error: unknown): void => {
console.error('Chat error:', error);
setIsProcessing(false);
// Type-safe error handling
if (error instanceof Error) {
console.error('Error message:', error.message);
}
}, []);
return (
<ChatInterface
projectId={projectId}
aiConfigId={aiConfigId}
authorization={`Bearer ${userToken}`}
onMessageSent={handleMessageSent}
onResponseReceived={handleResponseReceived}
onFunctionExecuted={handleFunctionExecuted}
onError={handleError}
/>
);
};
export default TypedChat;
ChatWidget with TypeScript
Type-safe ChatWidget implementation with advanced state management:Copy
import React, { useState, useCallback, useEffect } from 'react';
import {
ChatWidget,
ChatInterface,
Message,
FunctionCall,
ExecutionResult,
ChatWidgetProps,
ChatInterfaceProps
} from '@termix-it/react-tool';
interface WidgetState {
isOpen: boolean;
unreadCount: number;
lastActivity: Date | null;
}
interface TypedWidgetProps {
projectId: string;
aiConfigId: string;
userToken: string;
onWidgetStateChange?: (state: WidgetState) => void;
}
const TypedChatWidget: React.FC<TypedWidgetProps> = ({
projectId,
aiConfigId,
userToken,
onWidgetStateChange
}) => {
const [widgetState, setWidgetState] = useState<WidgetState>({
isOpen: false,
unreadCount: 0,
lastActivity: null
});
const updateWidgetState = useCallback((updates: Partial<WidgetState>) => {
setWidgetState(prev => {
const newState = { ...prev, ...updates };
onWidgetStateChange?.(newState);
return newState;
});
}, [onWidgetStateChange]);
const handleOpenChange = useCallback((open: boolean): void => {
updateWidgetState({
isOpen: open,
unreadCount: open ? 0 : widgetState.unreadCount,
lastActivity: new Date()
});
}, [updateWidgetState, widgetState.unreadCount]);
const handleResponseReceived = useCallback((message: Message): void => {
if (!widgetState.isOpen) {
updateWidgetState({
unreadCount: widgetState.unreadCount + 1,
lastActivity: message.timestamp
});
}
}, [widgetState.isOpen, widgetState.unreadCount, updateWidgetState]);
const handleFunctionExecuted = useCallback((
call: FunctionCall,
result: ExecutionResult
): void => {
console.log('Function executed in widget:', {
name: call.name,
success: result.success,
type: result.type
});
// Type-safe result handling
if (result.success) {
switch (result.type) {
case 'api':
console.log('API call succeeded:', result.data);
break;
case 'contract':
console.log('Contract call succeeded:', result.data);
break;
}
}
}, []);
// Widget props with proper typing
const widgetProps: ChatWidgetProps = {
title: widgetState.unreadCount > 0 ? `AI Assistant (${widgetState.unreadCount} new)` : "AI Assistant",
defaultOpen: widgetState.isOpen,
onOpenChange: handleOpenChange,
buttonClassName: widgetState.unreadCount > 0 ? 'animate-pulse ring-2 ring-blue-400' : ''
};
// ChatInterface props with proper typing
const chatProps: ChatInterfaceProps = {
projectId,
aiConfigId,
authorization: `Bearer ${userToken}`,
enableStreamingMode: true,
onResponseReceived: handleResponseReceived,
onFunctionExecuted: handleFunctionExecuted,
className: 'h-full'
};
return (
<div className="fixed bottom-6 right-6">
<ChatWidget {...widgetProps}>
<ChatInterface {...chatProps} />
</ChatWidget>
</div>
);
};
export default TypedChatWidget;
Streaming Mode Types
Additional types for streaming functionality:Copy
// Streaming connection states
type StreamingStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
// Streaming statistics
interface StreamingStats {
charactersReceived: number;
chunksReceived: number;
averageLatency: number;
connectionUptime: number;
}
// Streaming event types
interface StreamingEvent {
type: 'content' | 'done' | 'error';
data?: string;
sessionId?: string;
error?: string;
}
// Enhanced ChatInterface props for streaming
interface StreamingChatInterfaceProps extends ChatInterfaceProps {
enableStreamingMode: true;
onStreamingStatusChange?: (status: StreamingStatus) => void;
onStreamingStatsUpdate?: (stats: StreamingStats) => void;
}
// Streaming implementation example
const StreamingTypedChat: React.FC<{
projectId: string;
aiConfigId: string;
userToken: string;
}> = ({ projectId, aiConfigId, userToken }) => {
const [streamingStatus, setStreamingStatus] = useState<StreamingStatus>('disconnected');
const [streamingStats, setStreamingStats] = useState<StreamingStats>({
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
});
const handleStreamingStatusChange = useCallback((status: StreamingStatus): void => {
setStreamingStatus(status);
}, []);
const handleStreamingStatsUpdate = useCallback((stats: StreamingStats): void => {
setStreamingStats(stats);
}, []);
const chatProps: StreamingChatInterfaceProps = {
projectId,
aiConfigId,
authorization: `Bearer ${userToken}`,
enableStreamingMode: true,
onStreamingStatusChange: handleStreamingStatusChange,
onStreamingStatsUpdate: handleStreamingStatsUpdate
};
return (
<div className="space-y-4">
{/* Streaming status indicator */}
<div className="p-3 border rounded bg-blue-50">
<div className="flex items-center justify-between text-sm">
<span>Streaming Status:</span>
<span className={`font-semibold ${
streamingStatus === 'connected' ? 'text-green-600' :
streamingStatus === 'connecting' ? 'text-yellow-600' :
streamingStatus === 'error' ? 'text-red-600' :
'text-gray-500'
}`}>
{streamingStatus}
</span>
</div>
</div>
<ChatInterface {...chatProps} />
</div>
);
};
Advanced Typed Implementation
Copy
import React, { useState, useCallback, useMemo } from 'react';
import {
ChatInterface,
Message,
FunctionCall,
ExecutionResult,
KnowledgeContext,
ChatInterfaceProps
} from '@termix-it/react-tool';
// Custom hook for function call handling
interface UseFunctionCallsResult {
executionHistory: Array<{
call: FunctionCall;
result: ExecutionResult;
timestamp: Date;
}>;
handleExecution: (call: FunctionCall, result: ExecutionResult) => void;
clearHistory: () => void;
}
const useFunctionCalls = (): UseFunctionCallsResult => {
const [executionHistory, setExecutionHistory] = useState<
Array<{
call: FunctionCall;
result: ExecutionResult;
timestamp: Date;
}>
>([]);
const handleExecution = useCallback((
call: FunctionCall,
result: ExecutionResult
): void => {
setExecutionHistory(prev => [
...prev,
{ call, result, timestamp: new Date() }
]);
}, []);
const clearHistory = useCallback((): void => {
setExecutionHistory([]);
}, []);
return { executionHistory, handleExecution, clearHistory };
};
// Enhanced component with full TypeScript support
interface AdvancedChatProps extends Partial<ChatInterfaceProps> {
projectId: string;
aiConfigId: string;
userToken: string;
onKnowledgeSearch?: (context: KnowledgeContext[]) => void;
}
const AdvancedTypedChat: React.FC<AdvancedChatProps> = ({
projectId,
aiConfigId,
userToken,
onKnowledgeSearch,
...additionalProps
}) => {
const [chatHistory, setChatHistory] = useState<Message[]>([]);
const [knowledgeStats, setKnowledgeStats] = useState<{
totalSearches: number;
documentsFound: number;
}>({ totalSearches: 0, documentsFound: 0 });
const { executionHistory, handleExecution, clearHistory } = useFunctionCalls();
// Type-safe API headers configuration
const apiHeaders = useMemo<Record<string, string>>(() => ({
'X-Client-Version': '1.0.0',
'X-User-Agent': 'TypedChatInterface',
'Content-Type': 'application/json'
}), []);
const handleResponseReceived = useCallback((message: Message): void => {
setChatHistory(prev => [...prev, message]);
// Handle knowledge base context
if (message.knowledgeContext && message.knowledgeContext.length > 0) {
setKnowledgeStats(prev => ({
totalSearches: prev.totalSearches + 1,
documentsFound: prev.documentsFound + message.knowledgeContext!.length
}));
onKnowledgeSearch?.(message.knowledgeContext);
}
}, [onKnowledgeSearch]);
const handleTypedError = useCallback((error: unknown): void => {
// Comprehensive error type checking
if (error instanceof Error) {
console.error('Standard Error:', error.message);
} else if (typeof error === 'string') {
console.error('String Error:', error);
} else if (error && typeof error === 'object') {
const errorObj = error as Record<string, unknown>;
console.error('Object Error:', {
message: errorObj.message,
code: errorObj.code,
details: errorObj.details
});
} else {
console.error('Unknown Error:', error);
}
}, []);
// Merge props with type safety
const chatProps: ChatInterfaceProps = {
projectId,
aiConfigId,
authorization: `Bearer ${userToken}`,
apiHeaders,
onResponseReceived: handleResponseReceived,
onFunctionExecuted: handleExecution,
onError: handleTypedError,
...additionalProps
};
return (
<div className="space-y-4">
{/* Knowledge Stats Display */}
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg">
<h3 className="text-sm font-semibold text-blue-800">Knowledge Stats</h3>
<p className="text-xs text-blue-600">
Searches: {knowledgeStats.totalSearches},
Documents: {knowledgeStats.documentsFound}
</p>
</div>
{/* Function Execution History */}
{executionHistory.length > 0 && (
<div className="p-3 bg-green-50 border border-green-200 rounded-lg">
<div className="flex justify-between items-center mb-2">
<h3 className="text-sm font-semibold text-green-800">
Function Calls ({executionHistory.length})
</h3>
<button
onClick={clearHistory}
className="text-xs text-green-600 hover:text-green-800"
>
Clear
</button>
</div>
<div className="space-y-1">
{executionHistory.slice(-3).map((entry, index) => (
<div key={index} className="text-xs text-green-700">
<span className="font-mono">{entry.call.name}</span>
<span className={`ml-2 ${entry.result.success ? 'text-green-600' : 'text-red-600'}`}>
{entry.result.success ? '✓' : '✗'}
</span>
<span className="ml-2 text-gray-500">
{entry.timestamp.toLocaleTimeString()}
</span>
</div>
))}
</div>
</div>
)}
<ChatInterface {...chatProps} />
</div>
);
};
export default AdvancedTypedChat;
Custom Type Extensions
Extending Base Types
Create custom types for your specific use case:Copy
// Extend the base Message interface
interface CustomMessage extends Message {
customData?: {
userAgent?: string;
sessionId?: string;
metadata?: Record<string, any>;
};
}
// Create specific function call types
interface APIFunctionCall extends FunctionCall {
metadata: Required<Pick<FunctionCallMetadata, 'type' | 'method' | 'path'>> & {
type: 'api';
};
}
interface ContractFunctionCall extends FunctionCall {
metadata: Required<Pick<FunctionCallMetadata, 'type' | 'contractName' | 'contractAddress'>> & {
type: 'contract';
};
}
// Union type for specific function calls
type SpecificFunctionCall = APIFunctionCall | ContractFunctionCall;
// ChatWidget-specific extensions
interface WidgetConfiguration {
appearance: {
theme: 'light' | 'dark' | 'auto';
position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
size: 'small' | 'medium' | 'large';
};
behavior: {
autoOpen: boolean;
closeOnOutsideClick: boolean;
showNotifications: boolean;
};
}
interface EnhancedChatWidgetProps extends ChatWidgetProps {
configuration?: WidgetConfiguration;
analytics?: {
trackOpenClose: boolean;
trackMessageCount: boolean;
trackSessionDuration: boolean;
};
}
// Streaming-specific type extensions
interface StreamingMessage extends Message {
streamingMetadata?: {
chunkCount: number;
streamingDuration: number;
averageChunkSize: number;
};
}
// Custom execution result with specific data types
interface APIExecutionResult extends ExecutionResult {
type: 'api';
data?: {
status: number;
headers: Record<string, string>;
body: any;
};
}
interface ContractExecutionResult extends ExecutionResult {
type: 'contract';
data?: {
transactionHash: string;
blockNumber?: number;
gasUsed?: string;
};
}
Type Guards
Implement type guards for safe type checking:Copy
// Type guard for API function calls
function isAPIFunctionCall(call: FunctionCall): call is APIFunctionCall {
return call.metadata?.type === 'api';
}
// Type guard for contract function calls
function isContractFunctionCall(call: FunctionCall): call is ContractFunctionCall {
return call.metadata?.type === 'contract';
}
// Type guard for API execution results
function isAPIExecutionResult(result: ExecutionResult): result is APIExecutionResult {
return result.type === 'api';
}
// Type guard for contract execution results
function isContractExecutionResult(result: ExecutionResult): result is ContractExecutionResult {
return result.type === 'contract';
}
// Type guard for streaming messages
function isStreamingMessage(message: Message): message is StreamingMessage {
return 'streamingMetadata' in message && message.streamingMetadata !== undefined;
}
// Type guard for enhanced widget props
function isEnhancedChatWidgetProps(props: ChatWidgetProps): props is EnhancedChatWidgetProps {
return 'configuration' in props || 'analytics' in props;
}
// Usage with type guards
const handleTypedFunctionExecution = (
call: FunctionCall,
result: ExecutionResult
): void => {
if (isAPIFunctionCall(call) && isAPIExecutionResult(result)) {
// TypeScript now knows these are API-specific types
console.log('API call:', call.metadata.method, call.metadata.path);
console.log('Response status:', result.data?.status);
}
if (isContractFunctionCall(call) && isContractExecutionResult(result)) {
// TypeScript now knows these are contract-specific types
console.log('Contract:', call.metadata.contractName);
console.log('Transaction:', result.data?.transactionHash);
}
};
Generic Type Utilities
Utility Types
Create utility types for common patterns:Copy
// Extract function names from function calls
type FunctionNames<T extends FunctionCall> = T['name'];
// Create a mapped type for function parameters
type FunctionParameters<T extends string> = Record<T, any>;
// Utility type for configuration objects
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Configuration with optional nested properties
type ChatConfig = DeepPartial<ChatInterfaceProps>;
// Example usage
const config: ChatConfig = {
enableFunctionCalls: true,
// All other properties are optional
};
Hook Types
Type your custom hooks properly:Copy
interface UseChatStateResult {
messages: Message[];
isLoading: boolean;
error: Error | null;
sendMessage: (content: string) => Promise<void>;
clearMessages: () => void;
}
interface UseChatWidgetResult {
isOpen: boolean;
unreadCount: number;
toggleWidget: () => void;
closeWidget: () => void;
markAsRead: () => void;
widgetState: WidgetState;
}
interface UseStreamingResult {
streamingStatus: StreamingStatus;
streamingStats: StreamingStats;
isStreaming: boolean;
reconnect: () => void;
disconnect: () => void;
}
const useChatState = (
projectId: string,
aiConfigId: string,
authorization: string
): UseChatStateResult => {
const [messages, setMessages] = useState<Message[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(null);
const sendMessage = useCallback(async (content: string): Promise<void> => {
setIsLoading(true);
setError(null);
try {
// Implementation here
const newMessage: Message = {
id: `msg-${Date.now()}`,
role: 'user',
content,
timestamp: new Date()
};
setMessages(prev => [...prev, newMessage]);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setIsLoading(false);
}
}, [projectId, aiConfigId, authorization]);
const clearMessages = useCallback((): void => {
setMessages([]);
setError(null);
}, []);
return {
messages,
isLoading,
error,
sendMessage,
clearMessages
};
};
// ChatWidget custom hook
const useChatWidget = (initialOpen = false): UseChatWidgetResult => {
const [widgetState, setWidgetState] = useState<WidgetState>({
isOpen: initialOpen,
unreadCount: 0,
lastActivity: null
});
const toggleWidget = useCallback((): void => {
setWidgetState(prev => ({
...prev,
isOpen: !prev.isOpen,
unreadCount: prev.isOpen ? prev.unreadCount : 0
}));
}, []);
const closeWidget = useCallback((): void => {
setWidgetState(prev => ({ ...prev, isOpen: false, unreadCount: 0 }));
}, []);
const markAsRead = useCallback((): void => {
setWidgetState(prev => ({ ...prev, unreadCount: 0 }));
}, []);
return {
isOpen: widgetState.isOpen,
unreadCount: widgetState.unreadCount,
toggleWidget,
closeWidget,
markAsRead,
widgetState
};
};
// Streaming custom hook
const useStreaming = (enabled: boolean): UseStreamingResult => {
const [streamingStatus, setStreamingStatus] = useState<StreamingStatus>('disconnected');
const [streamingStats, setStreamingStats] = useState<StreamingStats>({
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
});
const reconnect = useCallback((): void => {
if (enabled) {
setStreamingStatus('connecting');
// Reconnection logic would go here
}
}, [enabled]);
const disconnect = useCallback((): void => {
setStreamingStatus('disconnected');
setStreamingStats({
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
});
}, []);
return {
streamingStatus,
streamingStats,
isStreaming: streamingStatus === 'connected',
reconnect,
disconnect
};
};
Best Practices
Strict Type Checking: Enable strict TypeScript settings in your
tsconfig.json
for better type safety:Copy
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
Type Imports: Use
import type
for type-only imports to avoid runtime overhead and ensure better tree shaking.Always use type guards when working with union types to ensure type safety at runtime.
Common TypeScript Patterns
Conditional Rendering with Types
Copy
interface ConditionalChatProps {
mode: 'simple' | 'advanced';
projectId: string;
aiConfigId: string;
}
const ConditionalChat: React.FC<ConditionalChatProps> = ({ mode, ...props }) => {
if (mode === 'simple') {
return (
<ChatInterface
{...props}
showUsageInfo={false}
/>
);
}
return (
<ChatInterface
{...props}
showUsageInfo={true}
/>
);
};