Type Imports
Import types directly from the library:- React
- Vue
Copy
import type {
Message,
ToolCall,
KnowledgeContext,
FunctionCall,
ExecutionResult,
ChatInterfaceProps,
ChatWidgetProps,
APIConfig
} from '@termix-it/react-tool';
Copy
import type {
Message,
ToolCall,
KnowledgeContext,
FunctionCall,
ExecutionResult,
ChatInterfaceProps,
ChatWidgetProps,
APIConfig
} from '@termix-it/vue-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[];
}
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
- React
- Vue
Copy
import React, { useState, useCallback } from 'react';
import {
ChatInterface,
ChatWidget,
Message,
FunctionCall,
ExecutionResult
} from '@termix-it/react-tool';
interface ChatProps {
projectId: string;
aiConfigId: string;
apiKey: string;
}
const TypedChat: React.FC<ChatProps> = ({
projectId,
aiConfigId,
apiKey
}) => {
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={apiKey}
onMessageSent={handleMessageSent}
onResponseReceived={handleResponseReceived}
onFunctionExecuted={handleFunctionExecuted}
onError={handleError}
/>
);
};
export default TypedChat;
Copy
<script setup lang="ts">
import { ref } from 'vue';
import {
ChatInterface,
type Message,
type FunctionCall,
type ExecutionResult
} from '@termix-it/vue-tool';
interface Props {
projectId: string;
aiConfigId: string;
apiKey: string;
}
const props = defineProps<Props>();
const messages = ref<Message[]>([]);
const isProcessing = ref<boolean>(false);
const handleMessageSent = (message: Message): void => {
messages.value.push(message);
isProcessing.value = true;
};
const handleResponseReceived = (message: Message): void => {
messages.value.push(message);
isProcessing.value = false;
};
const handleFunctionExecuted = (
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 = (error: unknown): void => {
console.error('Chat error:', error);
isProcessing.value = false;
// Type-safe error handling
if (error instanceof Error) {
console.error('Error message:', error.message);
}
};
</script>
<template>
<ChatInterface
:project-id="props.projectId"
:ai-config-id="props.aiConfigId"
:authorization="props.apiKey"
@message-sent="handleMessageSent"
@response-received="handleResponseReceived"
@function-executed="handleFunctionExecuted"
@error="handleError"
/>
</template>
ChatWidget with TypeScript
Type-safe ChatWidget implementation with advanced state management:- React
- Vue
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;
apiKey: string;
onWidgetStateChange?: (state: WidgetState) => void;
}
const TypedChatWidget: React.FC<TypedWidgetProps> = ({
projectId,
aiConfigId,
apiKey,
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: apiKey,
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;
Copy
<script setup lang="ts">
import { ref, computed } from 'vue';
import {
ChatWidget,
ChatInterface,
type Message,
type FunctionCall,
type ExecutionResult,
type ChatWidgetProps,
type ChatInterfaceProps
} from '@termix-it/vue-tool';
interface WidgetState {
isOpen: boolean;
unreadCount: number;
lastActivity: Date | null;
}
interface Props {
projectId: string;
aiConfigId: string;
apiKey: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
widgetStateChange: [state: WidgetState];
}>();
const widgetState = ref<WidgetState>({
isOpen: false,
unreadCount: 0,
lastActivity: null
});
const updateWidgetState = (updates: Partial<WidgetState>): void => {
widgetState.value = { ...widgetState.value, ...updates };
emit('widgetStateChange', widgetState.value);
};
const handleOpenChange = (open: boolean): void => {
updateWidgetState({
isOpen: open,
unreadCount: open ? 0 : widgetState.value.unreadCount,
lastActivity: new Date()
});
};
const handleResponseReceived = (message: Message): void => {
if (!widgetState.value.isOpen) {
updateWidgetState({
unreadCount: widgetState.value.unreadCount + 1,
lastActivity: message.timestamp
});
}
};
const handleFunctionExecuted = (
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;
}
}
};
// Computed props for widget
const widgetTitle = computed(() =>
widgetState.value.unreadCount > 0
? `AI Assistant (${widgetState.value.unreadCount} new)`
: 'AI Assistant'
);
const buttonClass = computed(() =>
widgetState.value.unreadCount > 0 ? 'animate-pulse ring-2 ring-blue-400' : ''
);
</script>
<template>
<div class="fixed bottom-6 right-6">
<ChatWidget
:title="widgetTitle"
:default-open="widgetState.isOpen"
:button-class-name="buttonClass"
@open-change="handleOpenChange"
>
<ChatInterface
:project-id="props.projectId"
:ai-config-id="props.aiConfigId"
:authorization="propsapiKey"
:enable-streaming-mode="true"
class="h-full"
@response-received="handleResponseReceived"
@function-executed="handleFunctionExecuted"
/>
</ChatWidget>
</div>
</template>
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;
}
- React
- Vue
Copy
const StreamingTypedChat: React.FC<{
projectId: string;
aiConfigId: string;
apiKey: string;
}> = ({ projectId, aiConfigId, apiKey }) => {
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: apiKey,
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>
);
};
Copy
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ChatInterface, type ChatInterfaceProps } from '@termix-it/vue-tool';
type StreamingStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
interface StreamingStats {
charactersReceived: number;
chunksReceived: number;
averageLatency: number;
connectionUptime: number;
}
interface Props {
projectId: string;
aiConfigId: string;
apiKey: string;
}
const props = defineProps<Props>();
const streamingStatus = ref<StreamingStatus>('disconnected');
const streamingStats = ref<StreamingStats>({
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
});
const handleStreamingStatusChange = (status: StreamingStatus): void => {
streamingStatus.value = status;
};
const handleStreamingStatsUpdate = (stats: StreamingStats): void => {
streamingStats.value = stats;
};
const statusClass = computed(() => {
switch (streamingStatus.value) {
case 'connected':
return 'text-green-600';
case 'connecting':
return 'text-yellow-600';
case 'error':
return 'text-red-600';
default:
return 'text-gray-500';
}
});
</script>
<template>
<div class="space-y-4">
<!-- Streaming status indicator -->
<div class="p-3 border rounded bg-blue-50">
<div class="flex items-center justify-between text-sm">
<span>Streaming Status:</span>
<span :class="['font-semibold', statusClass]">
{{ streamingStatus }}
</span>
</div>
</div>
<ChatInterface
:project-id="props.projectId"
:ai-config-id="props.aiConfigId"
:authorization="apiKey"
:enable-streaming-mode="true"
@streaming-status-change="handleStreamingStatusChange"
@streaming-stats-update="handleStreamingStatsUpdate"
/>
</div>
</template>
Advanced Typed Implementation
- React
- Vue
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;
apiKey: string;
onKnowledgeSearch?: (context: KnowledgeContext[]) => void;
}
const AdvancedTypedChat: React.FC<AdvancedChatProps> = ({
projectId,
aiConfigId,
apiKey,
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: apiKey,
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;
Copy
<script setup lang="ts">
import { ref, computed } from 'vue';
import {
ChatInterface,
type Message,
type FunctionCall,
type ExecutionResult,
type KnowledgeContext,
type ChatInterfaceProps
} from '@termix-it/vue-tool';
// Composable for function call handling
interface ExecutionHistoryEntry {
call: FunctionCall;
result: ExecutionResult;
timestamp: Date;
}
const useFunctionCalls = () => {
const executionHistory = ref<ExecutionHistoryEntry[]>([]);
const handleExecution = (call: FunctionCall, result: ExecutionResult): void => {
executionHistory.value.push({
call,
result,
timestamp: new Date()
});
};
const clearHistory = (): void => {
executionHistory.value = [];
};
return { executionHistory, handleExecution, clearHistory };
};
// Props and emits
interface Props {
projectId: string;
aiConfigId: string;
apiKey: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
knowledgeSearch: [context: KnowledgeContext[]];
}>();
// State
const chatHistory = ref<Message[]>([]);
const knowledgeStats = ref<{
totalSearches: number;
documentsFound: number;
}>({ totalSearches: 0, documentsFound: 0 });
const { executionHistory, handleExecution, clearHistory } = useFunctionCalls();
// Type-safe API headers configuration
const apiHeaders: Record<string, string> = {
'X-Client-Version': '1.0.0',
'X-User-Agent': 'TypedChatInterface',
'Content-Type': 'application/json'
};
const handleResponseReceived = (message: Message): void => {
chatHistory.value.push(message);
// Handle knowledge base context
if (message.knowledgeContext && message.knowledgeContext.length > 0) {
knowledgeStats.value = {
totalSearches: knowledgeStats.value.totalSearches + 1,
documentsFound: knowledgeStats.value.documentsFound + message.knowledgeContext.length
};
emit('knowledgeSearch', message.knowledgeContext);
}
};
const handleTypedError = (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);
}
};
// Computed for recent execution history
const recentExecutions = computed(() =>
executionHistory.value.slice(-3)
);
</script>
<template>
<div class="space-y-4">
<!-- Knowledge Stats Display -->
<div class="p-3 bg-blue-50 border border-blue-200 rounded-lg">
<h3 class="text-sm font-semibold text-blue-800">Knowledge Stats</h3>
<p class="text-xs text-blue-600">
Searches: {{ knowledgeStats.totalSearches }},
Documents: {{ knowledgeStats.documentsFound }}
</p>
</div>
<!-- Function Execution History -->
<div
v-if="executionHistory.length > 0"
class="p-3 bg-green-50 border border-green-200 rounded-lg"
>
<div class="flex justify-between items-center mb-2">
<h3 class="text-sm font-semibold text-green-800">
Function Calls ({{ executionHistory.length }})
</h3>
<button
class="text-xs text-green-600 hover:text-green-800"
@click="clearHistory"
>
Clear
</button>
</div>
<div class="space-y-1">
<div
v-for="(entry, index) in recentExecutions"
:key="index"
class="text-xs text-green-700"
>
<span class="font-mono">{{ entry.call.name }}</span>
<span
:class="['ml-2', entry.result.success ? 'text-green-600' : 'text-red-600']"
>
{{ entry.result.success ? '✓' : '✗' }}
</span>
<span class="ml-2 text-gray-500">
{{ entry.timestamp.toLocaleTimeString() }}
</span>
</div>
</div>
</div>
<ChatInterface
:project-id="props.projectId"
:ai-config-id="props.aiConfigId"
:authorization="props.apiKey"
:api-headers="apiHeaders"
@response-received="handleResponseReceived"
@function-executed="handleExecution"
@error="handleTypedError"
/>
</div>
</template>
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:- React
- Vue
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
};
};
Copy
import { ref, computed, type Ref, type ComputedRef } from 'vue';
import type { Message } from '@termix-it/vue-tool';
interface UseChatStateResult {
messages: Ref<Message[]>;
isLoading: Ref<boolean>;
error: Ref<Error | null>;
sendMessage: (content: string) => Promise<void>;
clearMessages: () => void;
}
interface UseChatWidgetResult {
isOpen: ComputedRef<boolean>;
unreadCount: ComputedRef<number>;
toggleWidget: () => void;
closeWidget: () => void;
markAsRead: () => void;
widgetState: Ref<WidgetState>;
}
interface UseStreamingResult {
streamingStatus: Ref<StreamingStatus>;
streamingStats: Ref<StreamingStats>;
isStreaming: ComputedRef<boolean>;
reconnect: () => void;
disconnect: () => void;
}
export const useChatState = (
projectId: string,
aiConfigId: string,
authorization: string
): UseChatStateResult => {
const messages = ref<Message[]>([]);
const isLoading = ref<boolean>(false);
const error = ref<Error | null>(null);
const sendMessage = async (content: string): Promise<void> => {
isLoading.value = true;
error.value = null;
try {
// Implementation here
const newMessage: Message = {
id: `msg-${Date.now()}`,
role: 'user',
content,
timestamp: new Date()
};
messages.value.push(newMessage);
} catch (err) {
error.value = err instanceof Error ? err : new Error('Unknown error');
} finally {
isLoading.value = false;
}
};
const clearMessages = (): void => {
messages.value = [];
error.value = null;
};
return {
messages,
isLoading,
error,
sendMessage,
clearMessages
};
};
// ChatWidget composable
export const useChatWidget = (initialOpen = false): UseChatWidgetResult => {
const widgetState = ref<WidgetState>({
isOpen: initialOpen,
unreadCount: 0,
lastActivity: null
});
const toggleWidget = (): void => {
widgetState.value = {
...widgetState.value,
isOpen: !widgetState.value.isOpen,
unreadCount: widgetState.value.isOpen ? widgetState.value.unreadCount : 0
};
};
const closeWidget = (): void => {
widgetState.value = { ...widgetState.value, isOpen: false, unreadCount: 0 };
};
const markAsRead = (): void => {
widgetState.value = { ...widgetState.value, unreadCount: 0 };
};
return {
isOpen: computed(() => widgetState.value.isOpen),
unreadCount: computed(() => widgetState.value.unreadCount),
toggleWidget,
closeWidget,
markAsRead,
widgetState
};
};
// Streaming composable
export const useStreaming = (enabled: Ref<boolean> | boolean): UseStreamingResult => {
const streamingStatus = ref<StreamingStatus>('disconnected');
const streamingStats = ref<StreamingStats>({
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
});
const isEnabled = computed(() =>
typeof enabled === 'boolean' ? enabled : enabled.value
);
const reconnect = (): void => {
if (isEnabled.value) {
streamingStatus.value = 'connecting';
// Reconnection logic would go here
}
};
const disconnect = (): void => {
streamingStatus.value = 'disconnected';
streamingStats.value = {
charactersReceived: 0,
chunksReceived: 0,
averageLatency: 0,
connectionUptime: 0
};
};
return {
streamingStatus,
streamingStats,
isStreaming: computed(() => streamingStatus.value === '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}
/>
);
};