@tanstack/ai-react 0.18.1 → 0.19.1
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/README.md +15 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -23
- package/dist/esm/mcp-app-resource.js +45 -45
- package/dist/esm/mcp-app-resource.js.map +1 -1
- package/dist/esm/mcp-apps.js +1 -4
- package/dist/esm/types.d.ts +29 -3
- package/dist/esm/use-audio-recorder.js +41 -46
- package/dist/esm/use-audio-recorder.js.map +1 -1
- package/dist/esm/use-chat.js +330 -275
- package/dist/esm/use-chat.js.map +1 -1
- package/dist/esm/use-generate-audio.d.ts +50 -4
- package/dist/esm/use-generate-audio.js +47 -23
- package/dist/esm/use-generate-audio.js.map +1 -1
- package/dist/esm/use-generate-image.d.ts +50 -4
- package/dist/esm/use-generate-image.js +49 -23
- package/dist/esm/use-generate-image.js.map +1 -1
- package/dist/esm/use-generate-speech.d.ts +50 -4
- package/dist/esm/use-generate-speech.js +43 -23
- package/dist/esm/use-generate-speech.js.map +1 -1
- package/dist/esm/use-generate-video.d.ts +50 -4
- package/dist/esm/use-generate-video.js +141 -103
- package/dist/esm/use-generate-video.js.map +1 -1
- package/dist/esm/use-generation.d.ts +59 -6
- package/dist/esm/use-generation.js +113 -90
- package/dist/esm/use-generation.js.map +1 -1
- package/dist/esm/use-mcp-app-bridge.js +46 -23
- package/dist/esm/use-mcp-app-bridge.js.map +1 -1
- package/dist/esm/use-realtime-chat.js +185 -185
- package/dist/esm/use-realtime-chat.js.map +1 -1
- package/dist/esm/use-summarize.d.ts +50 -4
- package/dist/esm/use-summarize.js +46 -23
- package/dist/esm/use-summarize.js.map +1 -1
- package/dist/esm/use-transcription.d.ts +50 -4
- package/dist/esm/use-transcription.js +51 -20
- package/dist/esm/use-transcription.js.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +10 -0
- package/src/types.ts +44 -0
- package/src/use-chat.ts +221 -35
- package/src/use-generate-audio.ts +64 -17
- package/src/use-generate-image.ts +64 -17
- package/src/use-generate-speech.ts +64 -17
- package/src/use-generate-video.ts +99 -18
- package/src/use-generation.ts +112 -18
- package/src/use-summarize.ts +64 -17
- package/src/use-transcription.ts +60 -18
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/mcp-apps.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source
|
|
4
|
+
media="(prefers-color-scheme: dark)"
|
|
5
|
+
srcset="https://tanstack.com/api/readme/ai.png?framework=react&theme=dark"
|
|
6
|
+
/>
|
|
7
|
+
<source
|
|
8
|
+
media="(prefers-color-scheme: light)"
|
|
9
|
+
srcset="https://tanstack.com/api/readme/ai.png?framework=react"
|
|
10
|
+
/>
|
|
11
|
+
<img
|
|
12
|
+
src="https://tanstack.com/api/readme/ai.png?framework=react"
|
|
13
|
+
alt="TanStack React AI"
|
|
14
|
+
width="900"
|
|
15
|
+
/>
|
|
16
|
+
</picture>
|
|
3
17
|
</div>
|
|
4
18
|
|
|
5
19
|
<br />
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -20,4 +20,4 @@ export { useGenerateVideo } from './use-generate-video.js';
|
|
|
20
20
|
export type { UseGenerateVideoOptions, UseGenerateVideoReturn, } from './use-generate-video.js';
|
|
21
21
|
export { useAudioRecorder } from './use-audio-recorder.js';
|
|
22
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';
|
|
23
|
+
export { fetchServerSentEvents, localStoragePersistence, sessionStoragePersistence, indexedDBPersistence, StorageUnavailableError, type ChatClientPersistence, type ChatPersistedState, type ChatPersistenceOption, type ChatStorageAdapter, type WebStoragePersistenceOptions, type IndexedDBPersistenceOptions, 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';
|
package/dist/esm/index.js
CHANGED
|
@@ -9,26 +9,5 @@ import { useTranscription } from "./use-transcription.js";
|
|
|
9
9
|
import { useSummarize } from "./use-summarize.js";
|
|
10
10
|
import { useGenerateVideo } from "./use-generate-video.js";
|
|
11
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
|
|
12
|
+
import { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, xhrHttpStream, xhrServerSentEvents } from "@tanstack/ai-client";
|
|
13
|
+
export { StorageUnavailableError, createChatClientOptions, createMcpAppBridge, fetchHttpStream, fetchServerSentEvents, indexedDBPersistence, localStoragePersistence, rpcStream, sessionStoragePersistence, stream, useAudioRecorder, useChat, useGenerateAudio, useGenerateImage, useGenerateSpeech, useGenerateVideo, useGeneration, useMcpAppBridge, useRealtimeChat, useSummarize, useTranscription, xhrHttpStream, xhrServerSentEvents };
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
1
|
import { AppRenderer } from "@mcp-ui/client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
//#region src/mcp-app-resource.tsx
|
|
3
4
|
function isRecord(value) {
|
|
4
|
-
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Coalesce an arbitrary bridge result into the `string` required by the
|
|
9
|
+
* `CallToolResult` text content block. `JSON.stringify` is typed to return
|
|
10
|
+
* `string` but actually returns `undefined` for inputs like `undefined` or a
|
|
11
|
+
* function, so we narrow explicitly to avoid `text: undefined`.
|
|
12
|
+
*/
|
|
6
13
|
function resultToText(result) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
if (typeof result === "string") return result;
|
|
15
|
+
const stringify = JSON.stringify;
|
|
16
|
+
return stringify(result) ?? "null";
|
|
10
17
|
}
|
|
11
18
|
function MCPAppResource(props) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
);
|
|
19
|
+
const { bridge } = props;
|
|
20
|
+
return /* @__PURE__ */ jsx(AppRenderer, {
|
|
21
|
+
toolName: props.part.toolName,
|
|
22
|
+
sandbox: props.sandbox,
|
|
23
|
+
html: props.part.resource.text,
|
|
24
|
+
toolResourceUri: props.part.resource.uri,
|
|
25
|
+
toolInput: props.toolInput,
|
|
26
|
+
onCallTool: bridge ? async ({ name, arguments: args }) => {
|
|
27
|
+
const result = await bridge.callTool({
|
|
28
|
+
serverId: props.part.serverId,
|
|
29
|
+
toolName: name,
|
|
30
|
+
args
|
|
31
|
+
});
|
|
32
|
+
const structuredContent = isRecord(result) ? result : void 0;
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: resultToText(result)
|
|
37
|
+
}],
|
|
38
|
+
structuredContent
|
|
39
|
+
};
|
|
40
|
+
} : void 0,
|
|
41
|
+
onMessage: bridge ? async ({ content }) => {
|
|
42
|
+
const text = content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
43
|
+
if (text) await bridge.sendPrompt(text);
|
|
44
|
+
return {};
|
|
45
|
+
} : void 0,
|
|
46
|
+
onOpenLink: bridge ? ({ url }) => Promise.resolve(bridge.openLink(url)) : void 0
|
|
47
|
+
});
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//# sourceMappingURL=mcp-app-resource.js.map
|
|
49
|
+
//#endregion
|
|
50
|
+
export { MCPAppResource };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=mcp-app-resource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-app-resource.js","sources":["../../src/mcp-app-resource.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"],"
|
|
1
|
+
{"version":3,"file":"mcp-app-resource.js","names":[],"sources":["../../src/mcp-app-resource.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"],"mappings":";;;AAoBA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;;;AAQA,SAAS,aAAa,QAAyB;CAC7C,IAAI,OAAO,WAAW,UAAU,OAAO;CAGvC,MAAM,YAAoD,KAAK;CAC/D,OAAO,UAAU,MAAM,KAAK;AAC9B;AAEA,SAAgB,eAAe,OAAyC;CACtE,MAAM,EAAE,WAAW;CACnB,OACE,oBAAC,aAAD;EACE,UAAU,MAAM,KAAK;EACrB,SAAS,MAAM;EACf,MAAM,MAAM,KAAK,SAAS;EAC1B,iBAAiB,MAAM,KAAK,SAAS;EACrC,WAAW,MAAM;EACjB,YACE,SACI,OAAO,EAAE,MAAM,WAAW,WAAW;GACnC,MAAM,SAAS,MAAM,OAAO,SAAS;IACnC,UAAU,MAAM,KAAK;IACrB,UAAU;IACV;GACF,CAAC;GACD,MAAM,oBAAoB,SAAS,MAAM,IAAI,SAAS,KAAA;GACtD,OAAO;IACL,SAAS,CACP;KACE,MAAM;KACN,MAAM,aAAa,MAAM;IAC3B,CACF;IACA;GACF;EACF,IACA,KAAA;EAEN,WACE,SACI,OAAO,EAAE,cAAc;GACrB,MAAM,OAAO,QACV,QACE,MAA2C,EAAE,SAAS,MACzD,CAAC,CACA,KAAK,MAAM,EAAE,IAAI,CAAC,CAClB,KAAK,EAAE;GACV,IAAI,MAAM,MAAM,OAAO,WAAW,IAAI;GACtC,OAAO,CAAC;EACV,IACA,KAAA;EAEN,YACE,UAAU,EAAE,UAAU,QAAQ,QAAQ,OAAO,SAAS,GAAG,CAAC,IAAI,KAAA;CAEjE,CAAA;AAEL"}
|
package/dist/esm/mcp-apps.js
CHANGED
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AnyClientTool, InferSchemaType, ModelMessage, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
-
import { AIDevtoolsDisplayOptions, ChatClientOptions, ChatClientState, ChatRequestBody, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
|
|
1
|
+
import { AnyClientTool, InferSchemaType, ModelMessage, RunAgentResumeItem, SchemaInput } from '@tanstack/ai/client';
|
|
2
|
+
import { AIDevtoolsDisplayOptions, BoundInterrupts, ChatClientOptions, ChatClientState, ChatInterrupt, ChatInterruptState, ChatRequestBody, ChatResumeState, ClientContextOptionFromTools, ConnectionStatus, DistributedOmit, InferredClientContext, MultimodalContent, QueueConfig, QueueOption, QueueStrategy, QueuedMessage, SendMessageOptions, UIMessage, WhenBusy } from '@tanstack/ai-client';
|
|
3
3
|
export type { ChatRequestBody, MultimodalContent, QueueConfig, QueuedMessage, QueueOption, QueueStrategy, SendMessageOptions, UIMessage, WhenBusy, };
|
|
4
4
|
/**
|
|
5
5
|
* Recursive partial — every property and every nested array element is optional.
|
|
@@ -35,7 +35,7 @@ export type DeepPartial<T> = T extends ReadonlyArray<infer U> ? Array<DeepPartia
|
|
|
35
35
|
* Note: Connection and body changes will recreate the ChatClient instance.
|
|
36
36
|
* To update these options, remount the component or use a key prop.
|
|
37
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' | 'onQueueChange' | 'context' | 'devtools'> & {
|
|
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' | 'onQueueChange' | 'onResumeStateChange' | 'onRunIdChange' | 'context' | 'devtools' | 'id'> & {
|
|
39
39
|
/** Display options for TanStack AI Devtools. */
|
|
40
40
|
devtools?: AIDevtoolsDisplayOptions;
|
|
41
41
|
/**
|
|
@@ -116,6 +116,32 @@ interface BaseUseChatReturn<TTools extends ReadonlyArray<AnyClientTool> = any, T
|
|
|
116
116
|
id: string;
|
|
117
117
|
approved: boolean;
|
|
118
118
|
}) => Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* The id of the run this client has in flight — one it started or rejoined —
|
|
121
|
+
* or `null` when there is none (including while a run sits paused on an
|
|
122
|
+
* interrupt, waiting on approval).
|
|
123
|
+
*
|
|
124
|
+
* A run is one turn of the conversation, so this changes from turn to turn. A
|
|
125
|
+
* whole tool loop stays inside one run, while resuming after an interrupt
|
|
126
|
+
* continues the turn under a new id — so one user message can produce several
|
|
127
|
+
* run ids. Use it to talk to your own server about that run (cancel it, poll
|
|
128
|
+
* it, correlate a log line).
|
|
129
|
+
*/
|
|
130
|
+
runId: string | null;
|
|
131
|
+
interrupts: BoundInterrupts<TTools>;
|
|
132
|
+
/** @deprecated Use `interrupts`. */
|
|
133
|
+
pendingInterrupts: BoundInterrupts<TTools>;
|
|
134
|
+
interruptErrors: ChatInterruptState<TTools>['interruptErrors'];
|
|
135
|
+
resuming: boolean;
|
|
136
|
+
resolveInterrupts: {
|
|
137
|
+
(approved: boolean): void;
|
|
138
|
+
(resolver: (interrupt: ChatInterrupt<TTools>) => undefined): void;
|
|
139
|
+
};
|
|
140
|
+
cancelInterrupts: () => void;
|
|
141
|
+
retryInterrupts: () => void;
|
|
142
|
+
resumeInterruptsUnsafe: (resume: Array<RunAgentResumeItem>, state?: ChatResumeState) => Promise<boolean>;
|
|
143
|
+
/** @deprecated Use bound interrupt methods or `resumeInterruptsUnsafe`. */
|
|
144
|
+
resumeInterrupts: (resume: Array<RunAgentResumeItem>, state?: ChatResumeState) => Promise<boolean>;
|
|
119
145
|
/**
|
|
120
146
|
* Reload the last assistant message
|
|
121
147
|
*/
|
|
@@ -1,49 +1,44 @@
|
|
|
1
|
-
import { useState, useRef, useMemo, useEffect, useCallback } from "react";
|
|
2
1
|
import { AudioRecorder } from "@tanstack/ai-client";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
//#region src/use-audio-recorder.ts
|
|
3
4
|
function useAudioRecorder(options = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
};
|
|
5
|
+
const [isRecording, setIsRecording] = useState(false);
|
|
6
|
+
const [recording, setRecording] = useState(null);
|
|
7
|
+
const optionsRef = useRef(options);
|
|
8
|
+
optionsRef.current = options;
|
|
9
|
+
const recorder = useMemo(() => 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
|
+
useEffect(() => {
|
|
15
|
+
const unsubscribe = recorder.subscribe((state) => {
|
|
16
|
+
setIsRecording(state === "recording");
|
|
17
|
+
});
|
|
18
|
+
return () => {
|
|
19
|
+
unsubscribe();
|
|
20
|
+
recorder.cancel();
|
|
21
|
+
};
|
|
22
|
+
}, [recorder]);
|
|
23
|
+
const start = useCallback(() => recorder.start(), [recorder]);
|
|
24
|
+
const stop = useCallback(async () => {
|
|
25
|
+
const recording = await recorder.stop();
|
|
26
|
+
const transformed = await optionsRef.current.onComplete?.(recording);
|
|
27
|
+
const output = transformed === void 0 ? recording : transformed;
|
|
28
|
+
setRecording(() => output);
|
|
29
|
+
return output;
|
|
30
|
+
}, [recorder]);
|
|
31
|
+
const cancel = useCallback(() => recorder.cancel(), [recorder]);
|
|
32
|
+
return {
|
|
33
|
+
recording,
|
|
34
|
+
isRecording,
|
|
35
|
+
isSupported: AudioRecorder.isSupported(),
|
|
36
|
+
start,
|
|
37
|
+
stop,
|
|
38
|
+
cancel
|
|
39
|
+
};
|
|
45
40
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//# sourceMappingURL=use-audio-recorder.js.map
|
|
41
|
+
//#endregion
|
|
42
|
+
export { useAudioRecorder };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=use-audio-recorder.js.map
|
|
@@ -1 +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"],"
|
|
1
|
+
{"version":3,"file":"use-audio-recorder.js","names":[],"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"],"mappings":";;;AA0DA,SAAgB,iBACd,UAA2E,CAAC,GAC3C;CACjC,MAAM,CAAC,aAAa,kBAAkB,SAAS,KAAK;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAkB,IAAI;CAExD,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,MAAM,WAAW,cAEb,IAAI,cAAc;EAChB,GAAI,QAAQ,UAAU,KAAA,KAAa,EAAE,OAAO,QAAQ,MAAM;EAC1D,GAAI,QAAQ,aAAa,KAAA,KAAa,EAAE,UAAU,QAAQ,SAAS;EACnE,UAAU,QAAQ,WAAW,QAAQ,UAAU,GAAG;CACpD,CAAC,GAGH,CAAC,CACH;CAEA,gBAAgB;EACd,MAAM,cAAc,SAAS,WAAW,UAAU;GAChD,eAAe,UAAU,WAAW;EACtC,CAAC;EACD,aAAa;GACX,YAAY;GACZ,SAAS,OAAO;EAClB;CACF,GAAG,CAAC,QAAQ,CAAC;CAEb,MAAM,QAAQ,kBAAkB,SAAS,MAAM,GAAG,CAAC,QAAQ,CAAC;CAC5D,MAAM,OAAO,YAAY,YAAY;EACnC,MAAM,YAAY,MAAM,SAAS,KAAK;EACtC,MAAM,cAAc,MAAM,WAAW,QAAQ,aAAa,SAAS;EAInE,MAAM,SAAS,gBAAgB,KAAA,IAAY,YAAY;EACvD,mBAAmB,MAAM;EACzB,OAAO;CACT,GAAG,CAAC,QAAQ,CAAC;CACb,MAAM,SAAS,kBAAkB,SAAS,OAAO,GAAG,CAAC,QAAQ,CAAC;CAE9D,OAAO;EACL;EACA;EAEA,aAAa,cAAc,YAAY;EACvC;EACA;EACA;CACF;AACF"}
|