1
1
import { useCallback , useEffect , useState } from "react" ;
2
2
import type { InputOptions } from "./backend" ;
3
- import type { Backend , Events , EventTypes , MessageParam } from "./types" ;
3
+ import type {
4
+ Backend ,
5
+ Events ,
6
+ EventTypes ,
7
+ MessageParam ,
8
+ Nullable ,
9
+ } from "./types" ;
4
10
5
- export function useChat (
6
- backend : Backend ,
7
- initialMessages : MessageParam [ ] = [ ] ,
8
- ) : {
11
+ /**
12
+ * Options for `useChat`.
13
+ */
14
+ export interface UseChatOptions {
15
+ /**
16
+ * Whether to throw an error when the backend is nullish.
17
+ * @description
18
+ * If `true`, an error will be thrown when the backend is nullish.
19
+ * The error will be thrown when calling `input` or `on` callback,
20
+ * but not thrown immediately.
21
+ * @default false
22
+ */
23
+ throwOnEmptyBackend ?: boolean ;
24
+ }
25
+
26
+ export interface UseChatReturn {
9
27
messages : MessageParam [ ] ;
10
28
input : ( prompt : string , options ?: InputOptions ) => Promise < void > ;
11
- on : < K extends EventTypes [ "type" ] > ( type : K , handler : Events [ K ] ) => ( ) => void ;
29
+ on : < K extends EventTypes [ "type" ] > (
30
+ type : K ,
31
+ handler : Events [ K ] ,
32
+ ) => Nullable < ( ) => void > ;
12
33
setMessages : React . Dispatch < React . SetStateAction < MessageParam [ ] > > ;
13
- isPending : boolean ;
14
- } {
34
+ pending : boolean ;
35
+ }
36
+
37
+ export function useChat (
38
+ backend ?: Backend ,
39
+ initialMessages : MessageParam [ ] = [ ] ,
40
+ options : UseChatOptions = { } ,
41
+ ) : UseChatReturn {
15
42
const [ messages , setMessages ] = useState < MessageParam [ ] > ( initialMessages ) ;
16
- const [ isPending , setIsPending ] = useState ( false ) ;
43
+ const [ pending , setPending ] = useState ( false ) ;
17
44
18
45
const input = useCallback (
19
- async ( prompt : string , options ?: InputOptions ) => {
20
- setIsPending ( true ) ;
21
- return backend . input ( prompt , {
46
+ async ( prompt : string , inputOptions ?: InputOptions ) => {
47
+ if ( ! backend && options . throwOnEmptyBackend ) {
48
+ throw new Error ( "Backend is not initialized" ) ;
49
+ }
50
+ return backend ?. input ( prompt , {
22
51
messages,
23
- ...options ,
52
+ ...inputOptions ,
24
53
} ) ;
25
54
} ,
26
- [ backend , messages ] ,
55
+ [ backend , messages , options . throwOnEmptyBackend ] ,
27
56
) ;
28
57
29
58
const on = useCallback (
30
- < K extends EventTypes [ "type" ] > (
31
- type : K ,
32
- handler : Events [ K ] ,
33
- ) : ( ( ) => void ) => {
34
- return backend . on ( type , handler ) ;
59
+ < K extends EventTypes [ "type" ] > ( type : K , handler : Events [ K ] ) => {
60
+ if ( ! backend && options . throwOnEmptyBackend ) {
61
+ throw new Error ( "Backend is not initialized" ) ;
62
+ }
63
+ return backend ? .on ( type , handler ) ;
35
64
} ,
36
- [ backend ] ,
65
+ [ backend , options , options . throwOnEmptyBackend ] ,
37
66
) ;
38
67
39
68
useEffect ( ( ) => {
40
- const cleanCbs : ( ( ) => void ) [ ] = [
41
- backend . on ( "input" , ( event ) => {
42
- setMessages ( ( prevMessages ) => [
43
- ...prevMessages ,
44
- {
45
- id : event . id ,
46
- role : "user" ,
47
- name : "User" ,
48
- content : event . payload . prompt ,
49
- avatar : {
50
- text : "U" ,
51
- } ,
52
- align : "right" ,
53
- } ,
54
- ] ) ;
55
- } ) ,
56
- backend . on ( "message" , ( event ) => {
57
- setMessages ( ( prevMessages ) => [ ...prevMessages , event . payload ] ) ;
58
- } ) ,
59
- backend . on ( "error" , ( event ) => {
60
- setIsPending ( false ) ;
61
- console . error ( "Error from backend:" , event . payload . error ) ;
62
- setMessages ( ( prevMessages ) => [
63
- ...prevMessages ,
64
- {
65
- id : event . id ,
66
- role : "system" ,
67
- name : "Error" ,
68
- content : event . payload . error ,
69
- align : "center" ,
70
- } ,
71
- ] ) ;
72
- } ) ,
73
- backend . on ( "finish" , ( ) => {
74
- setIsPending ( false ) ;
75
- } ) ,
76
- backend . on ( "chunk" , ( event ) => {
77
- setIsPending ( false ) ;
78
- setMessages ( ( prev ) => {
79
- const lastMessage = prev [ prev . length - 1 ] ;
80
- if ( lastMessage && lastMessage . role === "assistant" ) {
81
- return [
82
- ...prev . slice ( 0 , - 1 ) ,
69
+ const cleanCbs : ( ( ) => void ) [ ] = backend
70
+ ? [
71
+ backend . on ( "input" , ( event ) => {
72
+ setPending ( true ) ;
73
+ setMessages ( ( prevMessages ) => [
74
+ ...prevMessages ,
83
75
{
84
- ...lastMessage ,
85
- content : lastMessage . content + event . payload . chunk ,
76
+ id : event . id ,
77
+ role : "user" ,
78
+ name : "User" ,
79
+ content : event . payload . prompt ,
80
+ align : "right" ,
86
81
} ,
87
- ] ;
88
- }
89
- return [
90
- ...prev ,
91
- {
92
- id : event . id ,
93
- role : "assistant" ,
94
- content : event . payload . chunk ,
95
- avatar : {
96
- text : "A" ,
82
+ ] ) ;
83
+ } ) ,
84
+ backend . on ( "message" , ( event ) => {
85
+ setMessages ( ( prevMessages ) => [ ...prevMessages , event . payload ] ) ;
86
+ } ) ,
87
+ backend . on ( "error" , ( event ) => {
88
+ setPending ( false ) ;
89
+ console . error ( "Error from backend:" , event . payload . error ) ;
90
+ setMessages ( ( prevMessages ) => [
91
+ ...prevMessages ,
92
+ {
93
+ id : event . id ,
94
+ role : "system" ,
95
+ name : "Error" ,
96
+ content : event . payload . error ,
97
+ align : "center" ,
97
98
} ,
98
- align : "left" ,
99
- } ,
100
- ] ;
101
- } ) ;
102
- } ) ,
103
- ] ;
99
+ ] ) ;
100
+ } ) ,
101
+ backend . on ( "finish" , ( ) => {
102
+ setPending ( false ) ;
103
+ } ) ,
104
+ backend . on ( "chunk" , ( event ) => {
105
+ setPending ( false ) ;
106
+ setMessages ( ( prev ) => {
107
+ const lastMessage = prev [ prev . length - 1 ] ;
108
+ if ( lastMessage && lastMessage . role === "assistant" ) {
109
+ return [
110
+ ...prev . slice ( 0 , - 1 ) ,
111
+ {
112
+ ...lastMessage ,
113
+ content : lastMessage . content + event . payload . chunk ,
114
+ } ,
115
+ ] ;
116
+ }
117
+ return [
118
+ ...prev ,
119
+ {
120
+ id : event . id ,
121
+ role : "assistant" ,
122
+ content : event . payload . chunk ,
123
+ align : "left" ,
124
+ } ,
125
+ ] ;
126
+ } ) ;
127
+ } ) ,
128
+ ]
129
+ : [ ] ;
104
130
return ( ) => {
105
131
for ( const cb of cleanCbs ) {
106
132
cb ( ) ;
@@ -109,5 +135,5 @@ export function useChat(
109
135
} ;
110
136
} , [ backend ] ) ;
111
137
112
- return { messages, input, on, setMessages, isPending } ;
138
+ return { messages, input, on, setMessages, pending } ;
113
139
}
0 commit comments