1
1
import { useCallback , useContext } from 'react' ;
2
- import { useRecoilValue } from 'recoil' ;
2
+ import { useRecoilState , useRecoilValue } from 'recoil' ;
3
3
import { v4 as uuidv4 } from 'uuid' ;
4
4
5
5
import {
@@ -8,40 +8,125 @@ import {
8
8
IStep ,
9
9
useAuth ,
10
10
useChatData ,
11
- useChatInteract
11
+ useChatInteract ,
12
+ commandsState ,
13
+ toggleCommandsState
12
14
} from '@chainlit/react-client' ;
13
15
16
+ import Icon from '@/components/Icon' ;
14
17
import { Button } from '@/components/ui/button' ;
15
18
16
- import { persistentCommandState } from '@/state/chat' ;
19
+ import { IToggleable , persistentCommandState , toggleableStates } from '@/state/chat' ;
17
20
18
21
interface StarterProps {
19
22
starter : IStarter ;
20
23
}
21
24
22
25
export default function Starter ( { starter } : StarterProps ) {
23
26
const apiClient = useContext ( ChainlitContext ) ;
24
- const selectedCommand = useRecoilValue ( persistentCommandState ) ;
27
+ const [ selectedCommand , setSelectedCommand ] = useRecoilState ( persistentCommandState ) ;
28
+ const [ toggleables , setToggleables ] = useRecoilState ( toggleableStates ) ;
25
29
const { sendMessage } = useChatInteract ( ) ;
26
30
const { loading, connected } = useChatData ( ) ;
27
31
const { user } = useAuth ( ) ;
32
+ const commands = useRecoilValue ( commandsState ) ;
33
+ const toggleCommands = useRecoilValue ( toggleCommandsState ) ;
28
34
29
35
const disabled = loading || ! connected ;
30
36
31
37
const onSubmit = useCallback ( async ( ) => {
38
+ console . log ( "点击Starter,当前toggle状态:" , toggleables ) ;
39
+
40
+ // 处理命令激活
41
+ if ( starter . commands && starter . commands . length > 0 ) {
42
+ const commandToActivate = commands . find ( cmd => cmd . id === starter . commands ! [ 0 ] ) ;
43
+ if ( commandToActivate ) {
44
+ setSelectedCommand ( commandToActivate ) ;
45
+ }
46
+ } else {
47
+ // 如果starter没有指定commands,则清除当前选中的command
48
+ setSelectedCommand ( undefined ) ;
49
+ }
50
+
51
+ // 处理可切换命令激活
52
+ let newToggleables : IToggleable [ ] = [ ] ;
53
+
54
+ // 根据starter.toggle_commands处理toggleables
55
+ if ( starter . toggle_commands ) {
56
+ if ( starter . toggle_commands . length > 0 ) {
57
+ // 如果有指定的toggle_commands,先保留所有持久化的toggleables
58
+ const persistentToggles = toggleables . filter ( t => t . persistent ) ;
59
+ newToggleables . push ( ...persistentToggles ) ;
60
+
61
+ // 添加starter中指定的toggle_commands,确保它们被激活
62
+ starter . toggle_commands . forEach ( cmdId => {
63
+ const toggleCmd = toggleCommands . find ( cmd => cmd . id === cmdId ) ;
64
+ if ( toggleCmd ) {
65
+ // 检查是否已经存在于新的toggleables中
66
+ const existingIndex = newToggleables . findIndex ( t => t . id === cmdId ) ;
67
+
68
+ if ( existingIndex >= 0 ) {
69
+ // 如果已存在,确保它是激活状态
70
+ newToggleables [ existingIndex ] = {
71
+ ...newToggleables [ existingIndex ] ,
72
+ active : true
73
+ } ;
74
+ } else {
75
+ // 如果不存在,添加新的toggleable
76
+ newToggleables . push ( {
77
+ id : toggleCmd . id ,
78
+ active : true ,
79
+ persistent : toggleCmd . persistent
80
+ } ) ;
81
+ }
82
+ }
83
+ } ) ;
84
+ } else {
85
+ // 如果toggle_commands是空数组,保留所有toggleables但设置为非激活状态
86
+ newToggleables = toggleables . map ( toggle => ( {
87
+ ...toggle ,
88
+ active : false
89
+ } ) ) ;
90
+ console . log ( "重置所有toggleables为非激活状态:" , newToggleables ) ;
91
+ }
92
+ } else {
93
+ // 如果toggle_commands不存在,保留所有持久化的toggleables
94
+ const persistentToggles = toggleables . filter ( t => t . persistent ) ;
95
+ newToggleables . push ( ...persistentToggles ) ;
96
+ }
97
+
98
+ console . log ( "即将设置的新toggle状态:" , newToggleables ) ;
99
+
100
+ // 直接构造消息对象并发送,不等待状态更新
32
101
const message : IStep = {
33
102
threadId : '' ,
34
103
id : uuidv4 ( ) ,
35
- command : selectedCommand ?. id ,
104
+ command : starter . commands && starter . commands . length > 0 ? starter . commands [ 0 ] : undefined ,
105
+ toggleables : starter . toggle_commands && starter . toggle_commands . length > 0
106
+ ? starter . toggle_commands
107
+ : [ ] ,
36
108
name : user ?. identifier || 'User' ,
37
109
type : 'user_message' ,
38
110
output : starter . message ,
39
111
createdAt : new Date ( ) . toISOString ( ) ,
40
112
metadata : { location : window . location . href }
41
113
} ;
42
114
115
+ // 发送消息
43
116
sendMessage ( message , [ ] ) ;
44
- } , [ user , selectedCommand , sendMessage , starter ] ) ;
117
+
118
+ // 然后更新UI状态,这样不会影响消息发送
119
+ setToggleables ( newToggleables ) ;
120
+
121
+ } , [ user , selectedCommand , toggleables , commands , toggleCommands , sendMessage , starter , setSelectedCommand , setToggleables ] ) ;
122
+
123
+ // 检查是否是图片URL
124
+ const isImageUrl = ( url : string ) : boolean => {
125
+ return url . startsWith ( 'http' ) ||
126
+ url . startsWith ( '/' ) ||
127
+ url . startsWith ( './' ) ||
128
+ url . startsWith ( '../' ) ;
129
+ } ;
45
130
46
131
return (
47
132
< Button
@@ -53,15 +138,19 @@ export default function Starter({ starter }: StarterProps) {
53
138
>
54
139
< div className = "flex gap-2" >
55
140
{ starter . icon ? (
56
- < img
57
- className = "h-5 w-5 rounded-md"
58
- src = {
59
- starter . icon ?. startsWith ( '/public' )
60
- ? apiClient . buildEndpoint ( starter . icon )
61
- : starter . icon
62
- }
63
- alt = { starter . label }
64
- />
141
+ isImageUrl ( starter . icon ) ? (
142
+ < img
143
+ className = "h-5 w-5 rounded-md"
144
+ src = {
145
+ starter . icon ?. startsWith ( '/public' )
146
+ ? apiClient . buildEndpoint ( starter . icon )
147
+ : starter . icon
148
+ }
149
+ alt = { starter . label }
150
+ />
151
+ ) : (
152
+ < Icon name = { starter . icon } className = "!h-5 !w-5" />
153
+ )
65
154
) : null }
66
155
< p className = "text-sm text-muted-foreground truncate" >
67
156
{ starter . label }
0 commit comments