@@ -9,34 +9,56 @@ import {
9
9
import { Icon } from "@iconify/react" ;
10
10
import DelayedTooltip from "../../../core/components/DelayedTooltip" ;
11
11
import { useConfigContext } from "../../../core/contexts/ConfigContext/useConfigContext" ;
12
- import { FormTheme , useTransformErrors } from "../../../core/forms" ;
12
+ import { FormTheme , transformErrors } from "../../../core/forms" ;
13
13
import validator from "@rjsf/validator-ajv8" ;
14
14
import { IChangeEvent } from "@rjsf/core" ;
15
- import { useEffect } from "react" ;
15
+ import { FormEvent , useEffect , useRef } from "react" ;
16
16
import { getDefaultBasedOnSchemaType } from "@rjsf/utils/lib/schema/getDefaultFormState" ;
17
17
import {
18
18
useConversationProperty ,
19
19
useHistoryActions ,
20
20
} from "../../../core/stores/HistoryStore/selectors" ;
21
21
import { useHistoryStore } from "../../../core/stores/HistoryStore/useHistoryStore" ;
22
+ import { useLocalStorage } from "usehooks-ts" ;
23
+ import { pluginManager } from "../../../core/utils/plugins/PluginManager" ;
24
+ import { ChatHistoryPlugin } from "../../ChatHistoryPlugin" ;
25
+ import { AnimationDefinition } from "framer-motion" ;
26
+
27
+ const CHAT_OPTIONS_KEY = "ragbits-no-history-chat-options" ;
22
28
23
29
export default function ChatOptionsForm ( ) {
24
30
const { isOpen, onOpen, onClose } = useDisclosure ( ) ;
25
31
const chatOptions = useConversationProperty ( ( s ) => s . chatOptions ) ;
32
+ // Needed to solve flicker to default settings when the modal is closing
33
+ const pendingSettingsRef = useRef < Record < string , unknown > | null > ( null ) ;
26
34
const { setChatOptions, initializeChatOptions } = useHistoryActions ( ) ;
27
35
const currentConversation = useHistoryStore ( ( s ) => s . currentConversation ) ;
28
36
const {
29
37
config : { user_settings : userSettings } ,
30
38
} = useConfigContext ( ) ;
39
+ const [ savedSettings , setSettings ] = useLocalStorage < Record <
40
+ string ,
41
+ unknown
42
+ > | null > ( CHAT_OPTIONS_KEY , null ) ;
31
43
32
44
const schema = userSettings ?. form ;
33
45
34
- const onOpenChange = ( ) => {
35
- onClose ( ) ;
46
+ const ensureSyncWithStorage = ( data : Record < string , unknown > ) => {
47
+ // Sync to localStorage only when history is disabled
48
+ if ( pluginManager . isPluginActivated ( ChatHistoryPlugin . name ) ) {
49
+ return ;
50
+ }
51
+
52
+ setSettings ( data ) ;
53
+ } ;
54
+
55
+ const onModalOpen = ( ) => {
56
+ onOpen ( ) ;
36
57
} ;
37
58
38
- const handleFormSubmit = ( data : IChangeEvent ) => {
39
- setChatOptions ( data . formData ) ;
59
+ const handleFormSubmit = ( data : IChangeEvent , event : FormEvent ) => {
60
+ event . preventDefault ( ) ;
61
+ pendingSettingsRef . current = data . formData ;
40
62
onClose ( ) ;
41
63
} ;
42
64
@@ -46,20 +68,49 @@ export default function ChatOptionsForm() {
46
68
}
47
69
48
70
const defaultState = getDefaultBasedOnSchemaType ( validator , schema ) ;
49
- setChatOptions ( defaultState ) ;
71
+ pendingSettingsRef . current = defaultState ;
50
72
onClose ( ) ;
51
73
} ;
52
74
53
- const transformErrors = useTransformErrors ( ) ;
75
+ const onOpenChange = ( ) => {
76
+ onClose ( ) ;
77
+ } ;
78
+
79
+ const onAnimationComplete = ( definition : AnimationDefinition ) => {
80
+ if ( definition !== "exit" || ! pendingSettingsRef . current ) {
81
+ return ;
82
+ }
83
+
84
+ const settings = pendingSettingsRef . current ;
85
+ setChatOptions ( settings ) ;
86
+ ensureSyncWithStorage ( settings ) ;
87
+ pendingSettingsRef . current = null ;
88
+ } ;
54
89
55
90
useEffect ( ( ) => {
56
91
if ( ! schema ) {
57
92
return ;
58
93
}
59
94
60
95
const defaultState = getDefaultBasedOnSchemaType ( validator , schema ) ;
61
- initializeChatOptions ( defaultState ) ;
62
- } , [ initializeChatOptions , schema , currentConversation ] ) ;
96
+ // When history is active, use default state for new conversations
97
+ if ( pluginManager . isPluginActivated ( ChatHistoryPlugin . name ) ) {
98
+ initializeChatOptions ( defaultState ) ;
99
+ // Otherwise if we have saved settings, use them
100
+ } else if ( savedSettings !== null ) {
101
+ initializeChatOptions ( savedSettings ) ;
102
+ // Otherwise just use defaults
103
+ } else {
104
+ initializeChatOptions ( defaultState ) ;
105
+ setSettings ( defaultState ) ;
106
+ }
107
+ } , [
108
+ initializeChatOptions ,
109
+ schema ,
110
+ currentConversation ,
111
+ savedSettings ,
112
+ setSettings ,
113
+ ] ) ;
63
114
64
115
if ( ! schema ) {
65
116
return null ;
@@ -73,62 +124,64 @@ export default function ChatOptionsForm() {
73
124
variant = "ghost"
74
125
className = "p-0"
75
126
aria-label = "Open chat options"
76
- onPress = { onOpen }
127
+ onPress = { onModalOpen }
77
128
data-testid = "open-chat-options"
78
129
>
79
130
< Icon icon = "heroicons:cog-6-tooth" />
80
131
</ Button >
81
132
</ DelayedTooltip >
82
133
83
- < Modal isOpen = { isOpen } onOpenChange = { onOpenChange } >
134
+ < Modal
135
+ isOpen = { isOpen }
136
+ onOpenChange = { onOpenChange }
137
+ motionProps = { {
138
+ onAnimationComplete,
139
+ } }
140
+ >
84
141
< ModalContent >
85
- { ( onClose ) => (
86
- < >
87
- < ModalHeader className = "text-default-900 flex flex-col gap-1" >
88
- { schema . title || "Chat Options" }
89
- </ ModalHeader >
90
- < ModalBody >
91
- < div className = "flex flex-col gap-4" >
92
- < FormTheme
93
- schema = { schema }
94
- validator = { validator }
95
- formData = { chatOptions }
96
- onSubmit = { handleFormSubmit }
97
- transformErrors = { transformErrors }
98
- liveValidate
142
+ < ModalHeader className = "text-default-900 flex flex-col gap-1" >
143
+ { schema . title || "Chat Options" }
144
+ </ ModalHeader >
145
+ < ModalBody >
146
+ < div className = "flex flex-col gap-4" >
147
+ < FormTheme
148
+ schema = { schema }
149
+ validator = { validator }
150
+ formData = { chatOptions }
151
+ onSubmit = { handleFormSubmit }
152
+ transformErrors = { transformErrors }
153
+ liveValidate
154
+ >
155
+ < div className = "flex justify-end gap-4 py-4" >
156
+ < Button
157
+ className = "mr-auto"
158
+ color = "primary"
159
+ variant = "light"
160
+ onPress = { onRestoreDefaults }
161
+ aria-label = "Restore default user settings"
162
+ >
163
+ Restore defaults
164
+ </ Button >
165
+ < Button
166
+ color = "danger"
167
+ variant = "light"
168
+ onPress = { onClose }
169
+ aria-label = "Close chat options form"
170
+ >
171
+ Cancel
172
+ </ Button >
173
+ < Button
174
+ color = "primary"
175
+ type = "submit"
176
+ aria-label = "Save chat options"
177
+ data-testid = "chat-options-submit"
99
178
>
100
- < div className = "flex justify-end gap-4 py-4" >
101
- < Button
102
- className = "mr-auto"
103
- color = "primary"
104
- variant = "light"
105
- onPress = { onRestoreDefaults }
106
- aria-label = "Restore default user settings"
107
- >
108
- Restore defaults
109
- </ Button >
110
- < Button
111
- color = "danger"
112
- variant = "light"
113
- onPress = { onClose }
114
- aria-label = "Close chat options form"
115
- >
116
- Cancel
117
- </ Button >
118
- < Button
119
- color = "primary"
120
- type = "submit"
121
- aria-label = "Save chat options"
122
- data-testid = "chat-options-submit"
123
- >
124
- Save
125
- </ Button >
126
- </ div >
127
- </ FormTheme >
179
+ Save
180
+ </ Button >
128
181
</ div >
129
- </ ModalBody >
130
- </ >
131
- ) }
182
+ </ FormTheme >
183
+ </ div >
184
+ </ ModalBody >
132
185
</ ModalContent >
133
186
</ Modal >
134
187
</ >
0 commit comments