@ai-sdk/svelte 3.0.0-alpha.4 → 3.0.0-alpha.5
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 +13 -0
- package/dist/chat-store.svelte.d.ts +4 -0
- package/dist/chat-store.svelte.d.ts.map +1 -0
- package/dist/chat-store.svelte.js +43 -0
- package/dist/chat.svelte.d.ts +18 -16
- package/dist/chat.svelte.d.ts.map +1 -1
- package/dist/chat.svelte.js +73 -144
- package/dist/context-provider.d.ts +3 -1
- package/dist/context-provider.d.ts.map +1 -1
- package/dist/context-provider.js +10 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tests/chat-synchronization.svelte +7 -3
- package/dist/tests/chat-synchronization.svelte.d.ts +2 -2
- package/dist/tests/chat-synchronization.svelte.d.ts.map +1 -1
- package/dist/utils.svelte.js +1 -1
- package/package.json +5 -5
- package/src/chat-store.svelte.ts +85 -0
- package/src/chat.svelte.ts +122 -199
- package/src/context-provider.ts +15 -4
- package/src/index.ts +2 -0
- package/src/tests/chat-synchronization.svelte +7 -3
- package/src/utils.svelte.ts +1 -1
- package/dist/chat-context.svelte.js +0 -13
- package/src/chat-context.svelte.ts +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @ai-sdk/svelte
|
|
2
2
|
|
|
3
|
+
## 3.0.0-alpha.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [655cf3c]
|
|
8
|
+
- Updated dependencies [1675396]
|
|
9
|
+
- Updated dependencies [cf9af6e]
|
|
10
|
+
- Updated dependencies [ef256ed]
|
|
11
|
+
- Updated dependencies [1ed0287]
|
|
12
|
+
- Updated dependencies [825e8d7]
|
|
13
|
+
- Updated dependencies [7324c21]
|
|
14
|
+
- ai@5.0.0-alpha.5
|
|
15
|
+
|
|
3
16
|
## 3.0.0-alpha.4
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ChatStore, type ChatStoreOptions, type UIDataPartSchemas } from 'ai';
|
|
2
|
+
export declare const hasChatStoreContext: () => boolean, getChatStoreContext: () => ChatStore<unknown, UIDataPartSchemas>, setChatStoreContext: (value: ChatStore<unknown, UIDataPartSchemas>) => ChatStore<unknown, UIDataPartSchemas>;
|
|
3
|
+
export declare function createChatStore<MESSAGE_METADATA = unknown, DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas>(options: ChatStoreOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>): ChatStore<MESSAGE_METADATA, DATA_PART_SCHEMAS>;
|
|
4
|
+
//# sourceMappingURL=chat-store.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-store.svelte.d.ts","sourceRoot":"","sources":["../src/chat-store.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAKT,KAAK,gBAAgB,EAErB,KAAK,iBAAiB,EAGvB,MAAM,IAAI,CAAC;AAGZ,eAAO,MACO,mBAAmB,iBACnB,mBAAmB,+CACnB,mBAAmB,yFACQ,CAAC;AAqD1C,wBAAgB,eAAe,CAC7B,gBAAgB,GAAG,OAAO,EAC1B,iBAAiB,SAAS,iBAAiB,GAAG,iBAAiB,EAE/D,OAAO,EAAE,gBAAgB,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,GAC7D,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAQhD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ChatStore, SerialJobExecutor, } from 'ai';
|
|
2
|
+
import { createContext } from './utils.svelte.js';
|
|
3
|
+
export const { hasContext: hasChatStoreContext, getContext: getChatStoreContext, setContext: setChatStoreContext, } = createContext('ChatStore');
|
|
4
|
+
class SvelteChat {
|
|
5
|
+
messages;
|
|
6
|
+
status = $state('ready');
|
|
7
|
+
error = $state(undefined);
|
|
8
|
+
activeResponse = undefined;
|
|
9
|
+
jobExecutor = new SerialJobExecutor();
|
|
10
|
+
constructor(messages) {
|
|
11
|
+
this.messages = $state(messages ?? []);
|
|
12
|
+
}
|
|
13
|
+
setStatus = (status) => {
|
|
14
|
+
this.status = status;
|
|
15
|
+
};
|
|
16
|
+
setError = (error) => {
|
|
17
|
+
this.error = error;
|
|
18
|
+
};
|
|
19
|
+
setActiveResponse = (activeResponse) => {
|
|
20
|
+
this.activeResponse = activeResponse;
|
|
21
|
+
};
|
|
22
|
+
setMessages = (messages) => {
|
|
23
|
+
this.messages = messages;
|
|
24
|
+
};
|
|
25
|
+
pushMessage = (message) => {
|
|
26
|
+
this.messages.push(message);
|
|
27
|
+
};
|
|
28
|
+
popMessage = () => {
|
|
29
|
+
this.messages.pop();
|
|
30
|
+
};
|
|
31
|
+
replaceMessage = (index, message) => {
|
|
32
|
+
this.messages[index] = message;
|
|
33
|
+
};
|
|
34
|
+
snapshot = (thing) => {
|
|
35
|
+
return $state.snapshot(thing);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function createChatStore(options) {
|
|
39
|
+
return new ChatStore({
|
|
40
|
+
...options,
|
|
41
|
+
createChat: options => new SvelteChat(options.messages),
|
|
42
|
+
});
|
|
43
|
+
}
|
package/dist/chat.svelte.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import { type ChatRequestOptions, type CreateUIMessage, type
|
|
2
|
-
export type ChatOptions<MESSAGE_METADATA = unknown> = Readonly<
|
|
1
|
+
import { type ChatRequestOptions, type ChatStatus, type CreateUIMessage, type InferUIDataParts, type UIDataPartSchemas, type UIMessage, type UseChatOptions } from 'ai';
|
|
2
|
+
export type ChatOptions<MESSAGE_METADATA = unknown, DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas> = Readonly<UseChatOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>>;
|
|
3
3
|
export type { CreateUIMessage, UIMessage };
|
|
4
|
-
export declare class Chat<MESSAGE_METADATA = unknown> {
|
|
4
|
+
export declare class Chat<MESSAGE_METADATA = unknown, DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas> {
|
|
5
5
|
#private;
|
|
6
6
|
/**
|
|
7
7
|
* The id of the chat. If not provided through the constructor, a random ID will be generated
|
|
8
8
|
* using the provided `generateId` function, or a built-in function if not provided.
|
|
9
9
|
*/
|
|
10
10
|
readonly chatId: string;
|
|
11
|
+
/** The current value of the input. Writable, so it can be bound to form inputs. */
|
|
12
|
+
input: string;
|
|
13
|
+
/**
|
|
14
|
+
* Current messages in the chat.
|
|
15
|
+
*
|
|
16
|
+
* This is writable, which is useful when you want to edit the messages on the client, and then
|
|
17
|
+
* trigger {@link reload} to regenerate the AI response.
|
|
18
|
+
*/
|
|
19
|
+
get messages(): UIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>[];
|
|
20
|
+
set messages(messages: UIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>[]);
|
|
11
21
|
/**
|
|
12
22
|
* Hook status:
|
|
13
23
|
*
|
|
@@ -16,27 +26,19 @@ export declare class Chat<MESSAGE_METADATA = unknown> {
|
|
|
16
26
|
* - `ready`: The full response has been received and processed; a new user message can be submitted.
|
|
17
27
|
* - `error`: An error occurred during the API request, preventing successful completion.
|
|
18
28
|
*/
|
|
19
|
-
get status():
|
|
29
|
+
get status(): ChatStatus;
|
|
30
|
+
set status(value: ChatStatus);
|
|
20
31
|
/** The error object of the API request */
|
|
21
32
|
get error(): Error | undefined;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Current messages in the chat.
|
|
26
|
-
*
|
|
27
|
-
* This is writable, which is useful when you want to edit the messages on the client, and then
|
|
28
|
-
* trigger {@link reload} to regenerate the AI response.
|
|
29
|
-
*/
|
|
30
|
-
get messages(): UIMessage<MESSAGE_METADATA>[];
|
|
31
|
-
set messages(value: UIMessage<MESSAGE_METADATA>[]);
|
|
32
|
-
constructor(options?: ChatOptions<MESSAGE_METADATA>);
|
|
33
|
+
set error(value: Error | undefined);
|
|
34
|
+
constructor(options?: () => ChatOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>);
|
|
33
35
|
/**
|
|
34
36
|
* Append a user message to the chat list. This triggers the API call to fetch
|
|
35
37
|
* the assistant's response.
|
|
36
38
|
* @param message The message to append
|
|
37
39
|
* @param options Additional options to pass to the API call
|
|
38
40
|
*/
|
|
39
|
-
append: (message: UIMessage<MESSAGE_METADATA
|
|
41
|
+
append: (message: UIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>> | CreateUIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>, { headers, body }?: ChatRequestOptions) => Promise<void>;
|
|
40
42
|
/**
|
|
41
43
|
* Reload the last AI chat response for the given chat history. If the last
|
|
42
44
|
* message isn't from the assistant, it will request the API to generate a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.svelte.d.ts","sourceRoot":"","sources":["../src/chat.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat.svelte.d.ts","sourceRoot":"","sources":["../src/chat.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,IAAI,CAAC;AAOZ,MAAM,MAAM,WAAW,CACrB,gBAAgB,GAAG,OAAO,EAC1B,iBAAiB,SAAS,iBAAiB,GAAG,iBAAiB,IAC7D,QAAQ,CAAC,cAAc,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAElE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;AAE3C,qBAAa,IAAI,CACf,gBAAgB,GAAG,OAAO,EAC1B,iBAAiB,SAAS,iBAAiB,GAAG,iBAAiB;;IAK/D;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,mFAAmF;IACnF,KAAK,SAAsB;IAE3B;;;;;OAKG;IACH,IAAI,QAAQ,IAAI,SAAS,CACvB,gBAAgB,EAChB,gBAAgB,CAAC,iBAAiB,CAAC,CACpC,EAAE,CAEF;IACD,IAAI,QAAQ,CACV,QAAQ,EAAE,SAAS,CACjB,gBAAgB,EAChB,gBAAgB,CAAC,iBAAiB,CAAC,CACpC,EAAE,EAGJ;IAED;;;;;;;OAOG;IACH,IAAI,MAAM,IAAI,UAAU,CAEvB;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,UAAU,EAE3B;IAED,0CAA0C;IAC1C,IAAI,KAAK,IAAI,KAAK,GAAG,SAAS,CAE7B;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,EAMjC;gBAGC,OAAO,GAAE,MAAM,WAAW,CACxB,gBAAgB,EAChB,iBAAiB,CACL;IAkChB;;;;;OAKG;IACH,MAAM,YAEA,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,GAChE,eAAe,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,sBACvD,kBAAkB,mBAWrC;IAEF;;;;OAIG;IACH,MAAM,uBAA6B,kBAAkB,mBASnD;IAEF;;OAEG;IACH,IAAI,aAEF;IAEF,qFAAqF;IACrF,YAAY,WACF;QAAE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,YAC9B,kBAAkB,GAAG;QAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;KAAE,mBAwBlD;IAEF,aAAa,4BAGV;QACD,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;KACjB,mBAMC;CACH"}
|
package/dist/chat.svelte.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { untrack } from 'svelte';
|
|
4
|
-
import { KeyedChatStore, getChatContext, hasChatContext, } from './chat-context.svelte.js';
|
|
1
|
+
import { ChatStore, convertFileListToFileUIParts, defaultChatStoreOptions, generateId, } from 'ai';
|
|
2
|
+
import { getChatStoreContext, hasChatStoreContext, createChatStore, } from './chat-store.svelte.js';
|
|
5
3
|
export class Chat {
|
|
6
|
-
#options
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#maxSteps = $derived(this.#options.maxSteps ?? 1);
|
|
10
|
-
#streamProtocol = $derived(this.#options.streamProtocol ?? 'ui-message');
|
|
11
|
-
#keyedStore = $state();
|
|
4
|
+
#options;
|
|
5
|
+
#generateId;
|
|
6
|
+
#chatStore;
|
|
12
7
|
/**
|
|
13
8
|
* The id of the chat. If not provided through the constructor, a random ID will be generated
|
|
14
9
|
* using the provided `generateId` function, or a built-in function if not provided.
|
|
15
10
|
*/
|
|
16
|
-
chatId
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
chatId;
|
|
12
|
+
/** The current value of the input. Writable, so it can be bound to form inputs. */
|
|
13
|
+
input = $state('');
|
|
14
|
+
/**
|
|
15
|
+
* Current messages in the chat.
|
|
16
|
+
*
|
|
17
|
+
* This is writable, which is useful when you want to edit the messages on the client, and then
|
|
18
|
+
* trigger {@link reload} to regenerate the AI response.
|
|
19
|
+
*/
|
|
20
|
+
get messages() {
|
|
21
|
+
return this.#chatStore.getMessages(this.chatId);
|
|
22
|
+
}
|
|
23
|
+
set messages(messages) {
|
|
24
|
+
this.#chatStore.setMessages({ id: this.chatId, messages });
|
|
25
|
+
}
|
|
20
26
|
/**
|
|
21
27
|
* Hook status:
|
|
22
28
|
*
|
|
@@ -26,36 +32,48 @@ export class Chat {
|
|
|
26
32
|
* - `error`: An error occurred during the API request, preventing successful completion.
|
|
27
33
|
*/
|
|
28
34
|
get status() {
|
|
29
|
-
return this.#
|
|
35
|
+
return this.#chatStore.getStatus(this.chatId);
|
|
36
|
+
}
|
|
37
|
+
set status(value) {
|
|
38
|
+
this.#chatStore.setStatus({ id: this.chatId, status: value });
|
|
30
39
|
}
|
|
31
40
|
/** The error object of the API request */
|
|
32
41
|
get error() {
|
|
33
|
-
return this.#
|
|
42
|
+
return this.#chatStore.getError(this.chatId);
|
|
34
43
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* trigger {@link reload} to regenerate the AI response.
|
|
42
|
-
*/
|
|
43
|
-
get messages() {
|
|
44
|
-
return this.#store.messages;
|
|
45
|
-
}
|
|
46
|
-
set messages(value) {
|
|
47
|
-
untrack(() => (this.#store.messages = value));
|
|
44
|
+
set error(value) {
|
|
45
|
+
this.#chatStore.setStatus({
|
|
46
|
+
id: this.chatId,
|
|
47
|
+
status: 'error',
|
|
48
|
+
error: value,
|
|
49
|
+
});
|
|
48
50
|
}
|
|
49
|
-
constructor(options = {}) {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
constructor(options = () => ({})) {
|
|
52
|
+
this.#options = $derived.by(options);
|
|
53
|
+
this.#generateId = $derived(this.#options.generateId ?? generateId);
|
|
54
|
+
this.chatId = $derived(this.#options.chatId ?? this.#generateId());
|
|
55
|
+
if (this.#options.chatStore) {
|
|
56
|
+
if (typeof this.#options.chatStore === 'function') {
|
|
57
|
+
this.#chatStore = createChatStore(this.#options.chatStore());
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.#chatStore = this.#options.chatStore;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (hasChatStoreContext()) {
|
|
64
|
+
this.#chatStore = getChatStoreContext();
|
|
52
65
|
}
|
|
53
66
|
else {
|
|
54
|
-
this.#
|
|
67
|
+
this.#chatStore = createChatStore(defaultChatStoreOptions({
|
|
68
|
+
api: '/api/chat',
|
|
69
|
+
generateId: this.#options.generateId || generateId,
|
|
70
|
+
})());
|
|
71
|
+
}
|
|
72
|
+
this.input = this.#options.initialInput ?? '';
|
|
73
|
+
if (!this.#chatStore.hasChat(this.chatId)) {
|
|
74
|
+
const messages = $state([]);
|
|
75
|
+
this.#chatStore.addChat(this.chatId, messages);
|
|
55
76
|
}
|
|
56
|
-
this.#options = options;
|
|
57
|
-
this.messages = options.initialMessages ?? [];
|
|
58
|
-
this.input = options.initialInput ?? '';
|
|
59
77
|
}
|
|
60
78
|
/**
|
|
61
79
|
* Append a user message to the chat list. This triggers the API call to fetch
|
|
@@ -64,11 +82,15 @@ export class Chat {
|
|
|
64
82
|
* @param options Additional options to pass to the API call
|
|
65
83
|
*/
|
|
66
84
|
append = async (message, { headers, body } = {}) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
await this.#chatStore.submitMessage({
|
|
86
|
+
chatId: this.chatId,
|
|
87
|
+
message,
|
|
88
|
+
headers,
|
|
89
|
+
body,
|
|
90
|
+
onError: this.#options.onError,
|
|
91
|
+
onToolCall: this.#options.onToolCall,
|
|
92
|
+
onFinish: this.#options.onFinish,
|
|
70
93
|
});
|
|
71
|
-
return this.#triggerRequest({ messages, headers, body });
|
|
72
94
|
};
|
|
73
95
|
/**
|
|
74
96
|
* Reload the last AI chat response for the given chat history. If the last
|
|
@@ -76,32 +98,20 @@ export class Chat {
|
|
|
76
98
|
* new response.
|
|
77
99
|
*/
|
|
78
100
|
reload = async ({ headers, body } = {}) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
const lastMessage = this.messages[this.messages.length - 1];
|
|
83
|
-
await this.#triggerRequest({
|
|
84
|
-
messages: lastMessage.role === 'assistant'
|
|
85
|
-
? this.messages.slice(0, -1)
|
|
86
|
-
: this.messages,
|
|
101
|
+
await this.#chatStore.resubmitLastUserMessage({
|
|
102
|
+
chatId: this.chatId,
|
|
87
103
|
headers,
|
|
88
104
|
body,
|
|
105
|
+
onError: this.#options.onError,
|
|
106
|
+
onToolCall: this.#options.onToolCall,
|
|
107
|
+
onFinish: this.#options.onFinish,
|
|
89
108
|
});
|
|
90
109
|
};
|
|
91
110
|
/**
|
|
92
111
|
* Abort the current request immediately, keep the generated tokens if any.
|
|
93
112
|
*/
|
|
94
113
|
stop = () => {
|
|
95
|
-
|
|
96
|
-
this.#abortController?.abort();
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// ignore
|
|
100
|
-
}
|
|
101
|
-
finally {
|
|
102
|
-
this.#store.status = 'ready';
|
|
103
|
-
this.#abortController = undefined;
|
|
104
|
-
}
|
|
114
|
+
this.#chatStore.stopStream({ chatId: this.chatId });
|
|
105
115
|
};
|
|
106
116
|
/** Form submission handler to automatically reset input and append a user message */
|
|
107
117
|
handleSubmit = async (event, options = {}) => {
|
|
@@ -111,13 +121,11 @@ export class Chat {
|
|
|
111
121
|
: await convertFileListToFileUIParts(options?.files);
|
|
112
122
|
if (!this.input && fileParts.length === 0)
|
|
113
123
|
return;
|
|
114
|
-
const
|
|
124
|
+
const request = this.append({
|
|
115
125
|
id: this.#generateId(),
|
|
116
126
|
role: 'user',
|
|
117
127
|
parts: [...fileParts, { type: 'text', text: this.input }],
|
|
118
|
-
}
|
|
119
|
-
const request = this.#triggerRequest({
|
|
120
|
-
messages,
|
|
128
|
+
}, {
|
|
121
129
|
headers: options.headers,
|
|
122
130
|
body: options.body,
|
|
123
131
|
});
|
|
@@ -125,89 +133,10 @@ export class Chat {
|
|
|
125
133
|
await request;
|
|
126
134
|
};
|
|
127
135
|
addToolResult = async ({ toolCallId, result, }) => {
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
await this.#chatStore.addToolResult({
|
|
137
|
+
chatId: this.chatId,
|
|
130
138
|
toolCallId,
|
|
131
|
-
|
|
139
|
+
result,
|
|
132
140
|
});
|
|
133
|
-
// when the request is ongoing, the auto-submit will be triggered after the request is finished
|
|
134
|
-
if (this.#store.status === 'submitted' ||
|
|
135
|
-
this.#store.status === 'streaming') {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const lastMessage = this.messages[this.messages.length - 1];
|
|
139
|
-
if (isAssistantMessageWithCompletedToolCalls(lastMessage)) {
|
|
140
|
-
await this.#triggerRequest({ messages: this.messages });
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
#triggerRequest = async (chatRequest) => {
|
|
144
|
-
this.#store.status = 'submitted';
|
|
145
|
-
this.#store.error = undefined;
|
|
146
|
-
const messages = chatRequest.messages;
|
|
147
|
-
const messageCount = messages.length;
|
|
148
|
-
const maxStep = extractMaxToolInvocationStep(getToolInvocations(messages[messages.length - 1]));
|
|
149
|
-
try {
|
|
150
|
-
const abortController = new AbortController();
|
|
151
|
-
this.#abortController = abortController;
|
|
152
|
-
// Optimistically update messages
|
|
153
|
-
this.messages = messages;
|
|
154
|
-
await callChatApi({
|
|
155
|
-
api: this.#api,
|
|
156
|
-
body: {
|
|
157
|
-
id: this.chatId,
|
|
158
|
-
messages,
|
|
159
|
-
...$state.snapshot(this.#options.body),
|
|
160
|
-
...chatRequest.body,
|
|
161
|
-
},
|
|
162
|
-
streamProtocol: this.#streamProtocol,
|
|
163
|
-
credentials: this.#options.credentials,
|
|
164
|
-
headers: {
|
|
165
|
-
...this.#options.headers,
|
|
166
|
-
...chatRequest.headers,
|
|
167
|
-
},
|
|
168
|
-
abortController: () => abortController,
|
|
169
|
-
onUpdate: ({ message }) => {
|
|
170
|
-
this.#store.status = 'streaming';
|
|
171
|
-
const replaceLastMessage = message.id === messages[messages.length - 1].id;
|
|
172
|
-
this.messages = messages;
|
|
173
|
-
if (replaceLastMessage) {
|
|
174
|
-
this.messages[this.messages.length - 1] = message;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
this.messages.push(message);
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
onToolCall: this.#options.onToolCall,
|
|
181
|
-
onFinish: this.#options.onFinish,
|
|
182
|
-
generateId: this.#generateId,
|
|
183
|
-
fetch: this.#options.fetch,
|
|
184
|
-
// callChatApi calls structuredClone on the message
|
|
185
|
-
lastMessage: $state.snapshot(this.messages[this.messages.length - 1]),
|
|
186
|
-
messageMetadataSchema: this.#messageMetadataSchema,
|
|
187
|
-
});
|
|
188
|
-
this.#abortController = undefined;
|
|
189
|
-
this.#store.status = 'ready';
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
if (isAbortError(error)) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const coalescedError = error instanceof Error ? error : new Error(String(error));
|
|
196
|
-
if (this.#options.onError) {
|
|
197
|
-
this.#options.onError(coalescedError);
|
|
198
|
-
}
|
|
199
|
-
this.#store.status = 'error';
|
|
200
|
-
this.#store.error = coalescedError;
|
|
201
|
-
}
|
|
202
|
-
// auto-submit when all tool calls in the last assistant message have results
|
|
203
|
-
// and assistant has not answered yet
|
|
204
|
-
if (shouldResubmitMessages({
|
|
205
|
-
originalMaxToolInvocationStep: maxStep,
|
|
206
|
-
originalMessageCount: messageCount,
|
|
207
|
-
maxSteps: this.#maxSteps,
|
|
208
|
-
messages: this.messages,
|
|
209
|
-
})) {
|
|
210
|
-
await this.#triggerRequest({ messages: this.messages });
|
|
211
|
-
}
|
|
212
141
|
};
|
|
213
142
|
}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ChatStore } from 'ai';
|
|
2
|
+
export declare function createAIContext(chatStore?: ChatStore): void;
|
|
3
|
+
export declare function createChatStoreContext(chatStore?: ChatStore): void;
|
|
2
4
|
//# sourceMappingURL=context-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-provider.d.ts","sourceRoot":"","sources":["../src/context-provider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context-provider.d.ts","sourceRoot":"","sources":["../src/context-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,IAAI,CAAC;AAW7D,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,SAAS,QAQpD;AAED,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,SAAS,QAS3D"}
|
package/dist/context-provider.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultChatStoreOptions } from 'ai';
|
|
2
|
+
import { createChatStore, setChatStoreContext } from './chat-store.svelte.js';
|
|
2
3
|
import { KeyedCompletionStore, setCompletionContext, } from './completion-context.svelte.js';
|
|
3
4
|
import { KeyedStructuredObjectStore, setStructuredObjectContext, } from './structured-object-context.svelte.js';
|
|
4
|
-
export function createAIContext() {
|
|
5
|
-
|
|
6
|
-
setChatContext(chatStore);
|
|
5
|
+
export function createAIContext(chatStore) {
|
|
6
|
+
createChatStoreContext(chatStore);
|
|
7
7
|
const completionStore = new KeyedCompletionStore();
|
|
8
8
|
setCompletionContext(completionStore);
|
|
9
9
|
const objectStore = new KeyedStructuredObjectStore();
|
|
10
10
|
setStructuredObjectContext(objectStore);
|
|
11
11
|
}
|
|
12
|
+
export function createChatStoreContext(chatStore) {
|
|
13
|
+
setChatStoreContext(chatStore ??
|
|
14
|
+
createChatStore(defaultChatStoreOptions({
|
|
15
|
+
api: '/api/chat',
|
|
16
|
+
})()));
|
|
17
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { Chat, type ChatOptions, type CreateUIMessage, type UIMessage, } from '.
|
|
|
2
2
|
export { StructuredObject as Experimental_StructuredObject, type Experimental_StructuredObjectOptions, } from './structured-object.svelte.js';
|
|
3
3
|
export { Completion, type CompletionOptions } from './completion.svelte.js';
|
|
4
4
|
export { createAIContext } from './context-provider.js';
|
|
5
|
+
export { createChatStore } from './chat-store.svelte.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,gBAAgB,IAAI,6BAA6B,EACjD,KAAK,oCAAoC,GAC1C,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,gBAAgB,IAAI,6BAA6B,EACjD,KAAK,oCAAoC,GAC1C,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE5E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { Chat, } from './chat.svelte.js';
|
|
|
2
2
|
export { StructuredObject as Experimental_StructuredObject, } from './structured-object.svelte.js';
|
|
3
3
|
export { Completion } from './completion.svelte.js';
|
|
4
4
|
export { createAIContext } from './context-provider.js';
|
|
5
|
+
export { createChatStore } from './chat-store.svelte.js';
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createChatStore } from '../chat-store.svelte.js';
|
|
2
3
|
import { Chat } from '../chat.svelte.js';
|
|
4
|
+
import { defaultChatStoreOptions } from 'ai';
|
|
3
5
|
import { createAIContext } from '../context-provider.js';
|
|
4
6
|
|
|
5
7
|
let { chatId }: { chatId?: string } = $props();
|
|
6
8
|
|
|
7
|
-
createAIContext(
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
createAIContext(
|
|
10
|
+
createChatStore(defaultChatStoreOptions({ api: '/api/chat' })()),
|
|
11
|
+
);
|
|
12
|
+
const chat1 = new Chat(() => ({ chatId }));
|
|
13
|
+
const chat2 = new Chat(() => ({ chatId }));
|
|
10
14
|
|
|
11
15
|
export { chat1, chat2 };
|
|
12
16
|
</script>
|
|
@@ -3,8 +3,8 @@ type $$ComponentProps = {
|
|
|
3
3
|
chatId?: string;
|
|
4
4
|
};
|
|
5
5
|
declare const ChatSynchronization: import("svelte").Component<$$ComponentProps, {
|
|
6
|
-
chat1: Chat<unknown>;
|
|
7
|
-
chat2: Chat<unknown>;
|
|
6
|
+
chat1: Chat<unknown, import("ai").UIDataPartSchemas>;
|
|
7
|
+
chat2: Chat<unknown, import("ai").UIDataPartSchemas>;
|
|
8
8
|
}, "">;
|
|
9
9
|
type ChatSynchronization = ReturnType<typeof ChatSynchronization>;
|
|
10
10
|
export default ChatSynchronization;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-synchronization.svelte.d.ts","sourceRoot":"","sources":["../../src/tests/chat-synchronization.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat-synchronization.svelte.d.ts","sourceRoot":"","sources":["../../src/tests/chat-synchronization.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAIxC,KAAK,gBAAgB,GAAI;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAoB9C,QAAA,MAAM,mBAAmB;;;MAAsC,CAAC;AAChE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
package/dist/utils.svelte.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/svelte",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"svelte": "^5.
|
|
30
|
+
"svelte": "^5.31.0",
|
|
31
31
|
"zod": "^3.23.8"
|
|
32
32
|
},
|
|
33
33
|
"peerDependenciesMeta": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
-
"ai": "
|
|
39
|
+
"ai": "5.0.0-alpha.5",
|
|
40
|
+
"@ai-sdk/provider-utils": "3.0.0-alpha.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "20.17.24",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"globals": "^16.0.0",
|
|
53
53
|
"jsdom": "^26.0.0",
|
|
54
54
|
"publint": "^0.3.2",
|
|
55
|
-
"svelte": "^5.
|
|
55
|
+
"svelte": "^5.31.0",
|
|
56
56
|
"svelte-check": "^4.0.0",
|
|
57
57
|
"typescript": "^5.0.0",
|
|
58
58
|
"typescript-eslint": "^8.20.0",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChatStore,
|
|
3
|
+
SerialJobExecutor,
|
|
4
|
+
type ActiveResponse,
|
|
5
|
+
type Chat,
|
|
6
|
+
type ChatStatus,
|
|
7
|
+
type ChatStoreOptions,
|
|
8
|
+
type InferUIDataParts,
|
|
9
|
+
type UIDataPartSchemas,
|
|
10
|
+
type UIDataTypes,
|
|
11
|
+
type UIMessage,
|
|
12
|
+
} from 'ai';
|
|
13
|
+
import { createContext } from './utils.svelte.js';
|
|
14
|
+
|
|
15
|
+
export const {
|
|
16
|
+
hasContext: hasChatStoreContext,
|
|
17
|
+
getContext: getChatStoreContext,
|
|
18
|
+
setContext: setChatStoreContext,
|
|
19
|
+
} = createContext<ChatStore>('ChatStore');
|
|
20
|
+
|
|
21
|
+
class SvelteChat<MESSAGE_METADATA, DATA_TYPES extends UIDataTypes>
|
|
22
|
+
implements Chat<MESSAGE_METADATA, DATA_TYPES>
|
|
23
|
+
{
|
|
24
|
+
messages: UIMessage<MESSAGE_METADATA, DATA_TYPES>[];
|
|
25
|
+
status = $state<ChatStatus>('ready');
|
|
26
|
+
error = $state<Error | undefined>(undefined);
|
|
27
|
+
activeResponse: ActiveResponse<MESSAGE_METADATA> | undefined = undefined;
|
|
28
|
+
jobExecutor = new SerialJobExecutor();
|
|
29
|
+
|
|
30
|
+
constructor(messages?: UIMessage<MESSAGE_METADATA, DATA_TYPES>[]) {
|
|
31
|
+
this.messages = $state(messages ?? []);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
setStatus = (status: ChatStatus) => {
|
|
35
|
+
this.status = status;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
setError = (error: Error | undefined) => {
|
|
39
|
+
this.error = error;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
setActiveResponse = (
|
|
43
|
+
activeResponse: ActiveResponse<MESSAGE_METADATA> | undefined,
|
|
44
|
+
) => {
|
|
45
|
+
this.activeResponse = activeResponse;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
setMessages = (messages: UIMessage<MESSAGE_METADATA, DATA_TYPES>[]) => {
|
|
49
|
+
this.messages = messages;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
pushMessage = (message: UIMessage<MESSAGE_METADATA, DATA_TYPES>) => {
|
|
53
|
+
this.messages.push(message);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
popMessage = () => {
|
|
57
|
+
this.messages.pop();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
replaceMessage = (
|
|
61
|
+
index: number,
|
|
62
|
+
message: UIMessage<MESSAGE_METADATA, DATA_TYPES>,
|
|
63
|
+
) => {
|
|
64
|
+
this.messages[index] = message;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
snapshot = <T>(thing: T): T => {
|
|
68
|
+
return $state.snapshot(thing) as T;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function createChatStore<
|
|
73
|
+
MESSAGE_METADATA = unknown,
|
|
74
|
+
DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas,
|
|
75
|
+
>(
|
|
76
|
+
options: ChatStoreOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>,
|
|
77
|
+
): ChatStore<MESSAGE_METADATA, DATA_PART_SCHEMAS> {
|
|
78
|
+
return new ChatStore<MESSAGE_METADATA, DATA_PART_SCHEMAS>({
|
|
79
|
+
...options,
|
|
80
|
+
createChat: options =>
|
|
81
|
+
new SvelteChat<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>(
|
|
82
|
+
options.messages,
|
|
83
|
+
),
|
|
84
|
+
});
|
|
85
|
+
}
|
package/src/chat.svelte.ts
CHANGED
|
@@ -1,52 +1,66 @@
|
|
|
1
|
-
import { isAbortError } from '@ai-sdk/provider-utils';
|
|
2
1
|
import {
|
|
3
|
-
|
|
2
|
+
ChatStore,
|
|
4
3
|
convertFileListToFileUIParts,
|
|
5
|
-
|
|
4
|
+
defaultChatStoreOptions,
|
|
6
5
|
generateId,
|
|
7
|
-
getToolInvocations,
|
|
8
|
-
isAssistantMessageWithCompletedToolCalls,
|
|
9
|
-
shouldResubmitMessages,
|
|
10
|
-
updateToolCallResult,
|
|
11
6
|
type ChatRequestOptions,
|
|
7
|
+
type ChatStatus,
|
|
12
8
|
type CreateUIMessage,
|
|
13
|
-
type
|
|
9
|
+
type IdGenerator,
|
|
10
|
+
type InferUIDataParts,
|
|
11
|
+
type UIDataPartSchemas,
|
|
14
12
|
type UIMessage,
|
|
13
|
+
type UseChatOptions,
|
|
15
14
|
} from 'ai';
|
|
16
|
-
import { untrack } from 'svelte';
|
|
17
15
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from './chat-
|
|
16
|
+
getChatStoreContext,
|
|
17
|
+
hasChatStoreContext,
|
|
18
|
+
createChatStore,
|
|
19
|
+
} from './chat-store.svelte.js';
|
|
22
20
|
|
|
23
|
-
export type ChatOptions<
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
export type ChatOptions<
|
|
22
|
+
MESSAGE_METADATA = unknown,
|
|
23
|
+
DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas,
|
|
24
|
+
> = Readonly<UseChatOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>>;
|
|
26
25
|
|
|
27
26
|
export type { CreateUIMessage, UIMessage };
|
|
28
27
|
|
|
29
|
-
export class Chat<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
readonly #
|
|
34
|
-
readonly #
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
readonly #keyedStore = $state<KeyedChatStore<MESSAGE_METADATA>>()!;
|
|
28
|
+
export class Chat<
|
|
29
|
+
MESSAGE_METADATA = unknown,
|
|
30
|
+
DATA_PART_SCHEMAS extends UIDataPartSchemas = UIDataPartSchemas,
|
|
31
|
+
> {
|
|
32
|
+
readonly #options: ChatOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>;
|
|
33
|
+
readonly #generateId: IdGenerator;
|
|
34
|
+
readonly #chatStore: ChatStore<MESSAGE_METADATA, DATA_PART_SCHEMAS>;
|
|
38
35
|
/**
|
|
39
36
|
* The id of the chat. If not provided through the constructor, a random ID will be generated
|
|
40
37
|
* using the provided `generateId` function, or a built-in function if not provided.
|
|
41
38
|
*/
|
|
42
|
-
readonly chatId
|
|
43
|
-
readonly #store = $derived(this.#keyedStore.get(this.chatId));
|
|
39
|
+
readonly chatId: string;
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
41
|
+
/** The current value of the input. Writable, so it can be bound to form inputs. */
|
|
42
|
+
input = $state<string>('');
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Current messages in the chat.
|
|
46
|
+
*
|
|
47
|
+
* This is writable, which is useful when you want to edit the messages on the client, and then
|
|
48
|
+
* trigger {@link reload} to regenerate the AI response.
|
|
49
|
+
*/
|
|
50
|
+
get messages(): UIMessage<
|
|
51
|
+
MESSAGE_METADATA,
|
|
52
|
+
InferUIDataParts<DATA_PART_SCHEMAS>
|
|
53
|
+
>[] {
|
|
54
|
+
return this.#chatStore.getMessages(this.chatId);
|
|
55
|
+
}
|
|
56
|
+
set messages(
|
|
57
|
+
messages: UIMessage<
|
|
58
|
+
MESSAGE_METADATA,
|
|
59
|
+
InferUIDataParts<DATA_PART_SCHEMAS>
|
|
60
|
+
>[],
|
|
61
|
+
) {
|
|
62
|
+
this.#chatStore.setMessages({ id: this.chatId, messages });
|
|
63
|
+
}
|
|
50
64
|
|
|
51
65
|
/**
|
|
52
66
|
* Hook status:
|
|
@@ -56,41 +70,61 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
56
70
|
* - `ready`: The full response has been received and processed; a new user message can be submitted.
|
|
57
71
|
* - `error`: An error occurred during the API request, preventing successful completion.
|
|
58
72
|
*/
|
|
59
|
-
get status() {
|
|
60
|
-
return this.#
|
|
73
|
+
get status(): ChatStatus {
|
|
74
|
+
return this.#chatStore.getStatus(this.chatId);
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
get error() {
|
|
65
|
-
return this.#store.error;
|
|
76
|
+
set status(value: ChatStatus) {
|
|
77
|
+
this.#chatStore.setStatus({ id: this.chatId, status: value });
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
/** The
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Current messages in the chat.
|
|
73
|
-
*
|
|
74
|
-
* This is writable, which is useful when you want to edit the messages on the client, and then
|
|
75
|
-
* trigger {@link reload} to regenerate the AI response.
|
|
76
|
-
*/
|
|
77
|
-
get messages(): UIMessage<MESSAGE_METADATA>[] {
|
|
78
|
-
return this.#store.messages;
|
|
80
|
+
/** The error object of the API request */
|
|
81
|
+
get error(): Error | undefined {
|
|
82
|
+
return this.#chatStore.getError(this.chatId);
|
|
79
83
|
}
|
|
80
|
-
set
|
|
81
|
-
|
|
84
|
+
set error(value: Error | undefined) {
|
|
85
|
+
this.#chatStore.setStatus({
|
|
86
|
+
id: this.chatId,
|
|
87
|
+
status: 'error',
|
|
88
|
+
error: value,
|
|
89
|
+
});
|
|
82
90
|
}
|
|
83
91
|
|
|
84
|
-
constructor(
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
constructor(
|
|
93
|
+
options: () => ChatOptions<
|
|
94
|
+
MESSAGE_METADATA,
|
|
95
|
+
DATA_PART_SCHEMAS
|
|
96
|
+
> = () => ({}),
|
|
97
|
+
) {
|
|
98
|
+
this.#options = $derived.by(options);
|
|
99
|
+
this.#generateId = $derived(this.#options.generateId ?? generateId);
|
|
100
|
+
this.chatId = $derived(this.#options.chatId ?? this.#generateId());
|
|
101
|
+
|
|
102
|
+
if (this.#options.chatStore) {
|
|
103
|
+
if (typeof this.#options.chatStore === 'function') {
|
|
104
|
+
this.#chatStore = createChatStore(this.#options.chatStore());
|
|
105
|
+
} else {
|
|
106
|
+
this.#chatStore = this.#options.chatStore;
|
|
107
|
+
}
|
|
108
|
+
} else if (hasChatStoreContext()) {
|
|
109
|
+
this.#chatStore = getChatStoreContext() as ChatStore<
|
|
110
|
+
MESSAGE_METADATA,
|
|
111
|
+
DATA_PART_SCHEMAS
|
|
112
|
+
>;
|
|
87
113
|
} else {
|
|
88
|
-
this.#
|
|
114
|
+
this.#chatStore = createChatStore(
|
|
115
|
+
defaultChatStoreOptions<MESSAGE_METADATA, DATA_PART_SCHEMAS>({
|
|
116
|
+
api: '/api/chat',
|
|
117
|
+
generateId: this.#options.generateId || generateId,
|
|
118
|
+
})(),
|
|
119
|
+
);
|
|
89
120
|
}
|
|
90
121
|
|
|
91
|
-
this.#options
|
|
92
|
-
|
|
93
|
-
this.
|
|
122
|
+
this.input = this.#options.initialInput ?? '';
|
|
123
|
+
|
|
124
|
+
if (!this.#chatStore.hasChat(this.chatId)) {
|
|
125
|
+
const messages = $state([]);
|
|
126
|
+
this.#chatStore.addChat(this.chatId, messages);
|
|
127
|
+
}
|
|
94
128
|
}
|
|
95
129
|
|
|
96
130
|
/**
|
|
@@ -100,15 +134,20 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
100
134
|
* @param options Additional options to pass to the API call
|
|
101
135
|
*/
|
|
102
136
|
append = async (
|
|
103
|
-
message:
|
|
137
|
+
message:
|
|
138
|
+
| UIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>
|
|
139
|
+
| CreateUIMessage<MESSAGE_METADATA, InferUIDataParts<DATA_PART_SCHEMAS>>,
|
|
104
140
|
{ headers, body }: ChatRequestOptions = {},
|
|
105
141
|
) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
142
|
+
await this.#chatStore.submitMessage({
|
|
143
|
+
chatId: this.chatId,
|
|
144
|
+
message,
|
|
145
|
+
headers,
|
|
146
|
+
body,
|
|
147
|
+
onError: this.#options.onError,
|
|
148
|
+
onToolCall: this.#options.onToolCall,
|
|
149
|
+
onFinish: this.#options.onFinish,
|
|
109
150
|
});
|
|
110
|
-
|
|
111
|
-
return this.#triggerRequest({ messages, headers, body });
|
|
112
151
|
};
|
|
113
152
|
|
|
114
153
|
/**
|
|
@@ -117,18 +156,13 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
117
156
|
* new response.
|
|
118
157
|
*/
|
|
119
158
|
reload = async ({ headers, body }: ChatRequestOptions = {}) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const lastMessage = this.messages[this.messages.length - 1];
|
|
125
|
-
await this.#triggerRequest({
|
|
126
|
-
messages:
|
|
127
|
-
lastMessage.role === 'assistant'
|
|
128
|
-
? this.messages.slice(0, -1)
|
|
129
|
-
: this.messages,
|
|
159
|
+
await this.#chatStore.resubmitLastUserMessage({
|
|
160
|
+
chatId: this.chatId,
|
|
130
161
|
headers,
|
|
131
162
|
body,
|
|
163
|
+
onError: this.#options.onError,
|
|
164
|
+
onToolCall: this.#options.onToolCall,
|
|
165
|
+
onFinish: this.#options.onFinish,
|
|
132
166
|
});
|
|
133
167
|
};
|
|
134
168
|
|
|
@@ -136,14 +170,7 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
136
170
|
* Abort the current request immediately, keep the generated tokens if any.
|
|
137
171
|
*/
|
|
138
172
|
stop = () => {
|
|
139
|
-
|
|
140
|
-
this.#abortController?.abort();
|
|
141
|
-
} catch {
|
|
142
|
-
// ignore
|
|
143
|
-
} finally {
|
|
144
|
-
this.#store.status = 'ready';
|
|
145
|
-
this.#abortController = undefined;
|
|
146
|
-
}
|
|
173
|
+
this.#chatStore.stopStream({ chatId: this.chatId });
|
|
147
174
|
};
|
|
148
175
|
|
|
149
176
|
/** Form submission handler to automatically reset input and append a user message */
|
|
@@ -159,17 +186,17 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
159
186
|
|
|
160
187
|
if (!this.input && fileParts.length === 0) return;
|
|
161
188
|
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
const request = this.append(
|
|
190
|
+
{
|
|
191
|
+
id: this.#generateId(),
|
|
192
|
+
role: 'user',
|
|
193
|
+
parts: [...fileParts, { type: 'text', text: this.input }],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
headers: options.headers,
|
|
197
|
+
body: options.body,
|
|
198
|
+
},
|
|
199
|
+
);
|
|
173
200
|
|
|
174
201
|
this.input = '';
|
|
175
202
|
await request;
|
|
@@ -182,114 +209,10 @@ export class Chat<MESSAGE_METADATA = unknown> {
|
|
|
182
209
|
toolCallId: string;
|
|
183
210
|
result: unknown;
|
|
184
211
|
}) => {
|
|
185
|
-
|
|
186
|
-
|
|
212
|
+
await this.#chatStore.addToolResult({
|
|
213
|
+
chatId: this.chatId,
|
|
187
214
|
toolCallId,
|
|
188
|
-
|
|
215
|
+
result,
|
|
189
216
|
});
|
|
190
|
-
|
|
191
|
-
// when the request is ongoing, the auto-submit will be triggered after the request is finished
|
|
192
|
-
if (
|
|
193
|
-
this.#store.status === 'submitted' ||
|
|
194
|
-
this.#store.status === 'streaming'
|
|
195
|
-
) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const lastMessage = this.messages[this.messages.length - 1];
|
|
200
|
-
if (isAssistantMessageWithCompletedToolCalls(lastMessage)) {
|
|
201
|
-
await this.#triggerRequest({ messages: this.messages });
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
#triggerRequest = async (
|
|
206
|
-
chatRequest: ChatRequestOptions & {
|
|
207
|
-
messages: UIMessage<MESSAGE_METADATA>[];
|
|
208
|
-
},
|
|
209
|
-
) => {
|
|
210
|
-
this.#store.status = 'submitted';
|
|
211
|
-
this.#store.error = undefined;
|
|
212
|
-
|
|
213
|
-
const messages = chatRequest.messages;
|
|
214
|
-
const messageCount = messages.length;
|
|
215
|
-
const maxStep = extractMaxToolInvocationStep(
|
|
216
|
-
getToolInvocations(messages[messages.length - 1]),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
const abortController = new AbortController();
|
|
221
|
-
this.#abortController = abortController;
|
|
222
|
-
|
|
223
|
-
// Optimistically update messages
|
|
224
|
-
this.messages = messages;
|
|
225
|
-
|
|
226
|
-
await callChatApi({
|
|
227
|
-
api: this.#api,
|
|
228
|
-
body: {
|
|
229
|
-
id: this.chatId,
|
|
230
|
-
messages,
|
|
231
|
-
...$state.snapshot(this.#options.body),
|
|
232
|
-
...chatRequest.body,
|
|
233
|
-
},
|
|
234
|
-
streamProtocol: this.#streamProtocol,
|
|
235
|
-
credentials: this.#options.credentials,
|
|
236
|
-
headers: {
|
|
237
|
-
...this.#options.headers,
|
|
238
|
-
...chatRequest.headers,
|
|
239
|
-
},
|
|
240
|
-
abortController: () => abortController,
|
|
241
|
-
onUpdate: ({ message }) => {
|
|
242
|
-
this.#store.status = 'streaming';
|
|
243
|
-
|
|
244
|
-
const replaceLastMessage =
|
|
245
|
-
message.id === messages[messages.length - 1].id;
|
|
246
|
-
|
|
247
|
-
this.messages = messages;
|
|
248
|
-
if (replaceLastMessage) {
|
|
249
|
-
this.messages[this.messages.length - 1] = message;
|
|
250
|
-
} else {
|
|
251
|
-
this.messages.push(message);
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
onToolCall: this.#options.onToolCall,
|
|
255
|
-
onFinish: this.#options.onFinish,
|
|
256
|
-
generateId: this.#generateId,
|
|
257
|
-
fetch: this.#options.fetch,
|
|
258
|
-
// callChatApi calls structuredClone on the message
|
|
259
|
-
lastMessage: $state.snapshot(
|
|
260
|
-
this.messages[this.messages.length - 1],
|
|
261
|
-
) as UIMessage<MESSAGE_METADATA>,
|
|
262
|
-
messageMetadataSchema: this.#messageMetadataSchema,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
this.#abortController = undefined;
|
|
266
|
-
this.#store.status = 'ready';
|
|
267
|
-
} catch (error) {
|
|
268
|
-
if (isAbortError(error)) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const coalescedError =
|
|
273
|
-
error instanceof Error ? error : new Error(String(error));
|
|
274
|
-
if (this.#options.onError) {
|
|
275
|
-
this.#options.onError(coalescedError);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
this.#store.status = 'error';
|
|
279
|
-
this.#store.error = coalescedError;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// auto-submit when all tool calls in the last assistant message have results
|
|
283
|
-
// and assistant has not answered yet
|
|
284
|
-
if (
|
|
285
|
-
shouldResubmitMessages({
|
|
286
|
-
originalMaxToolInvocationStep: maxStep,
|
|
287
|
-
originalMessageCount: messageCount,
|
|
288
|
-
maxSteps: this.#maxSteps,
|
|
289
|
-
messages: this.messages,
|
|
290
|
-
})
|
|
291
|
-
) {
|
|
292
|
-
await this.#triggerRequest({ messages: this.messages });
|
|
293
|
-
}
|
|
294
217
|
};
|
|
295
218
|
}
|
package/src/context-provider.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultChatStoreOptions, type ChatStore } from 'ai';
|
|
2
|
+
import { createChatStore, setChatStoreContext } from './chat-store.svelte.js';
|
|
2
3
|
import {
|
|
3
4
|
KeyedCompletionStore,
|
|
4
5
|
setCompletionContext,
|
|
@@ -8,9 +9,8 @@ import {
|
|
|
8
9
|
setStructuredObjectContext,
|
|
9
10
|
} from './structured-object-context.svelte.js';
|
|
10
11
|
|
|
11
|
-
export function createAIContext() {
|
|
12
|
-
|
|
13
|
-
setChatContext(chatStore);
|
|
12
|
+
export function createAIContext(chatStore?: ChatStore) {
|
|
13
|
+
createChatStoreContext(chatStore);
|
|
14
14
|
|
|
15
15
|
const completionStore = new KeyedCompletionStore();
|
|
16
16
|
setCompletionContext(completionStore);
|
|
@@ -18,3 +18,14 @@ export function createAIContext() {
|
|
|
18
18
|
const objectStore = new KeyedStructuredObjectStore();
|
|
19
19
|
setStructuredObjectContext(objectStore);
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
export function createChatStoreContext(chatStore?: ChatStore) {
|
|
23
|
+
setChatStoreContext(
|
|
24
|
+
chatStore ??
|
|
25
|
+
createChatStore(
|
|
26
|
+
defaultChatStoreOptions({
|
|
27
|
+
api: '/api/chat',
|
|
28
|
+
})(),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { createChatStore } from '../chat-store.svelte.js';
|
|
2
3
|
import { Chat } from '../chat.svelte.js';
|
|
4
|
+
import { defaultChatStoreOptions } from 'ai';
|
|
3
5
|
import { createAIContext } from '../context-provider.js';
|
|
4
6
|
|
|
5
7
|
let { chatId }: { chatId?: string } = $props();
|
|
6
8
|
|
|
7
|
-
createAIContext(
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
createAIContext(
|
|
10
|
+
createChatStore(defaultChatStoreOptions({ api: '/api/chat' })()),
|
|
11
|
+
);
|
|
12
|
+
const chat1 = new Chat(() => ({ chatId }));
|
|
13
|
+
const chat2 = new Chat(() => ({ chatId }));
|
|
10
14
|
|
|
11
15
|
export { chat1, chat2 };
|
|
12
16
|
</script>
|
package/src/utils.svelte.ts
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createContext, KeyedStore } from './utils.svelte.js';
|
|
2
|
-
class ChatStore {
|
|
3
|
-
messages = $state([]);
|
|
4
|
-
data = $state();
|
|
5
|
-
status = $state('ready');
|
|
6
|
-
error = $state();
|
|
7
|
-
}
|
|
8
|
-
export class KeyedChatStore extends KeyedStore {
|
|
9
|
-
constructor(value) {
|
|
10
|
-
super(ChatStore, value);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
export const { hasContext: hasChatContext, getContext: getChatContext, setContext: setChatContext, } = createContext('Chat');
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { JSONValue, UIMessage } from 'ai';
|
|
2
|
-
import { createContext, KeyedStore } from './utils.svelte.js';
|
|
3
|
-
|
|
4
|
-
class ChatStore<MESSAGE_METADATA = unknown> {
|
|
5
|
-
messages = $state<UIMessage<MESSAGE_METADATA>[]>([]);
|
|
6
|
-
data = $state<JSONValue[]>();
|
|
7
|
-
status = $state<'submitted' | 'streaming' | 'ready' | 'error'>('ready');
|
|
8
|
-
error = $state<Error>();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export class KeyedChatStore<MESSAGE_METADATA = unknown> extends KeyedStore<
|
|
12
|
-
ChatStore<MESSAGE_METADATA>
|
|
13
|
-
> {
|
|
14
|
-
constructor(
|
|
15
|
-
value?:
|
|
16
|
-
| Iterable<readonly [string, ChatStore<MESSAGE_METADATA>]>
|
|
17
|
-
| null
|
|
18
|
-
| undefined,
|
|
19
|
-
) {
|
|
20
|
-
super(ChatStore, value);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const {
|
|
25
|
-
hasContext: hasChatContext,
|
|
26
|
-
getContext: getChatContext,
|
|
27
|
-
setContext: setChatContext,
|
|
28
|
-
} = createContext<KeyedChatStore>('Chat');
|