@tanstack/ai-react 0.15.14 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.d.ts +23 -0
- package/dist/esm/index.js +34 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mcp-apps.d.ts +20 -0
- package/dist/esm/mcp-apps.js +52 -0
- package/dist/esm/mcp-apps.js.map +1 -0
- package/dist/esm/realtime-types.d.ts +109 -0
- package/dist/esm/types.d.ts +150 -0
- package/dist/esm/use-audio-recorder.d.ts +43 -0
- package/dist/esm/use-audio-recorder.js +49 -0
- package/dist/esm/use-audio-recorder.js.map +1 -0
- package/dist/esm/use-chat.d.ts +4 -0
- package/dist/esm/use-chat.js +258 -0
- package/dist/esm/use-chat.js.map +1 -0
- package/dist/esm/use-generate-audio.d.ts +87 -0
- package/dist/esm/use-generate-audio.js +26 -0
- package/dist/esm/use-generate-audio.js.map +1 -0
- package/dist/esm/use-generate-image.d.ts +89 -0
- package/dist/esm/use-generate-image.js +26 -0
- package/dist/esm/use-generate-image.js.map +1 -0
- package/dist/esm/use-generate-speech.d.ts +83 -0
- package/dist/esm/use-generate-speech.js +26 -0
- package/dist/esm/use-generate-speech.js.map +1 -0
- package/dist/esm/use-generate-video.d.ts +94 -0
- package/dist/esm/use-generate-video.js +107 -0
- package/dist/esm/use-generate-video.js.map +1 -0
- package/dist/esm/use-generation.d.ts +80 -0
- package/dist/esm/use-generation.js +94 -0
- package/dist/esm/use-generation.js.map +1 -0
- package/dist/esm/use-mcp-app-bridge.d.ts +25 -0
- package/dist/esm/use-mcp-app-bridge.js +26 -0
- package/dist/esm/use-mcp-app-bridge.js.map +1 -0
- package/dist/esm/use-realtime-chat.d.ts +42 -0
- package/dist/esm/use-realtime-chat.js +189 -0
- package/dist/esm/use-realtime-chat.js.map +1 -0
- package/dist/esm/use-summarize.d.ts +86 -0
- package/dist/esm/use-summarize.js +26 -0
- package/dist/esm/use-summarize.js.map +1 -0
- package/dist/esm/use-transcription.d.ts +91 -0
- package/dist/esm/use-transcription.js +23 -0
- package/dist/esm/use-transcription.js.map +1 -0
- package/package.json +15 -4
- package/src/index.ts +11 -0
- package/src/mcp-apps.tsx +88 -0
- package/src/use-audio-recorder.ts +112 -0
- package/src/use-generate-audio.ts +5 -8
- package/src/use-generate-image.ts +5 -8
- package/src/use-generate-speech.ts +7 -7
- package/src/use-generate-video.ts +18 -9
- package/src/use-generation.ts +17 -6
- package/src/use-mcp-app-bridge.ts +60 -0
- package/src/use-summarize.ts +7 -8
- package/src/use-transcription.ts +9 -11
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { useChat } from './use-chat.js';
|
|
2
|
+
export { useRealtimeChat } from './use-realtime-chat.js';
|
|
3
|
+
export { useMcpAppBridge } from './use-mcp-app-bridge.js';
|
|
4
|
+
export type { UseMcpAppBridgeOptions } from './use-mcp-app-bridge.js';
|
|
5
|
+
export type { DeepPartial, UseChatOptions, UseChatReturn, UIMessage, ChatRequestBody, } from './types.js';
|
|
6
|
+
export type { UseRealtimeChatOptions, UseRealtimeChatReturn, } from './realtime-types.js';
|
|
7
|
+
export { useGeneration } from './use-generation.js';
|
|
8
|
+
export type { UseGenerationOptions, UseGenerationReturn, } from './use-generation.js';
|
|
9
|
+
export { useGenerateImage } from './use-generate-image.js';
|
|
10
|
+
export type { UseGenerateImageOptions, UseGenerateImageReturn, } from './use-generate-image.js';
|
|
11
|
+
export { useGenerateAudio } from './use-generate-audio.js';
|
|
12
|
+
export type { UseGenerateAudioOptions, UseGenerateAudioReturn, } from './use-generate-audio.js';
|
|
13
|
+
export { useGenerateSpeech } from './use-generate-speech.js';
|
|
14
|
+
export type { UseGenerateSpeechOptions, UseGenerateSpeechReturn, } from './use-generate-speech.js';
|
|
15
|
+
export { useTranscription } from './use-transcription.js';
|
|
16
|
+
export type { UseTranscriptionOptions, UseTranscriptionReturn, } from './use-transcription.js';
|
|
17
|
+
export { useSummarize } from './use-summarize.js';
|
|
18
|
+
export type { UseSummarizeOptions, UseSummarizeReturn } from './use-summarize.js';
|
|
19
|
+
export { useGenerateVideo } from './use-generate-video.js';
|
|
20
|
+
export type { UseGenerateVideoOptions, UseGenerateVideoReturn, } from './use-generate-video.js';
|
|
21
|
+
export { useAudioRecorder } from './use-audio-recorder.js';
|
|
22
|
+
export type { UseAudioRecorderOptions, UseAudioRecorderReturn, } from './use-audio-recorder.js';
|
|
23
|
+
export { fetchServerSentEvents, fetchHttpStream, xhrServerSentEvents, xhrHttpStream, stream, rpcStream, createChatClientOptions, createMcpAppBridge, type McpAppBridge, type CreateMcpAppBridgeOptions, type ChatFetcher, type ChatFetcherInput, type ChatFetcherOptions, type ConnectionAdapter, type ConnectConnectionAdapter, type SubscribeConnectionAdapter, type RunAgentInputContext, type FetchConnectionOptions, type XhrConnectionOptions, type InferChatMessages, type GenerationClientState, type ImageGenerateInput, type AudioGenerateInput, type SpeechGenerateInput, type TranscriptionGenerateInput, type SummarizeGenerateInput, type VideoGenerateInput, type VideoGenerateResult, type VideoStatusInfo, } from '@tanstack/ai-client';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useChat } from "./use-chat.js";
|
|
2
|
+
import { useRealtimeChat } from "./use-realtime-chat.js";
|
|
3
|
+
import { useMcpAppBridge } from "./use-mcp-app-bridge.js";
|
|
4
|
+
import { useGeneration } from "./use-generation.js";
|
|
5
|
+
import { useGenerateImage } from "./use-generate-image.js";
|
|
6
|
+
import { useGenerateAudio } from "./use-generate-audio.js";
|
|
7
|
+
import { useGenerateSpeech } from "./use-generate-speech.js";
|
|
8
|
+
import { useTranscription } from "./use-transcription.js";
|
|
9
|
+
import { useSummarize } from "./use-summarize.js";
|
|
10
|
+
import { useGenerateVideo } from "./use-generate-video.js";
|
|
11
|
+
import { useAudioRecorder } from "./use-audio-recorder.js";
|
|
12
|
+
import { createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, rpcStream, stream, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
13
|
+
export {
|
|
14
|
+
createChatClientOptions,
|
|
15
|
+
createMcpAppBridge,
|
|
16
|
+
fetchHttpStream,
|
|
17
|
+
fetchServerSentEvents,
|
|
18
|
+
rpcStream,
|
|
19
|
+
stream,
|
|
20
|
+
useAudioRecorder,
|
|
21
|
+
useChat,
|
|
22
|
+
useGenerateAudio,
|
|
23
|
+
useGenerateImage,
|
|
24
|
+
useGenerateSpeech,
|
|
25
|
+
useGenerateVideo,
|
|
26
|
+
useGeneration,
|
|
27
|
+
useMcpAppBridge,
|
|
28
|
+
useRealtimeChat,
|
|
29
|
+
useSummarize,
|
|
30
|
+
useTranscription,
|
|
31
|
+
xhrHttpStream,
|
|
32
|
+
xhrServerSentEvents
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { UIResourcePart } from '@tanstack/ai';
|
|
2
|
+
import { McpAppBridge } from '@tanstack/ai-client';
|
|
3
|
+
import { JSX } from 'react';
|
|
4
|
+
export interface MCPAppResourceProps {
|
|
5
|
+
/** The ui-resource part from a UIMessage assistant part. */
|
|
6
|
+
part: UIResourcePart;
|
|
7
|
+
/**
|
|
8
|
+
* Framework-agnostic bridge for tool calls, prompt sending, and link opening.
|
|
9
|
+
* Omit it to render the widget in display-only mode — iframe interactions
|
|
10
|
+
* that would trigger tool calls or prompts are ignored.
|
|
11
|
+
*/
|
|
12
|
+
bridge?: McpAppBridge;
|
|
13
|
+
/** Sandbox iframe configuration — must include the proxy page URL. */
|
|
14
|
+
sandbox: {
|
|
15
|
+
url: URL;
|
|
16
|
+
};
|
|
17
|
+
/** Optional structured arguments forwarded to the guest UI once it's ready. */
|
|
18
|
+
toolInput?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
export declare function MCPAppResource(props: MCPAppResourceProps): JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AppRenderer } from "@mcp-ui/client";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function resultToText(result) {
|
|
7
|
+
if (typeof result === "string") return result;
|
|
8
|
+
const stringify = JSON.stringify;
|
|
9
|
+
return stringify(result) ?? "null";
|
|
10
|
+
}
|
|
11
|
+
function MCPAppResource(props) {
|
|
12
|
+
const { bridge } = props;
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
AppRenderer,
|
|
15
|
+
{
|
|
16
|
+
toolName: props.part.toolName,
|
|
17
|
+
sandbox: props.sandbox,
|
|
18
|
+
html: props.part.resource.text,
|
|
19
|
+
toolResourceUri: props.part.resource.uri,
|
|
20
|
+
toolInput: props.toolInput,
|
|
21
|
+
onCallTool: bridge ? async ({ name, arguments: args }) => {
|
|
22
|
+
const result = await bridge.callTool({
|
|
23
|
+
serverId: props.part.serverId,
|
|
24
|
+
toolName: name,
|
|
25
|
+
args
|
|
26
|
+
});
|
|
27
|
+
const structuredContent = isRecord(result) ? result : void 0;
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: resultToText(result)
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
structuredContent
|
|
36
|
+
};
|
|
37
|
+
} : void 0,
|
|
38
|
+
onMessage: bridge ? async ({ content }) => {
|
|
39
|
+
const text = content.filter(
|
|
40
|
+
(c) => c.type === "text"
|
|
41
|
+
).map((c) => c.text).join("");
|
|
42
|
+
if (text) await bridge.sendPrompt(text);
|
|
43
|
+
return {};
|
|
44
|
+
} : void 0,
|
|
45
|
+
onOpenLink: bridge ? ({ url }) => Promise.resolve(bridge.openLink(url)) : void 0
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
MCPAppResource
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=mcp-apps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-apps.js","sources":["../../src/mcp-apps.tsx"],"sourcesContent":["import { AppRenderer } from '@mcp-ui/client'\nimport type { UIResourcePart } from '@tanstack/ai'\nimport type { McpAppBridge } from '@tanstack/ai-client'\nimport type { JSX } from 'react'\n\nexport interface MCPAppResourceProps {\n /** The ui-resource part from a UIMessage assistant part. */\n part: UIResourcePart\n /**\n * Framework-agnostic bridge for tool calls, prompt sending, and link opening.\n * Omit it to render the widget in display-only mode — iframe interactions\n * that would trigger tool calls or prompts are ignored.\n */\n bridge?: McpAppBridge\n /** Sandbox iframe configuration — must include the proxy page URL. */\n sandbox: { url: URL }\n /** Optional structured arguments forwarded to the guest UI once it's ready. */\n toolInput?: Record<string, unknown>\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Coalesce an arbitrary bridge result into the `string` required by the\n * `CallToolResult` text content block. `JSON.stringify` is typed to return\n * `string` but actually returns `undefined` for inputs like `undefined` or a\n * function, so we narrow explicitly to avoid `text: undefined`.\n */\nfunction resultToText(result: unknown): string {\n if (typeof result === 'string') return result\n // `JSON.stringify`'s lib signature claims `string`, but it returns\n // `undefined` for `undefined`/function inputs; type the call honestly.\n const stringify: (value: unknown) => string | undefined = JSON.stringify\n return stringify(result) ?? 'null'\n}\n\nexport function MCPAppResource(props: MCPAppResourceProps): JSX.Element {\n const { bridge } = props\n return (\n <AppRenderer\n toolName={props.part.toolName}\n sandbox={props.sandbox}\n html={props.part.resource.text}\n toolResourceUri={props.part.resource.uri}\n toolInput={props.toolInput}\n onCallTool={\n bridge\n ? async ({ name, arguments: args }) => {\n const result = await bridge.callTool({\n serverId: props.part.serverId,\n toolName: name,\n args,\n })\n const structuredContent = isRecord(result) ? result : undefined\n return {\n content: [\n {\n type: 'text' as const,\n text: resultToText(result),\n },\n ],\n structuredContent,\n }\n }\n : undefined\n }\n onMessage={\n bridge\n ? async ({ content }) => {\n const text = content\n .filter(\n (c): c is { type: 'text'; text: string } => c.type === 'text',\n )\n .map((c) => c.text)\n .join('')\n if (text) await bridge.sendPrompt(text)\n return {}\n }\n : undefined\n }\n onOpenLink={\n bridge ? ({ url }) => Promise.resolve(bridge.openLink(url)) : undefined\n }\n />\n )\n}\n"],"names":[],"mappings":";;AAoBA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAQA,SAAS,aAAa,QAAyB;AAC7C,MAAI,OAAO,WAAW,SAAU,QAAO;AAGvC,QAAM,YAAoD,KAAK;AAC/D,SAAO,UAAU,MAAM,KAAK;AAC9B;AAEO,SAAS,eAAe,OAAyC;AACtE,QAAM,EAAE,WAAW;AACnB,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,UAAU,MAAM,KAAK;AAAA,MACrB,SAAS,MAAM;AAAA,MACf,MAAM,MAAM,KAAK,SAAS;AAAA,MAC1B,iBAAiB,MAAM,KAAK,SAAS;AAAA,MACrC,WAAW,MAAM;AAAA,MACjB,YACE,SACI,OAAO,EAAE,MAAM,WAAW,WAAW;AACnC,cAAM,SAAS,MAAM,OAAO,SAAS;AAAA,UACnC,UAAU,MAAM,KAAK;AAAA,UACrB,UAAU;AAAA,UACV;AAAA,QAAA,CACD;AACD,cAAM,oBAAoB,SAAS,MAAM,IAAI,SAAS;AACtD,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,aAAa,MAAM;AAAA,YAAA;AAAA,UAC3B;AAAA,UAEF;AAAA,QAAA;AAAA,MAEJ,IACA;AAAA,MAEN,WACE,SACI,OAAO,EAAE,cAAc;AACrB,cAAM,OAAO,QACV;AAAA,UACC,CAAC,MAA2C,EAAE,SAAS;AAAA,QAAA,EAExD,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,EAAE;AACV,YAAI,KAAM,OAAM,OAAO,WAAW,IAAI;AACtC,eAAO,CAAA;AAAA,MACT,IACA;AAAA,MAEN,YACE,SAAS,CAAC,EAAE,UAAU,QAAQ,QAAQ,OAAO,SAAS,GAAG,CAAC,IAAI;AAAA,IAAA;AAAA,EAAA;AAItE;"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { AnyClientTool, RealtimeMessage, RealtimeMode, RealtimeStatus, RealtimeToken } from '@tanstack/ai';
|
|
2
|
+
import { RealtimeAdapter } from '@tanstack/ai-client';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the useRealtimeChat hook.
|
|
5
|
+
*/
|
|
6
|
+
export interface UseRealtimeChatOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Function to fetch a realtime token from the server.
|
|
9
|
+
* Called on connect and when token needs refresh.
|
|
10
|
+
*/
|
|
11
|
+
getToken: () => Promise<RealtimeToken>;
|
|
12
|
+
/**
|
|
13
|
+
* The realtime adapter to use (e.g., openaiRealtime())
|
|
14
|
+
*/
|
|
15
|
+
adapter: RealtimeAdapter;
|
|
16
|
+
/**
|
|
17
|
+
* Client-side tools with execution logic
|
|
18
|
+
*/
|
|
19
|
+
tools?: ReadonlyArray<AnyClientTool>;
|
|
20
|
+
/**
|
|
21
|
+
* Auto-play assistant audio (default: true)
|
|
22
|
+
*/
|
|
23
|
+
autoPlayback?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Request microphone access on connect (default: true)
|
|
26
|
+
*/
|
|
27
|
+
autoCapture?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* System instructions for the assistant
|
|
30
|
+
*/
|
|
31
|
+
instructions?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Voice to use for audio output
|
|
34
|
+
*/
|
|
35
|
+
voice?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Voice activity detection mode (default: 'server')
|
|
38
|
+
*/
|
|
39
|
+
vadMode?: 'server' | 'semantic' | 'manual';
|
|
40
|
+
/**
|
|
41
|
+
* Output modalities for responses (e.g., ['audio', 'text'])
|
|
42
|
+
*/
|
|
43
|
+
outputModalities?: Array<'audio' | 'text'>;
|
|
44
|
+
/**
|
|
45
|
+
* Temperature for generation (provider-specific range)
|
|
46
|
+
*/
|
|
47
|
+
temperature?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of tokens in a response
|
|
50
|
+
*/
|
|
51
|
+
maxOutputTokens?: number | 'inf';
|
|
52
|
+
/**
|
|
53
|
+
* Eagerness level for semantic VAD ('low', 'medium', 'high')
|
|
54
|
+
*/
|
|
55
|
+
semanticEagerness?: 'low' | 'medium' | 'high';
|
|
56
|
+
onConnect?: () => void;
|
|
57
|
+
onDisconnect?: () => void;
|
|
58
|
+
onError?: (error: Error) => void;
|
|
59
|
+
onMessage?: (message: RealtimeMessage) => void;
|
|
60
|
+
onModeChange?: (mode: RealtimeMode) => void;
|
|
61
|
+
onInterrupted?: () => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Return type for the useRealtimeChat hook.
|
|
65
|
+
*/
|
|
66
|
+
export interface UseRealtimeChatReturn {
|
|
67
|
+
/** Current connection status */
|
|
68
|
+
status: RealtimeStatus;
|
|
69
|
+
/** Current error, if any */
|
|
70
|
+
error: Error | null;
|
|
71
|
+
/** Connect to the realtime session */
|
|
72
|
+
connect: () => Promise<void>;
|
|
73
|
+
/** Disconnect from the realtime session */
|
|
74
|
+
disconnect: () => Promise<void>;
|
|
75
|
+
/** Current mode (idle, listening, thinking, speaking) */
|
|
76
|
+
mode: RealtimeMode;
|
|
77
|
+
/** Conversation messages */
|
|
78
|
+
messages: Array<RealtimeMessage>;
|
|
79
|
+
/** User transcript while speaking (before finalized) */
|
|
80
|
+
pendingUserTranscript: string | null;
|
|
81
|
+
/** Assistant transcript while speaking (before finalized) */
|
|
82
|
+
pendingAssistantTranscript: string | null;
|
|
83
|
+
/** Start listening for voice input (manual VAD mode) */
|
|
84
|
+
startListening: () => void;
|
|
85
|
+
/** Stop listening for voice input (manual VAD mode) */
|
|
86
|
+
stopListening: () => void;
|
|
87
|
+
/** Interrupt the current assistant response */
|
|
88
|
+
interrupt: () => void;
|
|
89
|
+
/** Send a text message instead of voice */
|
|
90
|
+
sendText: (text: string) => void;
|
|
91
|
+
/** Send an image to the conversation */
|
|
92
|
+
sendImage: (imageData: string, mimeType: string) => void;
|
|
93
|
+
/** Current input (microphone) volume level */
|
|
94
|
+
inputLevel: number;
|
|
95
|
+
/** Current output (speaker) volume level */
|
|
96
|
+
outputLevel: number;
|
|
97
|
+
/** Get frequency data for input audio visualization */
|
|
98
|
+
getInputFrequencyData: () => Uint8Array;
|
|
99
|
+
/** Get frequency data for output audio visualization */
|
|
100
|
+
getOutputFrequencyData: () => Uint8Array;
|
|
101
|
+
/** Get time domain data for input waveform */
|
|
102
|
+
getInputTimeDomainData: () => Uint8Array;
|
|
103
|
+
/** Get time domain data for output waveform */
|
|
104
|
+
getOutputTimeDomainData: () => Uint8Array;
|
|
105
|
+
/** Current VAD mode */
|
|
106
|
+
vadMode: 'server' | 'semantic' | 'manual';
|
|
107
|
+
/** Change VAD mode at runtime */
|
|
108
|
+
setVADMode: (mode: 'server' | 'semantic' | 'manual') => void;
|
|
109
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { AnyClientTool, InferSchemaType, ModelMessage, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
+
import { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, UIMessage } from '@tanstack/ai-client';
|
|
3
|
+
export type { ChatRequestBody, MultimodalContent, UIMessage };
|
|
4
|
+
/**
|
|
5
|
+
* Recursive partial — every property and every nested array element is optional.
|
|
6
|
+
* Used to type the in-flight `partial` value the hook exposes while a structured
|
|
7
|
+
* output stream is still arriving (the JSON has shape but is incomplete).
|
|
8
|
+
*/
|
|
9
|
+
export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartial<U>> : T extends object ? {
|
|
10
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
11
|
+
} : T;
|
|
12
|
+
/**
|
|
13
|
+
* Options for the useChat hook.
|
|
14
|
+
*
|
|
15
|
+
* Pass either `connection` or `fetcher` — the XOR is enforced at the type
|
|
16
|
+
* level via `ChatTransport`.
|
|
17
|
+
*
|
|
18
|
+
* This extends ChatClientOptions but omits the state change callbacks that are
|
|
19
|
+
* managed internally by React state:
|
|
20
|
+
* - `onMessagesChange` - Managed by React state (exposed as `messages`)
|
|
21
|
+
* - `onLoadingChange` - Managed by React state (exposed as `isLoading`)
|
|
22
|
+
* - `onErrorChange` - Managed by React state (exposed as `error`)
|
|
23
|
+
* - `onStatusChange` - Managed by React state (exposed as `status`)
|
|
24
|
+
*
|
|
25
|
+
* All other callbacks (onResponse, onChunk, onFinish, onError) are
|
|
26
|
+
* passed through to the underlying ChatClient and can be used for side effects.
|
|
27
|
+
*
|
|
28
|
+
* When `outputSchema` is supplied, the hook returns a typed `partial` (live
|
|
29
|
+
* progressive object, updated from `TEXT_MESSAGE_CONTENT` deltas via
|
|
30
|
+
* `parsePartialJSON`) and `final` (validated terminal payload from the
|
|
31
|
+
* `structured-output.complete` event). The schema is used purely for type
|
|
32
|
+
* inference on the client — server-side validation still runs against the
|
|
33
|
+
* schema you pass to `chat({ outputSchema })` on the server route.
|
|
34
|
+
*
|
|
35
|
+
* Note: Connection and body changes will recreate the ChatClient instance.
|
|
36
|
+
* To update these options, remount the component or use a key prop.
|
|
37
|
+
*/
|
|
38
|
+
export type UseChatOptions<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>> = DistributedOmit<ChatClientOptions<TTools, TContext>, 'onMessagesChange' | 'onLoadingChange' | 'onErrorChange' | 'onStatusChange' | 'onSubscriptionChange' | 'onConnectionStatusChange' | 'onSessionGeneratingChange' | 'context' | 'devtools'> & {
|
|
39
|
+
/** Display options for TanStack AI Devtools. */
|
|
40
|
+
devtools?: AIDevtoolsDisplayOptions;
|
|
41
|
+
/**
|
|
42
|
+
* Opt into mount-time live subscription behavior.
|
|
43
|
+
* When enabled, the hook subscribes on mount and unsubscribes on unmount.
|
|
44
|
+
*/
|
|
45
|
+
live?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Standard-schema-compatible schema (Zod, Valibot, ArkType, or a plain JSON
|
|
48
|
+
* Schema). Used to infer the shape of `partial` and `final` in the return.
|
|
49
|
+
* The schema is **not** sent to the server — server-side validation runs
|
|
50
|
+
* against the schema passed to `chat({ outputSchema })` on the server route.
|
|
51
|
+
*/
|
|
52
|
+
outputSchema?: TSchema;
|
|
53
|
+
} & ClientContextOptionFromTools<TTools, TContext>;
|
|
54
|
+
/**
|
|
55
|
+
* Discriminated return shape: when `outputSchema` is supplied, the hook adds
|
|
56
|
+
* typed `partial` / `final` fields; when it is omitted (default), the return
|
|
57
|
+
* is unchanged.
|
|
58
|
+
*/
|
|
59
|
+
export type UseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined> = BaseUseChatReturn<TTools, TSchema extends SchemaInput ? InferSchemaType<TSchema> : unknown> & (TSchema extends SchemaInput ? {
|
|
60
|
+
/**
|
|
61
|
+
* Live, progressively-parsed structured output. Updated from
|
|
62
|
+
* `TEXT_MESSAGE_CONTENT` deltas via `parsePartialJSON` while the stream
|
|
63
|
+
* is still arriving, and snapped to the validated payload when
|
|
64
|
+
* `structured-output.complete` fires. Resets on every new run
|
|
65
|
+
* (`sendMessage` / `reload`).
|
|
66
|
+
*/
|
|
67
|
+
partial: DeepPartial<InferSchemaType<TSchema>>;
|
|
68
|
+
/**
|
|
69
|
+
* Final, schema-validated structured output. `null` until the terminal
|
|
70
|
+
* `structured-output.complete` event arrives. Resets on every new run.
|
|
71
|
+
*/
|
|
72
|
+
final: InferSchemaType<TSchema> | null;
|
|
73
|
+
} : Record<never, never>);
|
|
74
|
+
interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, TData = unknown> {
|
|
75
|
+
/**
|
|
76
|
+
* Current messages in the conversation. When `outputSchema` is supplied,
|
|
77
|
+
* `messages[i].parts.find(p => p.type === 'structured-output')` is typed
|
|
78
|
+
* with the schema's inferred shape — `data: T`, `partial: DeepPartial<T>`.
|
|
79
|
+
*/
|
|
80
|
+
messages: Array<UIMessage<TTools, TData>>;
|
|
81
|
+
/**
|
|
82
|
+
* Send a message and get a response.
|
|
83
|
+
* Can be a simple string or multimodal content with images, audio, etc.
|
|
84
|
+
*/
|
|
85
|
+
sendMessage: (content: string | MultimodalContent) => Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Append a message to the conversation
|
|
88
|
+
*/
|
|
89
|
+
append: (message: ModelMessage | UIMessage<TTools, TData>) => Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Add the result of a client-side tool execution
|
|
92
|
+
*/
|
|
93
|
+
addToolResult: (result: {
|
|
94
|
+
toolCallId: string;
|
|
95
|
+
tool: string;
|
|
96
|
+
output: any;
|
|
97
|
+
state?: 'output-available' | 'output-error';
|
|
98
|
+
errorText?: string;
|
|
99
|
+
}) => Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Respond to a tool approval request
|
|
102
|
+
*/
|
|
103
|
+
addToolApprovalResponse: (response: {
|
|
104
|
+
id: string;
|
|
105
|
+
approved: boolean;
|
|
106
|
+
}) => Promise<void>;
|
|
107
|
+
/**
|
|
108
|
+
* Reload the last assistant message
|
|
109
|
+
*/
|
|
110
|
+
reload: () => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Stop the current response generation
|
|
113
|
+
*/
|
|
114
|
+
stop: () => void;
|
|
115
|
+
/**
|
|
116
|
+
* Whether a response is currently being generated
|
|
117
|
+
*/
|
|
118
|
+
isLoading: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Current error, if any
|
|
121
|
+
*/
|
|
122
|
+
error: Error | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Current status of the chat client
|
|
125
|
+
*/
|
|
126
|
+
status: ChatClientState;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the subscription loop is currently active
|
|
129
|
+
*/
|
|
130
|
+
isSubscribed: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Current connection lifecycle status
|
|
133
|
+
*/
|
|
134
|
+
connectionStatus: ConnectionStatus;
|
|
135
|
+
/**
|
|
136
|
+
* Whether the shared session is actively generating.
|
|
137
|
+
* Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).
|
|
138
|
+
* Unlike `isLoading` (request-local), this reflects shared generation
|
|
139
|
+
* activity visible to all subscribers (e.g. across tabs/devices).
|
|
140
|
+
*/
|
|
141
|
+
sessionGenerating: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Set messages manually
|
|
144
|
+
*/
|
|
145
|
+
setMessages: (messages: Array<UIMessage<TTools, TData>>) => void;
|
|
146
|
+
/**
|
|
147
|
+
* Clear all messages
|
|
148
|
+
*/
|
|
149
|
+
clear: () => void;
|
|
150
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AudioRecorderOptions, AudioRecording, InferAudioRecordingOutput } from '@tanstack/ai-client';
|
|
2
|
+
export type UseAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {
|
|
3
|
+
/**
|
|
4
|
+
* Optional transform applied to the recording when `stop()` resolves. Its
|
|
5
|
+
* (awaited) return value becomes `recording` and the resolved value of
|
|
6
|
+
* `stop()`. Return nothing to keep the raw `AudioRecording`.
|
|
7
|
+
*/
|
|
8
|
+
onComplete?: TOnComplete;
|
|
9
|
+
};
|
|
10
|
+
export interface UseAudioRecorderReturn<TOutput> {
|
|
11
|
+
/** Latest recording (transformed if `onComplete` provided), or null. */
|
|
12
|
+
recording: TOutput | null;
|
|
13
|
+
/** True while actively capturing audio. */
|
|
14
|
+
isRecording: boolean;
|
|
15
|
+
/** Whether the browser supports recording (getUserMedia + MediaRecorder). */
|
|
16
|
+
isSupported: boolean;
|
|
17
|
+
/** Acquire the mic and begin recording. */
|
|
18
|
+
start: () => Promise<void>;
|
|
19
|
+
/** Stop and resolve with the completed recording (transformed if `onComplete` provided). */
|
|
20
|
+
stop: () => Promise<TOutput>;
|
|
21
|
+
/** Discard the in-progress recording and release the mic. */
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* React hook for recording an audio message. The resolved
|
|
26
|
+
* {@link AudioRecording} carries `.part` (an audio content part for
|
|
27
|
+
* `useChat.sendMessage`) and `.base64` (for the generation hooks).
|
|
28
|
+
*
|
|
29
|
+
* Errors are delivered via `onError`. `start()` and `stop()` also reject on
|
|
30
|
+
* failure (and `stop()` rejects with `Recording cancelled` if the component
|
|
31
|
+
* unmounts while a stop is in flight) — handle one channel, not both.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```tsx
|
|
35
|
+
* const { isRecording, start, stop, recording } = useAudioRecorder()
|
|
36
|
+
* const { sendMessage } = useChat({ connection })
|
|
37
|
+
* // ...
|
|
38
|
+
* const rec = await stop()
|
|
39
|
+
* sendMessage({ content: [rec.part] })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function useAudioRecorder<TOnComplete extends (recording: AudioRecording) => unknown>(options: UseAudioRecorderOptions<TOnComplete>): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>;
|
|
43
|
+
export declare function useAudioRecorder(options?: UseAudioRecorderOptions<undefined>): UseAudioRecorderReturn<AudioRecording>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useState, useRef, useMemo, useEffect, useCallback } from "react";
|
|
2
|
+
import { AudioRecorder } from "@tanstack/ai-client";
|
|
3
|
+
function useAudioRecorder(options = {}) {
|
|
4
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
5
|
+
const [recording, setRecording] = useState(null);
|
|
6
|
+
const optionsRef = useRef(options);
|
|
7
|
+
optionsRef.current = options;
|
|
8
|
+
const recorder = useMemo(
|
|
9
|
+
() => new AudioRecorder({
|
|
10
|
+
...options.audio !== void 0 && { audio: options.audio },
|
|
11
|
+
...options.mimeType !== void 0 && { mimeType: options.mimeType },
|
|
12
|
+
onError: (err) => optionsRef.current.onError?.(err)
|
|
13
|
+
}),
|
|
14
|
+
// Recorder config (audio/mimeType) is captured once at mount, matching the
|
|
15
|
+
// other hooks' create-once pattern.
|
|
16
|
+
[]
|
|
17
|
+
);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const unsubscribe = recorder.subscribe((state) => {
|
|
20
|
+
setIsRecording(state === "recording");
|
|
21
|
+
});
|
|
22
|
+
return () => {
|
|
23
|
+
unsubscribe();
|
|
24
|
+
recorder.cancel();
|
|
25
|
+
};
|
|
26
|
+
}, [recorder]);
|
|
27
|
+
const start = useCallback(() => recorder.start(), [recorder]);
|
|
28
|
+
const stop = useCallback(async () => {
|
|
29
|
+
const recording2 = await recorder.stop();
|
|
30
|
+
const transformed = await optionsRef.current.onComplete?.(recording2);
|
|
31
|
+
const output = transformed === void 0 ? recording2 : transformed;
|
|
32
|
+
setRecording(() => output);
|
|
33
|
+
return output;
|
|
34
|
+
}, [recorder]);
|
|
35
|
+
const cancel = useCallback(() => recorder.cancel(), [recorder]);
|
|
36
|
+
return {
|
|
37
|
+
recording,
|
|
38
|
+
isRecording,
|
|
39
|
+
// recording is client-only; if SSR'd, gate UI on a mounted flag.
|
|
40
|
+
isSupported: AudioRecorder.isSupported(),
|
|
41
|
+
start,
|
|
42
|
+
stop,
|
|
43
|
+
cancel
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
useAudioRecorder
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=use-audio-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-audio-recorder.js","sources":["../../src/use-audio-recorder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { AudioRecorder } from '@tanstack/ai-client'\nimport type {\n AudioRecorderOptions,\n AudioRecording,\n InferAudioRecordingOutput,\n} from '@tanstack/ai-client'\n\nexport type UseAudioRecorderOptions<TOnComplete> = AudioRecorderOptions & {\n /**\n * Optional transform applied to the recording when `stop()` resolves. Its\n * (awaited) return value becomes `recording` and the resolved value of\n * `stop()`. Return nothing to keep the raw `AudioRecording`.\n */\n onComplete?: TOnComplete\n}\n\nexport interface UseAudioRecorderReturn<TOutput> {\n /** Latest recording (transformed if `onComplete` provided), or null. */\n recording: TOutput | null\n /** True while actively capturing audio. */\n isRecording: boolean\n /** Whether the browser supports recording (getUserMedia + MediaRecorder). */\n isSupported: boolean\n /** Acquire the mic and begin recording. */\n start: () => Promise<void>\n /** Stop and resolve with the completed recording (transformed if `onComplete` provided). */\n stop: () => Promise<TOutput>\n /** Discard the in-progress recording and release the mic. */\n cancel: () => void\n}\n\n/**\n * React hook for recording an audio message. The resolved\n * {@link AudioRecording} carries `.part` (an audio content part for\n * `useChat.sendMessage`) and `.base64` (for the generation hooks).\n *\n * Errors are delivered via `onError`. `start()` and `stop()` also reject on\n * failure (and `stop()` rejects with `Recording cancelled` if the component\n * unmounts while a stop is in flight) — handle one channel, not both.\n *\n * @example\n * ```tsx\n * const { isRecording, start, stop, recording } = useAudioRecorder()\n * const { sendMessage } = useChat({ connection })\n * // ...\n * const rec = await stop()\n * sendMessage({ content: [rec.part] })\n * ```\n */\nexport function useAudioRecorder<\n TOnComplete extends (recording: AudioRecording) => unknown,\n>(\n options: UseAudioRecorderOptions<TOnComplete>,\n): UseAudioRecorderReturn<InferAudioRecordingOutput<TOnComplete>>\nexport function useAudioRecorder(\n options?: UseAudioRecorderOptions<undefined>,\n): UseAudioRecorderReturn<AudioRecording>\nexport function useAudioRecorder(\n options: UseAudioRecorderOptions<(recording: AudioRecording) => unknown> = {},\n): UseAudioRecorderReturn<unknown> {\n const [isRecording, setIsRecording] = useState(false)\n const [recording, setRecording] = useState<unknown>(null)\n // Read the freshest callbacks at fire time without recreating the recorder.\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const recorder = useMemo(\n () =>\n new AudioRecorder({\n ...(options.audio !== undefined && { audio: options.audio }),\n ...(options.mimeType !== undefined && { mimeType: options.mimeType }),\n onError: (err) => optionsRef.current.onError?.(err),\n }),\n // Recorder config (audio/mimeType) is captured once at mount, matching the\n // other hooks' create-once pattern.\n [],\n )\n\n useEffect(() => {\n const unsubscribe = recorder.subscribe((state) => {\n setIsRecording(state === 'recording')\n })\n return () => {\n unsubscribe()\n recorder.cancel()\n }\n }, [recorder])\n\n const start = useCallback(() => recorder.start(), [recorder])\n const stop = useCallback(async () => {\n const recording = await recorder.stop()\n const transformed = await optionsRef.current.onComplete?.(recording)\n // Only `undefined` (returning nothing) falls back to the raw recording, so\n // a transform that returns null is preserved — matching the inferred type,\n // which excludes only undefined/void/null from the transform's return.\n const output = transformed === undefined ? recording : transformed\n setRecording(() => output)\n return output\n }, [recorder])\n const cancel = useCallback(() => recorder.cancel(), [recorder])\n\n return {\n recording,\n isRecording,\n // recording is client-only; if SSR'd, gate UI on a mounted flag.\n isSupported: AudioRecorder.isSupported(),\n start,\n stop,\n cancel,\n }\n}\n"],"names":["recording"],"mappings":";;AA0DO,SAAS,iBACd,UAA2E,IAC1C;AACjC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AACpD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAkB,IAAI;AAExD,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,WAAW;AAAA,IACf,MACE,IAAI,cAAc;AAAA,MAChB,GAAI,QAAQ,UAAU,UAAa,EAAE,OAAO,QAAQ,MAAA;AAAA,MACpD,GAAI,QAAQ,aAAa,UAAa,EAAE,UAAU,QAAQ,SAAA;AAAA,MAC1D,SAAS,CAAC,QAAQ,WAAW,QAAQ,UAAU,GAAG;AAAA,IAAA,CACnD;AAAA;AAAA;AAAA,IAGH,CAAA;AAAA,EAAC;AAGH,YAAU,MAAM;AACd,UAAM,cAAc,SAAS,UAAU,CAAC,UAAU;AAChD,qBAAe,UAAU,WAAW;AAAA,IACtC,CAAC;AACD,WAAO,MAAM;AACX,kBAAA;AACA,eAAS,OAAA;AAAA,IACX;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,QAAQ,YAAY,MAAM,SAAS,SAAS,CAAC,QAAQ,CAAC;AAC5D,QAAM,OAAO,YAAY,YAAY;AACnC,UAAMA,aAAY,MAAM,SAAS,KAAA;AACjC,UAAM,cAAc,MAAM,WAAW,QAAQ,aAAaA,UAAS;AAInE,UAAM,SAAS,gBAAgB,SAAYA,aAAY;AACvD,iBAAa,MAAM,MAAM;AACzB,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AACb,QAAM,SAAS,YAAY,MAAM,SAAS,UAAU,CAAC,QAAQ,CAAC;AAE9D,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA,IAEA,aAAa,cAAc,YAAA;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { AnyClientTool, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
+
import { InferredClientContext } from '@tanstack/ai-client';
|
|
3
|
+
import { UseChatOptions, UseChatReturn } from './types.js';
|
|
4
|
+
export declare function useChat<TTools extends ReadonlyArray<AnyClientTool> = any, TSchema extends SchemaInput | undefined = undefined, TContext = InferredClientContext<TTools>>(options: UseChatOptions<TTools, TSchema, TContext>): UseChatReturn<TTools, TSchema>;
|