@ai-sdk/vue 0.0.44 → 0.0.45
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/CHANGELOG.md +7 -0
- package/package.json +6 -2
- package/.eslintrc.js +0 -4
- package/.turbo/turbo-build.log +0 -21
- package/.turbo/turbo-clean.log +0 -4
- package/src/TestChatAssistantStreamComponent.vue +0 -18
- package/src/TestChatAssistantThreadChangeComponent.vue +0 -22
- package/src/TestChatComponent.vue +0 -44
- package/src/TestChatCustomMetadataComponent.vue +0 -32
- package/src/TestChatFormComponent.vue +0 -22
- package/src/TestChatFormOptionsComponent.vue +0 -29
- package/src/TestChatReloadComponent.vue +0 -34
- package/src/TestChatTextStreamComponent.vue +0 -42
- package/src/TestCompletionComponent.vue +0 -17
- package/src/TestCompletionTextStreamComponent.vue +0 -19
- package/src/index.ts +0 -3
- package/src/package.json +0 -10
- package/src/shims-vue.d.ts +0 -5
- package/src/use-assistant.ts +0 -305
- package/src/use-assistant.ui.test.tsx +0 -272
- package/src/use-chat.ts +0 -321
- package/src/use-chat.ui.test.tsx +0 -446
- package/src/use-completion.ts +0 -177
- package/src/use-completion.ui.test.ts +0 -93
- package/tsconfig.json +0 -9
- package/tsup.config.ts +0 -13
- package/turbo.json +0 -8
- package/vitest.config.js +0 -12
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { formatStreamPart } from '@ai-sdk/ui-utils';
|
|
2
|
-
import {
|
|
3
|
-
mockFetchDataStream,
|
|
4
|
-
mockFetchDataStreamWithGenerator,
|
|
5
|
-
} from '@ai-sdk/ui-utils/test';
|
|
6
|
-
import '@testing-library/jest-dom/vitest';
|
|
7
|
-
import { cleanup, findByText, render, screen } from '@testing-library/vue';
|
|
8
|
-
import userEvent from '@testing-library/user-event';
|
|
9
|
-
import TestChatAssistantStreamComponent from './TestChatAssistantStreamComponent.vue';
|
|
10
|
-
import TestChatAssistantThreadChangeComponent from './TestChatAssistantThreadChangeComponent.vue';
|
|
11
|
-
|
|
12
|
-
describe('stream data stream', () => {
|
|
13
|
-
// Render the TestChatAssistantStreamComponent before each test
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
render(TestChatAssistantStreamComponent);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Cleanup after each test
|
|
19
|
-
afterEach(() => {
|
|
20
|
-
vi.restoreAllMocks();
|
|
21
|
-
cleanup();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should show streamed response', async () => {
|
|
25
|
-
// Mock the fetch data stream
|
|
26
|
-
const { requestBody } = mockFetchDataStream({
|
|
27
|
-
url: 'https://example.com/api/assistant',
|
|
28
|
-
chunks: [
|
|
29
|
-
// Format the stream part
|
|
30
|
-
formatStreamPart('assistant_control_data', {
|
|
31
|
-
threadId: 't0',
|
|
32
|
-
messageId: 'm0',
|
|
33
|
-
}),
|
|
34
|
-
formatStreamPart('assistant_message', {
|
|
35
|
-
id: 'm0',
|
|
36
|
-
role: 'assistant',
|
|
37
|
-
content: [{ type: 'text', text: { value: '' } }],
|
|
38
|
-
}),
|
|
39
|
-
// Text parts
|
|
40
|
-
'0:"Hello"\n',
|
|
41
|
-
'0:", world"\n',
|
|
42
|
-
'0:"."\n',
|
|
43
|
-
],
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Click the button
|
|
47
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
48
|
-
|
|
49
|
-
// Find the message-0 element
|
|
50
|
-
await screen.findByTestId('message-0');
|
|
51
|
-
// Expect the message-0 element to have the text content 'User: hi'
|
|
52
|
-
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
53
|
-
|
|
54
|
-
// Find the message-1 element
|
|
55
|
-
await screen.findByTestId('message-1');
|
|
56
|
-
// Expect the message-1 element to have the text content 'AI: Hello, world.'
|
|
57
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
58
|
-
'AI: Hello, world.',
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
expect(await requestBody).toStrictEqual(
|
|
62
|
-
JSON.stringify({
|
|
63
|
-
message: 'hi',
|
|
64
|
-
threadId: null,
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('loading state', () => {
|
|
70
|
-
it('should show loading state', async () => {
|
|
71
|
-
let finishGeneration: ((value?: unknown) => void) | undefined;
|
|
72
|
-
|
|
73
|
-
const finishGenerationPromise = new Promise(resolve => {
|
|
74
|
-
finishGeneration = resolve;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Mock the fetch data stream with generator
|
|
78
|
-
mockFetchDataStreamWithGenerator({
|
|
79
|
-
url: 'https://example.com/api/assistant',
|
|
80
|
-
chunkGenerator: (async function* generate() {
|
|
81
|
-
const encoder = new TextEncoder();
|
|
82
|
-
|
|
83
|
-
yield encoder.encode(
|
|
84
|
-
formatStreamPart('assistant_control_data', {
|
|
85
|
-
threadId: 't0',
|
|
86
|
-
messageId: 'm1',
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
yield encoder.encode(
|
|
91
|
-
formatStreamPart('assistant_message', {
|
|
92
|
-
id: 'm1',
|
|
93
|
-
role: 'assistant',
|
|
94
|
-
content: [{ type: 'text', text: { value: '' } }],
|
|
95
|
-
}),
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
yield encoder.encode('0:"Hello"\n');
|
|
99
|
-
|
|
100
|
-
await finishGenerationPromise;
|
|
101
|
-
})(),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Click the button
|
|
105
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
106
|
-
|
|
107
|
-
// Find the loading element and expect it to be in progress
|
|
108
|
-
await screen.findByTestId('status');
|
|
109
|
-
expect(screen.getByTestId('status')).toHaveTextContent('in_progress');
|
|
110
|
-
|
|
111
|
-
// Resolve the finishGenerationPromise
|
|
112
|
-
finishGeneration?.();
|
|
113
|
-
|
|
114
|
-
// Find the loading element and expect it to be awaiting a message
|
|
115
|
-
await findByText(await screen.findByTestId('status'), 'awaiting_message');
|
|
116
|
-
expect(screen.getByTestId('status')).toHaveTextContent(
|
|
117
|
-
'awaiting_message',
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('Thread management', () => {
|
|
124
|
-
beforeEach(() => {
|
|
125
|
-
render(TestChatAssistantThreadChangeComponent);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
afterEach(() => {
|
|
129
|
-
vi.restoreAllMocks();
|
|
130
|
-
cleanup();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('create new thread', async () => {
|
|
134
|
-
await screen.findByTestId('thread-id');
|
|
135
|
-
expect(screen.getByTestId('thread-id')).toHaveTextContent('undefined');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it('should show streamed response', async () => {
|
|
139
|
-
const { requestBody } = mockFetchDataStream({
|
|
140
|
-
url: 'https://example.com/api/assistant',
|
|
141
|
-
chunks: [
|
|
142
|
-
formatStreamPart('assistant_control_data', {
|
|
143
|
-
threadId: 't0',
|
|
144
|
-
messageId: 'm0',
|
|
145
|
-
}),
|
|
146
|
-
formatStreamPart('assistant_message', {
|
|
147
|
-
id: 'm0',
|
|
148
|
-
role: 'assistant',
|
|
149
|
-
content: [{ type: 'text', text: { value: '' } }],
|
|
150
|
-
}),
|
|
151
|
-
// text parts:
|
|
152
|
-
'0:"Hello"\n',
|
|
153
|
-
'0:","\n',
|
|
154
|
-
'0:" world"\n',
|
|
155
|
-
'0:"."\n',
|
|
156
|
-
],
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
160
|
-
|
|
161
|
-
await screen.findByTestId('message-0');
|
|
162
|
-
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
163
|
-
|
|
164
|
-
expect(screen.getByTestId('thread-id')).toHaveTextContent('t0');
|
|
165
|
-
|
|
166
|
-
await screen.findByTestId('message-1');
|
|
167
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
168
|
-
'AI: Hello, world.',
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
expect(await requestBody).toStrictEqual(
|
|
172
|
-
JSON.stringify({
|
|
173
|
-
message: 'hi',
|
|
174
|
-
threadId: null,
|
|
175
|
-
}),
|
|
176
|
-
);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should switch to new thread on setting undefined threadId', async () => {
|
|
180
|
-
await userEvent.click(screen.getByTestId('do-new-thread'));
|
|
181
|
-
|
|
182
|
-
expect(screen.queryByTestId('message-0')).toBeNull();
|
|
183
|
-
expect(screen.queryByTestId('message-1')).toBeNull();
|
|
184
|
-
|
|
185
|
-
const { requestBody } = mockFetchDataStream({
|
|
186
|
-
url: 'https://example.com/api/assistant',
|
|
187
|
-
chunks: [
|
|
188
|
-
formatStreamPart('assistant_control_data', {
|
|
189
|
-
threadId: 't1',
|
|
190
|
-
messageId: 'm0',
|
|
191
|
-
}),
|
|
192
|
-
formatStreamPart('assistant_message', {
|
|
193
|
-
id: 'm0',
|
|
194
|
-
role: 'assistant',
|
|
195
|
-
content: [{ type: 'text', text: { value: '' } }],
|
|
196
|
-
}),
|
|
197
|
-
// text parts:
|
|
198
|
-
'0:"Hello"\n',
|
|
199
|
-
'0:","\n',
|
|
200
|
-
'0:" world"\n',
|
|
201
|
-
'0:"."\n',
|
|
202
|
-
],
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
206
|
-
|
|
207
|
-
await screen.findByTestId('message-0');
|
|
208
|
-
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
209
|
-
|
|
210
|
-
expect(screen.getByTestId('thread-id')).toHaveTextContent('t1');
|
|
211
|
-
|
|
212
|
-
await screen.findByTestId('message-1');
|
|
213
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
214
|
-
'AI: Hello, world.',
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// check that correct information was sent to the server:
|
|
218
|
-
expect(await requestBody).toStrictEqual(
|
|
219
|
-
JSON.stringify({
|
|
220
|
-
message: 'hi',
|
|
221
|
-
threadId: null,
|
|
222
|
-
}),
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should switch to thread on setting previously created threadId', async () => {
|
|
227
|
-
await userEvent.click(screen.getByTestId('do-thread-3'));
|
|
228
|
-
|
|
229
|
-
expect(screen.queryByTestId('message-0')).toBeNull();
|
|
230
|
-
expect(screen.queryByTestId('message-1')).toBeNull();
|
|
231
|
-
|
|
232
|
-
const { requestBody } = mockFetchDataStream({
|
|
233
|
-
url: 'https://example.com/api/assistant',
|
|
234
|
-
chunks: [
|
|
235
|
-
formatStreamPart('assistant_control_data', {
|
|
236
|
-
threadId: 't3',
|
|
237
|
-
messageId: 'm0',
|
|
238
|
-
}),
|
|
239
|
-
formatStreamPart('assistant_message', {
|
|
240
|
-
id: 'm0',
|
|
241
|
-
role: 'assistant',
|
|
242
|
-
content: [{ type: 'text', text: { value: '' } }],
|
|
243
|
-
}),
|
|
244
|
-
// text parts:
|
|
245
|
-
'0:"Hello"\n',
|
|
246
|
-
'0:","\n',
|
|
247
|
-
'0:" world"\n',
|
|
248
|
-
'0:"."\n',
|
|
249
|
-
],
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
253
|
-
|
|
254
|
-
await screen.findByTestId('message-0');
|
|
255
|
-
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
256
|
-
|
|
257
|
-
expect(screen.getByTestId('thread-id')).toHaveTextContent('t3');
|
|
258
|
-
|
|
259
|
-
await screen.findByTestId('message-1');
|
|
260
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
261
|
-
'AI: Hello, world.',
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
// check that correct information was sent to the server:
|
|
265
|
-
expect(await requestBody).toStrictEqual(
|
|
266
|
-
JSON.stringify({
|
|
267
|
-
message: 'hi',
|
|
268
|
-
threadId: 't3',
|
|
269
|
-
}),
|
|
270
|
-
);
|
|
271
|
-
});
|
|
272
|
-
});
|
package/src/use-chat.ts
DELETED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChatRequest,
|
|
3
|
-
ChatRequestOptions,
|
|
4
|
-
CreateMessage,
|
|
5
|
-
JSONValue,
|
|
6
|
-
Message,
|
|
7
|
-
UseChatOptions,
|
|
8
|
-
} from '@ai-sdk/ui-utils';
|
|
9
|
-
import {
|
|
10
|
-
callChatApi,
|
|
11
|
-
generateId as generateIdFunc,
|
|
12
|
-
processChatStream,
|
|
13
|
-
} from '@ai-sdk/ui-utils';
|
|
14
|
-
import swrv from 'swrv';
|
|
15
|
-
import type { Ref } from 'vue';
|
|
16
|
-
import { ref, unref } from 'vue';
|
|
17
|
-
|
|
18
|
-
export type { CreateMessage, Message, UseChatOptions };
|
|
19
|
-
|
|
20
|
-
export type UseChatHelpers = {
|
|
21
|
-
/** Current messages in the chat */
|
|
22
|
-
messages: Ref<Message[]>;
|
|
23
|
-
/** The error object of the API request */
|
|
24
|
-
error: Ref<undefined | Error>;
|
|
25
|
-
/**
|
|
26
|
-
* Append a user message to the chat list. This triggers the API call to fetch
|
|
27
|
-
* the assistant's response.
|
|
28
|
-
*/
|
|
29
|
-
append: (
|
|
30
|
-
message: Message | CreateMessage,
|
|
31
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
32
|
-
) => Promise<string | null | undefined>;
|
|
33
|
-
/**
|
|
34
|
-
* Reload the last AI chat response for the given chat history. If the last
|
|
35
|
-
* message isn't from the assistant, it will request the API to generate a
|
|
36
|
-
* new response.
|
|
37
|
-
*/
|
|
38
|
-
reload: (
|
|
39
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
40
|
-
) => Promise<string | null | undefined>;
|
|
41
|
-
/**
|
|
42
|
-
* Abort the current request immediately, keep the generated tokens if any.
|
|
43
|
-
*/
|
|
44
|
-
stop: () => void;
|
|
45
|
-
/**
|
|
46
|
-
* Update the `messages` state locally. This is useful when you want to
|
|
47
|
-
* edit the messages on the client, and then trigger the `reload` method
|
|
48
|
-
* manually to regenerate the AI response.
|
|
49
|
-
*/
|
|
50
|
-
setMessages: (
|
|
51
|
-
messages: Message[] | ((messages: Message[]) => Message[]),
|
|
52
|
-
) => void;
|
|
53
|
-
/** The current value of the input */
|
|
54
|
-
input: Ref<string>;
|
|
55
|
-
/** Form submission handler to automatically reset input and append a user message */
|
|
56
|
-
handleSubmit: (
|
|
57
|
-
event?: { preventDefault?: () => void },
|
|
58
|
-
chatRequestOptions?: ChatRequestOptions,
|
|
59
|
-
) => void;
|
|
60
|
-
/** Whether the API request is in progress */
|
|
61
|
-
isLoading: Ref<boolean | undefined>;
|
|
62
|
-
|
|
63
|
-
/** Additional data added on the server via StreamData */
|
|
64
|
-
data: Ref<JSONValue[] | undefined>;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
let uniqueId = 0;
|
|
68
|
-
|
|
69
|
-
// @ts-expect-error - some issues with the default export of useSWRV
|
|
70
|
-
const useSWRV = (swrv.default as typeof import('swrv')['default']) || swrv;
|
|
71
|
-
const store: Record<string, Message[] | undefined> = {};
|
|
72
|
-
|
|
73
|
-
export function useChat({
|
|
74
|
-
api = '/api/chat',
|
|
75
|
-
id,
|
|
76
|
-
initialMessages = [],
|
|
77
|
-
initialInput = '',
|
|
78
|
-
sendExtraMessageFields,
|
|
79
|
-
experimental_onFunctionCall,
|
|
80
|
-
streamMode,
|
|
81
|
-
streamProtocol,
|
|
82
|
-
onResponse,
|
|
83
|
-
onFinish,
|
|
84
|
-
onError,
|
|
85
|
-
credentials,
|
|
86
|
-
headers: metadataHeaders,
|
|
87
|
-
body: metadataBody,
|
|
88
|
-
generateId = generateIdFunc,
|
|
89
|
-
fetch,
|
|
90
|
-
keepLastMessageOnError = false,
|
|
91
|
-
}: UseChatOptions = {}): UseChatHelpers {
|
|
92
|
-
// streamMode is deprecated, use streamProtocol instead.
|
|
93
|
-
if (streamMode) {
|
|
94
|
-
streamProtocol ??= streamMode === 'text' ? 'text' : undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Generate a unique ID for the chat if not provided.
|
|
98
|
-
const chatId = id || `chat-${uniqueId++}`;
|
|
99
|
-
|
|
100
|
-
const key = `${api}|${chatId}`;
|
|
101
|
-
const { data: messagesData, mutate: originalMutate } = useSWRV<Message[]>(
|
|
102
|
-
key,
|
|
103
|
-
() => store[key] || initialMessages,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const { data: isLoading, mutate: mutateLoading } = useSWRV<boolean>(
|
|
107
|
-
`${chatId}-loading`,
|
|
108
|
-
null,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
isLoading.value ??= false;
|
|
112
|
-
|
|
113
|
-
// Force the `data` to be `initialMessages` if it's `undefined`.
|
|
114
|
-
messagesData.value ??= initialMessages;
|
|
115
|
-
|
|
116
|
-
const mutate = (data?: Message[]) => {
|
|
117
|
-
store[key] = data;
|
|
118
|
-
return originalMutate();
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Because of the `initialData` option, the `data` will never be `undefined`.
|
|
122
|
-
const messages = messagesData as Ref<Message[]>;
|
|
123
|
-
|
|
124
|
-
const error = ref<undefined | Error>(undefined);
|
|
125
|
-
// cannot use JSONValue[] in ref because of infinite Typescript recursion:
|
|
126
|
-
const streamData = ref<undefined | unknown[]>(undefined);
|
|
127
|
-
|
|
128
|
-
let abortController: AbortController | null = null;
|
|
129
|
-
|
|
130
|
-
async function triggerRequest(
|
|
131
|
-
messagesSnapshot: Message[],
|
|
132
|
-
{ options, data, headers, body }: ChatRequestOptions = {},
|
|
133
|
-
) {
|
|
134
|
-
try {
|
|
135
|
-
error.value = undefined;
|
|
136
|
-
mutateLoading(() => true);
|
|
137
|
-
|
|
138
|
-
abortController = new AbortController();
|
|
139
|
-
|
|
140
|
-
// Do an optimistic update to the chat state to show the updated messages
|
|
141
|
-
// immediately.
|
|
142
|
-
const previousMessages = messagesSnapshot;
|
|
143
|
-
mutate(messagesSnapshot);
|
|
144
|
-
|
|
145
|
-
const requestOptions = {
|
|
146
|
-
headers: headers ?? options?.headers,
|
|
147
|
-
body: body ?? options?.body,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
let chatRequest: ChatRequest = {
|
|
151
|
-
messages: messagesSnapshot,
|
|
152
|
-
options: requestOptions,
|
|
153
|
-
body: requestOptions.body,
|
|
154
|
-
headers: requestOptions.headers,
|
|
155
|
-
data,
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
await processChatStream({
|
|
159
|
-
getStreamedResponse: async () => {
|
|
160
|
-
const existingData = (streamData.value ?? []) as JSONValue[];
|
|
161
|
-
|
|
162
|
-
const constructedMessagesPayload = sendExtraMessageFields
|
|
163
|
-
? chatRequest.messages
|
|
164
|
-
: chatRequest.messages.map(
|
|
165
|
-
({
|
|
166
|
-
role,
|
|
167
|
-
content,
|
|
168
|
-
name,
|
|
169
|
-
data,
|
|
170
|
-
annotations,
|
|
171
|
-
function_call,
|
|
172
|
-
}) => ({
|
|
173
|
-
role,
|
|
174
|
-
content,
|
|
175
|
-
...(name !== undefined && { name }),
|
|
176
|
-
...(data !== undefined && { data }),
|
|
177
|
-
...(annotations !== undefined && { annotations }),
|
|
178
|
-
// outdated function/tool call handling (TODO deprecate):
|
|
179
|
-
...(function_call !== undefined && { function_call }),
|
|
180
|
-
}),
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
return await callChatApi({
|
|
184
|
-
api,
|
|
185
|
-
body: {
|
|
186
|
-
messages: constructedMessagesPayload,
|
|
187
|
-
data: chatRequest.data,
|
|
188
|
-
...unref(metadataBody), // Use unref to unwrap the ref value
|
|
189
|
-
...requestOptions.body,
|
|
190
|
-
},
|
|
191
|
-
streamProtocol,
|
|
192
|
-
headers: {
|
|
193
|
-
...metadataHeaders,
|
|
194
|
-
...requestOptions.headers,
|
|
195
|
-
},
|
|
196
|
-
abortController: () => abortController,
|
|
197
|
-
credentials,
|
|
198
|
-
onResponse,
|
|
199
|
-
onUpdate(merged, data) {
|
|
200
|
-
mutate([...chatRequest.messages, ...merged]);
|
|
201
|
-
streamData.value = [...existingData, ...(data ?? [])];
|
|
202
|
-
},
|
|
203
|
-
onFinish(message, options) {
|
|
204
|
-
// workaround: sometimes the last chunk is not shown in the UI.
|
|
205
|
-
// push it twice to make sure it's displayed.
|
|
206
|
-
mutate([...chatRequest.messages, message]);
|
|
207
|
-
onFinish?.(message, options);
|
|
208
|
-
},
|
|
209
|
-
restoreMessagesOnFailure() {
|
|
210
|
-
// Restore the previous messages if the request fails.
|
|
211
|
-
if (!keepLastMessageOnError) {
|
|
212
|
-
mutate(previousMessages);
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
generateId,
|
|
216
|
-
onToolCall: undefined, // not implemented yet
|
|
217
|
-
fetch,
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
experimental_onFunctionCall,
|
|
221
|
-
updateChatRequest(newChatRequest) {
|
|
222
|
-
chatRequest = newChatRequest;
|
|
223
|
-
},
|
|
224
|
-
getCurrentMessages: () => messages.value,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
abortController = null;
|
|
228
|
-
} catch (err) {
|
|
229
|
-
// Ignore abort errors as they are expected.
|
|
230
|
-
if ((err as any).name === 'AbortError') {
|
|
231
|
-
abortController = null;
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (onError && err instanceof Error) {
|
|
236
|
-
onError(err);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
error.value = err as Error;
|
|
240
|
-
} finally {
|
|
241
|
-
mutateLoading(() => false);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const append: UseChatHelpers['append'] = async (message, options) => {
|
|
246
|
-
if (!message.id) {
|
|
247
|
-
message.id = generateId();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return triggerRequest(messages.value.concat(message as Message), options);
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const reload: UseChatHelpers['reload'] = async options => {
|
|
254
|
-
const messagesSnapshot = messages.value;
|
|
255
|
-
if (messagesSnapshot.length === 0) return null;
|
|
256
|
-
|
|
257
|
-
const lastMessage = messagesSnapshot[messagesSnapshot.length - 1];
|
|
258
|
-
if (lastMessage.role === 'assistant') {
|
|
259
|
-
return triggerRequest(messagesSnapshot.slice(0, -1), options);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return triggerRequest(messagesSnapshot, options);
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const stop = () => {
|
|
266
|
-
if (abortController) {
|
|
267
|
-
abortController.abort();
|
|
268
|
-
abortController = null;
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const setMessages = (
|
|
273
|
-
messagesArg: Message[] | ((messages: Message[]) => Message[]),
|
|
274
|
-
) => {
|
|
275
|
-
if (typeof messagesArg === 'function') {
|
|
276
|
-
messagesArg = messagesArg(messages.value);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
mutate(messagesArg);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const input = ref(initialInput);
|
|
283
|
-
|
|
284
|
-
const handleSubmit = (
|
|
285
|
-
event?: { preventDefault?: () => void },
|
|
286
|
-
options: ChatRequestOptions = {},
|
|
287
|
-
) => {
|
|
288
|
-
event?.preventDefault?.();
|
|
289
|
-
|
|
290
|
-
const inputValue = input.value;
|
|
291
|
-
|
|
292
|
-
if (!inputValue && !options.allowEmptySubmit) return;
|
|
293
|
-
|
|
294
|
-
triggerRequest(
|
|
295
|
-
!inputValue && options.allowEmptySubmit
|
|
296
|
-
? messages.value
|
|
297
|
-
: messages.value.concat({
|
|
298
|
-
id: generateId(),
|
|
299
|
-
createdAt: new Date(),
|
|
300
|
-
content: inputValue,
|
|
301
|
-
role: 'user',
|
|
302
|
-
}),
|
|
303
|
-
options,
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
input.value = '';
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
messages,
|
|
311
|
-
append,
|
|
312
|
-
error,
|
|
313
|
-
reload,
|
|
314
|
-
stop,
|
|
315
|
-
setMessages,
|
|
316
|
-
input,
|
|
317
|
-
handleSubmit,
|
|
318
|
-
isLoading,
|
|
319
|
-
data: streamData as Ref<undefined | JSONValue[]>,
|
|
320
|
-
};
|
|
321
|
-
}
|