@tanstack/ai-solid 0.0.3 → 0.1.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/README.md +17 -22
- package/dist/use-chat.js +2 -2
- package/dist/use-chat.js.map +1 -1
- package/package.json +5 -5
- package/src/use-chat.ts +4 -4
package/README.md
CHANGED
|
@@ -158,13 +158,13 @@ Your backend should use the `chat()` method which **automatically handles tool e
|
|
|
158
158
|
|
|
159
159
|
```typescript
|
|
160
160
|
import { chat, toStreamResponse } from '@tanstack/ai'
|
|
161
|
-
import {
|
|
161
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
162
162
|
|
|
163
163
|
export async function POST(request: Request) {
|
|
164
164
|
const { messages } = await request.json()
|
|
165
165
|
|
|
166
166
|
const stream = chat({
|
|
167
|
-
adapter:
|
|
167
|
+
adapter: openaiText(),
|
|
168
168
|
model: 'gpt-4o',
|
|
169
169
|
messages,
|
|
170
170
|
tools: [weatherTool], // Optional: auto-executed in loop
|
|
@@ -311,20 +311,19 @@ function App() {
|
|
|
311
311
|
|
|
312
312
|
```typescript
|
|
313
313
|
import express from 'express'
|
|
314
|
-
import {
|
|
315
|
-
import {
|
|
314
|
+
import { chat, toStreamResponse } from '@tanstack/ai'
|
|
315
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
316
316
|
|
|
317
317
|
const app = express()
|
|
318
318
|
app.use(express.json())
|
|
319
319
|
|
|
320
|
-
const ai = new AI(new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY }))
|
|
321
|
-
|
|
322
320
|
app.post('/api/chat', async (req, res) => {
|
|
323
321
|
const { messages } = req.body
|
|
324
322
|
|
|
325
323
|
// One line to create streaming response!
|
|
326
|
-
const stream =
|
|
327
|
-
|
|
324
|
+
const stream = chat({
|
|
325
|
+
adapter: openaiText(),
|
|
326
|
+
model: 'gpt-4o',
|
|
328
327
|
messages,
|
|
329
328
|
})
|
|
330
329
|
|
|
@@ -353,20 +352,19 @@ app.listen(3000)
|
|
|
353
352
|
|
|
354
353
|
```typescript
|
|
355
354
|
// app/api/chat/route.ts
|
|
356
|
-
import {
|
|
357
|
-
import {
|
|
355
|
+
import { chat, toStreamResponse } from '@tanstack/ai'
|
|
356
|
+
import { openaiText } from '@tanstack/ai-openai'
|
|
358
357
|
|
|
359
358
|
export const runtime = 'edge'
|
|
360
359
|
|
|
361
|
-
const ai = new AI(new OpenAIAdapter({ apiKey: process.env.OPENAI_API_KEY }))
|
|
362
|
-
|
|
363
360
|
export async function POST(req: Request) {
|
|
364
361
|
const { messages } = await req.json()
|
|
365
362
|
|
|
366
363
|
// One line!
|
|
367
364
|
return toStreamResponse(
|
|
368
|
-
|
|
369
|
-
|
|
365
|
+
chat({
|
|
366
|
+
adapter: openaiText(),
|
|
367
|
+
model: 'gpt-4o',
|
|
370
368
|
messages,
|
|
371
369
|
}),
|
|
372
370
|
)
|
|
@@ -377,12 +375,8 @@ export async function POST(req: Request) {
|
|
|
377
375
|
|
|
378
376
|
```typescript
|
|
379
377
|
import { createFileRoute } from '@tanstack/react-router'
|
|
380
|
-
import {
|
|
381
|
-
import {
|
|
382
|
-
|
|
383
|
-
const ai = new AI(
|
|
384
|
-
new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
385
|
-
)
|
|
378
|
+
import { chat, toStreamResponse } from '@tanstack/ai'
|
|
379
|
+
import { anthropicText } from '@tanstack/ai-anthropic'
|
|
386
380
|
|
|
387
381
|
export const Route = createFileRoute('/api/chat')({
|
|
388
382
|
server: {
|
|
@@ -392,8 +386,9 @@ export const Route = createFileRoute('/api/chat')({
|
|
|
392
386
|
|
|
393
387
|
// One line with automatic tool execution!
|
|
394
388
|
return toStreamResponse(
|
|
395
|
-
|
|
396
|
-
|
|
389
|
+
chat({
|
|
390
|
+
adapter: anthropicText(),
|
|
391
|
+
model: 'claude-sonnet-4-20250514',
|
|
397
392
|
messages,
|
|
398
393
|
tools, // Tools with execute functions
|
|
399
394
|
}),
|
package/dist/use-chat.js
CHANGED
|
@@ -38,9 +38,9 @@ function useChat(options = {}) {
|
|
|
38
38
|
});
|
|
39
39
|
createEffect(() => {
|
|
40
40
|
return () => {
|
|
41
|
-
|
|
41
|
+
client().stop();
|
|
42
42
|
};
|
|
43
|
-
}
|
|
43
|
+
});
|
|
44
44
|
const sendMessage = async (content) => {
|
|
45
45
|
await client().sendMessage(content);
|
|
46
46
|
};
|
package/dist/use-chat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport { ChatClient } from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\nimport type { UIMessage, UseChatOptions, UseChatReturn } from './types'\n\nexport function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(\n options: UseChatOptions<TTools> = {} as UseChatOptions<TTools>,\n): UseChatReturn<TTools> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n\n // Create ChatClient instance with callbacks to sync state\n // Note: Options are captured at client creation time.\n // The connection adapter can use functions for dynamic values (url, headers, etc.)\n // which are evaluated lazily on each request.\n const client = createMemo(() => {\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n initialMessages: options.initialMessages,\n body: options.body,\n onResponse: options.onResponse,\n onChunk: options.onChunk,\n onFinish: options.onFinish,\n onError: options.onError,\n tools: options.tools,\n streamProcessor: options.streamProcessor,\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Cleanup on unmount: stop any in-flight requests\n createEffect(() => {\n return () => {\n // Stop any active generation when component unmounts\n
|
|
1
|
+
{"version":3,"file":"use-chat.js","names":[],"sources":["../src/use-chat.ts"],"sourcesContent":["import {\n createEffect,\n createMemo,\n createSignal,\n createUniqueId,\n} from 'solid-js'\nimport { ChatClient } from '@tanstack/ai-client'\nimport type { AnyClientTool, ModelMessage } from '@tanstack/ai'\nimport type { UIMessage, UseChatOptions, UseChatReturn } from './types'\n\nexport function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(\n options: UseChatOptions<TTools> = {} as UseChatOptions<TTools>,\n): UseChatReturn<TTools> {\n const hookId = createUniqueId()\n const clientId = options.id || hookId\n\n const [messages, setMessages] = createSignal<Array<UIMessage<TTools>>>(\n options.initialMessages || [],\n )\n const [isLoading, setIsLoading] = createSignal(false)\n const [error, setError] = createSignal<Error | undefined>(undefined)\n\n // Create ChatClient instance with callbacks to sync state\n // Note: Options are captured at client creation time.\n // The connection adapter can use functions for dynamic values (url, headers, etc.)\n // which are evaluated lazily on each request.\n const client = createMemo(() => {\n return new ChatClient({\n connection: options.connection,\n id: clientId,\n initialMessages: options.initialMessages,\n body: options.body,\n onResponse: options.onResponse,\n onChunk: options.onChunk,\n onFinish: options.onFinish,\n onError: options.onError,\n tools: options.tools,\n streamProcessor: options.streamProcessor,\n onMessagesChange: (newMessages: Array<UIMessage<TTools>>) => {\n setMessages(newMessages)\n },\n onLoadingChange: (newIsLoading: boolean) => {\n setIsLoading(newIsLoading)\n },\n onErrorChange: (newError: Error | undefined) => {\n setError(newError)\n },\n })\n // Only recreate when clientId changes\n // Connection and other options are captured at creation time\n }, [clientId])\n\n // Sync initial messages on mount only\n // Note: initialMessages are passed to ChatClient constructor, but we also\n // set them here to ensure React state is in sync\n createEffect(() => {\n if (options.initialMessages && options.initialMessages.length > 0) {\n // Only set if current messages are empty (initial state)\n if (messages().length === 0) {\n client().setMessagesManually(options.initialMessages)\n }\n }\n }) // Only run on mount - initialMessages are handled by ChatClient constructor\n\n // Cleanup on unmount: stop any in-flight requests\n // Note: We use createEffect with a cleanup return to handle component unmount.\n // The cleanup only runs on disposal (unmount), not on signal changes.\n createEffect(() => {\n return () => {\n // Stop any active generation when component unmounts\n client().stop()\n }\n })\n\n // Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)\n // are captured at client creation time. Changes to these callbacks require\n // remounting the component or changing the connection to recreate the client.\n\n const sendMessage = async (content: string) => {\n await client().sendMessage(content)\n }\n\n const append = async (message: ModelMessage | UIMessage<TTools>) => {\n await client().append(message)\n }\n\n const reload = async () => {\n await client().reload()\n }\n\n const stop = () => {\n client().stop()\n }\n\n const clear = () => {\n client().clear()\n }\n\n const setMessagesManually = (newMessages: Array<UIMessage<TTools>>) => {\n client().setMessagesManually(newMessages)\n }\n\n const addToolResult = async (result: {\n toolCallId: string\n tool: string\n output: any\n state?: 'output-available' | 'output-error'\n errorText?: string\n }) => {\n await client().addToolResult(result)\n }\n\n const addToolApprovalResponse = async (response: {\n id: string\n approved: boolean\n }) => {\n await client().addToolApprovalResponse(response)\n }\n\n return {\n messages,\n sendMessage,\n append,\n reload,\n stop,\n isLoading,\n error,\n setMessages: setMessagesManually,\n clear,\n addToolResult,\n addToolApprovalResponse,\n }\n}\n"],"mappings":";;;;AAUA,SAAgB,QACd,UAAkC,EAAE,EACb;CACvB,MAAM,SAAS,gBAAgB;CAC/B,MAAM,WAAW,QAAQ,MAAM;CAE/B,MAAM,CAAC,UAAU,eAAe,aAC9B,QAAQ,mBAAmB,EAAE,CAC9B;CACD,MAAM,CAAC,WAAW,gBAAgB,aAAa,MAAM;CACrD,MAAM,CAAC,OAAO,YAAY,aAAgC,OAAU;CAMpE,MAAM,SAAS,iBAAiB;AAC9B,SAAO,IAAI,WAAW;GACpB,YAAY,QAAQ;GACpB,IAAI;GACJ,iBAAiB,QAAQ;GACzB,MAAM,QAAQ;GACd,YAAY,QAAQ;GACpB,SAAS,QAAQ;GACjB,UAAU,QAAQ;GAClB,SAAS,QAAQ;GACjB,OAAO,QAAQ;GACf,iBAAiB,QAAQ;GACzB,mBAAmB,gBAA0C;AAC3D,gBAAY,YAAY;;GAE1B,kBAAkB,iBAA0B;AAC1C,iBAAa,aAAa;;GAE5B,gBAAgB,aAAgC;AAC9C,aAAS,SAAS;;GAErB,CAAC;IAGD,CAAC,SAAS,CAAC;AAKd,oBAAmB;AACjB,MAAI,QAAQ,mBAAmB,QAAQ,gBAAgB,SAAS,GAE9D;OAAI,UAAU,CAAC,WAAW,EACxB,SAAQ,CAAC,oBAAoB,QAAQ,gBAAgB;;GAGzD;AAKF,oBAAmB;AACjB,eAAa;AAEX,WAAQ,CAAC,MAAM;;GAEjB;CAMF,MAAM,cAAc,OAAO,YAAoB;AAC7C,QAAM,QAAQ,CAAC,YAAY,QAAQ;;CAGrC,MAAM,SAAS,OAAO,YAA8C;AAClE,QAAM,QAAQ,CAAC,OAAO,QAAQ;;CAGhC,MAAM,SAAS,YAAY;AACzB,QAAM,QAAQ,CAAC,QAAQ;;CAGzB,MAAM,aAAa;AACjB,UAAQ,CAAC,MAAM;;CAGjB,MAAM,cAAc;AAClB,UAAQ,CAAC,OAAO;;CAGlB,MAAM,uBAAuB,gBAA0C;AACrE,UAAQ,CAAC,oBAAoB,YAAY;;CAG3C,MAAM,gBAAgB,OAAO,WAMvB;AACJ,QAAM,QAAQ,CAAC,cAAc,OAAO;;CAGtC,MAAM,0BAA0B,OAAO,aAGjC;AACJ,QAAM,QAAQ,CAAC,wBAAwB,SAAS;;AAGlD,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA;EACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-solid",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Solid hooks for TanStack AI",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"zod": "^4.1.13",
|
|
35
|
-
"@tanstack/ai": "0.0
|
|
36
|
-
"@tanstack/ai-client": "0.0
|
|
35
|
+
"@tanstack/ai": "0.1.0",
|
|
36
|
+
"@tanstack/ai-client": "0.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@solidjs/testing-library": "^0.8.10",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"solid-js": ">=1.9.10",
|
|
50
|
-
"@tanstack/ai": "0.0
|
|
51
|
-
"@tanstack/ai-client": "0.0
|
|
50
|
+
"@tanstack/ai": "0.1.0",
|
|
51
|
+
"@tanstack/ai-client": "0.1.0"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"clean": "premove ./build ./dist",
|
package/src/use-chat.ts
CHANGED
|
@@ -63,14 +63,14 @@ export function useChat<TTools extends ReadonlyArray<AnyClientTool> = any>(
|
|
|
63
63
|
}) // Only run on mount - initialMessages are handled by ChatClient constructor
|
|
64
64
|
|
|
65
65
|
// Cleanup on unmount: stop any in-flight requests
|
|
66
|
+
// Note: We use createEffect with a cleanup return to handle component unmount.
|
|
67
|
+
// The cleanup only runs on disposal (unmount), not on signal changes.
|
|
66
68
|
createEffect(() => {
|
|
67
69
|
return () => {
|
|
68
70
|
// Stop any active generation when component unmounts
|
|
69
|
-
|
|
70
|
-
client().stop()
|
|
71
|
-
}
|
|
71
|
+
client().stop()
|
|
72
72
|
}
|
|
73
|
-
}
|
|
73
|
+
})
|
|
74
74
|
|
|
75
75
|
// Note: Callback options (onResponse, onChunk, onFinish, onError, onToolCall)
|
|
76
76
|
// are captured at client creation time. Changes to these callbacks require
|