instantsearch-ui-components
Version:
Common UI components for InstantSearch.
422 lines (421 loc) • 13.5 kB
TypeScript
import type { ComponentProps, SendEventForHits } from '../../types';
import type { SearchParameters } from 'algoliasearch-helper';
export type ChatStatus = 'ready' | 'submitted' | 'streaming' | 'error';
export type ChatRole = 'data' | 'user' | 'assistant' | 'system';
/**
* Provider metadata type for UI message parts.
*/
export type ProviderMetadata = Record<string, Record<string, unknown>>;
/**
* A record of data types for data parts in UI messages.
*/
export type UIDataTypes = Record<string, unknown>;
/**
* Tool input/output type definition.
*/
export type UITool = {
input: unknown;
output: unknown | undefined;
};
/**
* A record of UI tools.
*/
export type UITools = Record<string, UITool>;
/**
* Helper type to get values of an object.
*/
type ValueOf<T> = T[keyof T];
/**
* Deep partial type.
*/
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
/**
* A text part of a message.
*/
export type TextUIPart = {
type: 'text';
text: string;
state?: 'streaming' | 'done';
providerMetadata?: ProviderMetadata;
};
/**
* A reasoning part of a message.
*/
export type ReasoningUIPart = {
type: 'reasoning';
text: string;
state?: 'streaming' | 'done';
providerMetadata?: ProviderMetadata;
};
/**
* A source URL part of a message.
*/
export type SourceUrlUIPart = {
type: 'source-url';
sourceId: string;
url: string;
title?: string;
providerMetadata?: ProviderMetadata;
};
/**
* A document source part of a message.
*/
export type SourceDocumentUIPart = {
type: 'source-document';
sourceId: string;
mediaType: string;
title: string;
filename?: string;
providerMetadata?: ProviderMetadata;
};
/**
* A file part of a message.
*/
export type FileUIPart = {
type: 'file';
mediaType: string;
filename?: string;
url: string;
providerMetadata?: ProviderMetadata;
};
/**
* A step boundary part of a message.
*/
export type StepStartUIPart = {
type: 'step-start';
};
/**
* A data part of a message.
*/
export type DataUIPart<TDataTypes extends UIDataTypes> = ValueOf<{
[NAME in keyof TDataTypes & string]: {
type: `data-${NAME}`;
id?: string;
data: TDataTypes[NAME];
};
}>;
/**
* A tool invocation part of a message.
*/
export type ToolUIPart<TTools extends UITools = UITools> = ValueOf<{
[NAME in keyof TTools & string]: {
type: `tool-${NAME}`;
toolCallId: string;
} & ({
state: 'input-streaming';
input: DeepPartial<TTools[NAME]['input']> | undefined;
providerExecuted?: boolean;
output?: never;
errorText?: never;
} | {
state: 'input-available';
input: TTools[NAME]['input'];
providerExecuted?: boolean;
output?: never;
errorText?: never;
callProviderMetadata?: ProviderMetadata;
} | {
state: 'output-available';
input: TTools[NAME]['input'];
output: TTools[NAME]['output'];
errorText?: never;
providerExecuted?: boolean;
callProviderMetadata?: ProviderMetadata;
preliminary?: boolean;
} | {
state: 'output-error';
input: TTools[NAME]['input'] | undefined;
rawInput?: unknown;
output?: never;
errorText: string;
providerExecuted?: boolean;
callProviderMetadata?: ProviderMetadata;
});
}>;
/**
* A dynamic tool invocation part of a message.
*/
export type DynamicToolUIPart = {
type: 'dynamic-tool';
toolName: string;
toolCallId: string;
} & ({
state: 'input-streaming';
input: unknown | undefined;
output?: never;
errorText?: never;
} | {
state: 'input-available';
input: unknown;
output?: never;
errorText?: never;
callProviderMetadata?: ProviderMetadata;
} | {
state: 'output-available';
input: unknown;
output: unknown;
errorText?: never;
callProviderMetadata?: ProviderMetadata;
preliminary?: boolean;
} | {
state: 'output-error';
input: unknown;
output?: never;
errorText: string;
callProviderMetadata?: ProviderMetadata;
});
/**
* All possible message part types.
*/
export type UIMessagePart<TDataTypes extends UIDataTypes = UIDataTypes, TTools extends UITools = UITools> = TextUIPart | ReasoningUIPart | ToolUIPart<TTools> | DynamicToolUIPart | SourceUrlUIPart | SourceDocumentUIPart | FileUIPart | DataUIPart<TDataTypes> | StepStartUIPart;
/**
* AI SDK UI Messages. They are used in the client and to communicate between the frontend and the API routes.
*/
export interface UIMessage<TMetadata = unknown, TDataParts extends UIDataTypes = UIDataTypes, TTools extends UITools = UITools> {
/** A unique identifier for the message. */
id: string;
/** The role of the message. */
role: 'system' | 'user' | 'assistant';
/** The metadata of the message. */
metadata?: TMetadata;
/** The parts of the message. Use this for rendering the message in the UI. */
parts: Array<UIMessagePart<TDataParts, TTools>>;
}
export type ChatMessageBase = UIMessage;
export type ChatToolMessage = Extract<ChatMessageBase['parts'][number], {
type: `tool-${string}`;
}>;
export type ChatToolType = ChatToolMessage['type'];
/**
* Infer metadata type from UIMessage.
*/
export type InferUIMessageMetadata<T extends UIMessage> = T extends UIMessage<infer TMetadata> ? TMetadata : unknown;
/**
* Infer data types from UIMessage.
*/
export type InferUIMessageData<T extends UIMessage> = T extends UIMessage<unknown, infer TDataTypes> ? TDataTypes : UIDataTypes;
/**
* Infer tools from UIMessage.
*/
export type InferUIMessageTools<T extends UIMessage> = T extends UIMessage<unknown, UIDataTypes, infer TTools> ? TTools : UITools;
/**
* Chat state interface.
*/
export interface ChatState<TUIMessage extends UIMessage> {
status: ChatStatus;
error: Error | undefined;
messages: TUIMessage[];
pushMessage: (message: TUIMessage) => void;
popMessage: () => void;
replaceMessage: (index: number, message: TUIMessage) => void;
snapshot: <T>(thing: T) => T;
}
/**
* ID generator function type.
*/
export type IdGenerator = () => string;
/**
* Callback function to be called when an error is encountered.
*/
export type ChatOnErrorCallback = (error: Error) => void;
/**
* Infer tool call type from UIMessage.
*/
export type InferUIMessageToolCall<TUIMessage extends UIMessage> = ValueOf<{
[NAME in keyof InferUIMessageTools<TUIMessage>]: {
toolName: NAME & string;
toolCallId: string;
input: InferUIMessageTools<TUIMessage>[NAME] extends {
input: infer INPUT;
} ? INPUT : never;
dynamic?: false;
};
}> | {
toolName: string;
toolCallId: string;
input: unknown;
dynamic: true;
};
/**
* Optional callback function that is invoked when a tool call is received.
*/
export type ChatOnToolCallCallback<TUIMessage extends UIMessage = UIMessage> = (options: {
toolCall: InferUIMessageToolCall<TUIMessage>;
}) => void | PromiseLike<void>;
/**
* Function that is called when the assistant response has finished streaming.
*/
export type ChatOnFinishCallback<TUIMessage extends UIMessage> = (options: {
message: TUIMessage;
messages: TUIMessage[];
isAbort: boolean;
isDisconnect: boolean;
isError: boolean;
}) => void;
/**
* Optional callback function that is called when a data part is received.
*/
export type ChatOnDataCallback<TUIMessage extends UIMessage> = (dataPart: DataUIPart<InferUIMessageData<TUIMessage>>) => void;
/**
* Transport interface for sending and receiving chat messages.
*/
export interface ChatTransport<TUIMessage extends UIMessage> {
sendMessages: (options: {
chatId: string;
messages: TUIMessage[];
abortSignal: AbortSignal;
requestMetadata?: unknown;
trigger: 'submit-message' | 'regenerate-message';
messageId?: string;
}) => Promise<ReadableStream<unknown>>;
reconnectToStream: (options: {
chatId: string;
}) => Promise<ReadableStream<unknown> | null>;
}
/**
* Chat initialization options.
*/
export interface ChatInit<TUIMessage extends UIMessage> {
/** A unique identifier for the chat. If not provided, a random one will be generated. */
id?: string;
messages?: TUIMessage[];
/** A way to provide a function for generating message and chat IDs. */
generateId?: IdGenerator;
transport?: ChatTransport<TUIMessage>;
/** Callback function to be called when an error is encountered. */
onError?: ChatOnErrorCallback;
/** Optional callback function that is invoked when a tool call is received. */
onToolCall?: ChatOnToolCallCallback<TUIMessage>;
/** Function that is called when the assistant response has finished streaming. */
onFinish?: ChatOnFinishCallback<TUIMessage>;
/** Optional callback function that is called when a data part is received. */
onData?: ChatOnDataCallback<TUIMessage>;
/**
* When provided, this function will be called when the stream is finished or a tool call is added
* to determine if the current messages should be resubmitted.
*/
sendAutomaticallyWhen?: (options: {
messages: TUIMessage[];
}) => boolean | PromiseLike<boolean>;
}
/**
* Abstract base class for chat implementations.
*/
export interface AbstractChat<TUIMessage extends UIMessage> {
readonly id: string;
readonly generateId: IdGenerator;
status: ChatStatus;
error: Error | undefined;
messages: TUIMessage[];
lastMessage: TUIMessage | undefined;
sendMessage: (message?: (Omit<TUIMessage, 'id' | 'role'> & {
id?: TUIMessage['id'];
role?: TUIMessage['role'];
text?: never;
files?: never;
messageId?: string;
}) | {
text: string;
files?: FileList | FileUIPart[];
metadata?: InferUIMessageMetadata<TUIMessage>;
parts?: never;
messageId?: string;
} | {
files: FileList | FileUIPart[];
metadata?: InferUIMessageMetadata<TUIMessage>;
parts?: never;
messageId?: string;
}, options?: {
headers?: Record<string, string> | Headers;
body?: object;
}) => Promise<void>;
regenerate: (options?: {
messageId?: string;
} & {
headers?: Record<string, string> | Headers;
body?: object;
}) => Promise<void>;
resumeStream: (options?: {
headers?: Record<string, string> | Headers;
body?: object;
}) => Promise<void>;
resetConversationId: () => void;
clearError: () => void;
addToolResult: <TTool extends keyof InferUIMessageTools<TUIMessage>>(params: {
tool: TTool;
toolCallId: string;
output: InferUIMessageTools<TUIMessage>[TTool]['output'];
}) => Promise<void>;
stop: () => Promise<void>;
}
export type AddToolResult = AbstractChat<UIMessage>['addToolResult'];
export type AddToolResultWithOutput = (params: Pick<Parameters<AddToolResult>[0], 'output'>) => ReturnType<AddToolResult>;
export type SearchToolInput = {
query: string;
number_of_results?: number;
facet_filters?: string[][];
};
export type ApplyFiltersParams = {
query?: string;
facetFilters?: string[][];
};
export type ChatLayoutOwnProps<TMessage extends ChatMessageBase = ChatMessageBase> = {
open: boolean;
maximized: boolean;
headerComponent: JSX.Element;
messagesComponent: JSX.Element;
promptComponent: JSX.Element;
classNames?: {
root?: string | string[];
container?: string | string[];
};
isClearing?: boolean;
clearMessages?: () => void;
onClearTransitionEnd?: () => void;
suggestions?: string[];
tools: ClientSideTools;
} & Pick<ChatState<TMessage>, 'messages'> & Partial<Pick<ChatState<TMessage>, 'status'>> & Pick<AbstractChat<TMessage>, 'sendMessage' | 'regenerate' | 'stop' | 'error'> & ComponentProps<'div'>;
export type ClientSideToolComponentProps = {
message: ChatToolMessage;
indexUiState: object;
setIndexUiState: (state: object) => void;
onClose: () => void;
addToolResult: AddToolResultWithOutput;
applyFilters: (params: ApplyFiltersParams) => SearchParameters;
sendEvent: SendEventForHits;
};
export type ClientSideToolComponent = (props: ClientSideToolComponentProps) => JSX.Element;
export type ClientSideTool = {
layoutComponent?: ClientSideToolComponent;
streamInput?: boolean;
addToolResult: AddToolResult;
sendEvent?: SendEventForHits;
onToolCall?: (params: Parameters<NonNullable<ChatInit<UIMessage>['onToolCall']>>[0]['toolCall'] & {
addToolResult: AddToolResultWithOutput;
}) => void;
applyFilters: (params: ApplyFiltersParams) => SearchParameters;
};
export type ClientSideTools = Record<string, ClientSideTool>;
export type UserClientSideTool = Omit<ClientSideTool, 'addToolResult' | 'applyFilters' | 'sendEvent'>;
export type UserClientSideTools = Record<string, UserClientSideTool>;
export type ChatEmptyProps = {
/**
* Function to send a message to the chat
*/
sendMessage?: ChatLayoutOwnProps['sendMessage'];
/**
* Current chat status
*/
status?: ChatStatus;
/**
* Callback to close the chat
*/
onClose?: () => void;
/**
* Function to set the prompt input value
*/
setInput?: (input: string) => void;
};
export {};