66import { IDocumentManager } from '@jupyterlab/docmanager' ;
77import {
88 Autocomplete ,
9+ AutocompleteInputChangeReason ,
910 Box ,
1011 InputAdornment ,
1112 SxProps ,
@@ -17,33 +18,27 @@ import React, { useEffect, useRef, useState } from 'react';
1718
1819import { AttachmentPreviewList } from './attachments' ;
1920import { AttachButton , CancelButton , SendButton } from './input' ;
20- import { IChatModel } from '../model' ;
21+ import { IInputModel , InputModel } from '../input- model' ;
2122import { IAutocompletionRegistry } from '../registry' ;
22- import { IAttachment , IConfig , Selection } from '../types' ;
23+ import { IAttachment , Selection } from '../types' ;
2324import { useChatCommands } from './input/use-chat-commands' ;
2425import { IChatCommandRegistry } from '../chat-commands' ;
2526
2627const INPUT_BOX_CLASS = 'jp-chat-input-container' ;
2728
2829export function ChatInput ( props : ChatInput . IProps ) : JSX . Element {
2930 const { documentManager, model } = props ;
30- const [ input , setInput ] = useState < string > ( props . value || '' ) ;
31+ const [ input , setInput ] = useState < string > ( model . value ) ;
3132 const inputRef = useRef < HTMLInputElement > ( ) ;
3233
33- const chatCommands = useChatCommands (
34- input ,
35- setInput ,
36- inputRef ,
37- props . chatCommandRegistry
38- ) ;
34+ const chatCommands = useChatCommands ( model , props . chatCommandRegistry ) ;
3935
4036 const [ sendWithShiftEnter , setSendWithShiftEnter ] = useState < boolean > (
4137 model . config . sendWithShiftEnter ?? false
4238 ) ;
43- const [ typingNotification , setTypingNotification ] = useState < boolean > (
44- model . config . sendTypingNotification ?? false
39+ const [ attachments , setAttachments ] = useState < IAttachment [ ] > (
40+ model . attachments
4541 ) ;
46- const [ attachments , setAttachments ] = useState < IAttachment [ ] > ( [ ] ) ;
4742
4843 // Display the include selection menu if it is not explicitly hidden, and if at least
4944 // one of the tool to check for text or cell selection is enabled.
@@ -53,9 +48,13 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
5348 }
5449
5550 useEffect ( ( ) => {
56- const configChanged = ( _ : IChatModel , config : IConfig ) => {
51+ const inputChanged = ( _ : IInputModel , value : string ) => {
52+ setInput ( value ) ;
53+ } ;
54+ model . valueChanged . connect ( inputChanged ) ;
55+
56+ const configChanged = ( _ : IInputModel , config : InputModel . IConfig ) => {
5757 setSendWithShiftEnter ( config . sendWithShiftEnter ?? false ) ;
58- setTypingNotification ( config . sendTypingNotification ?? false ) ;
5958 } ;
6059 model . configChanged . connect ( configChanged ) ;
6160
@@ -66,15 +65,15 @@ export function ChatInput(props: ChatInput.IProps): JSX.Element {
6665 } ;
6766 model . focusInputSignal ?. connect ( focusInputElement ) ;
6867
69- const attachmentChanged = ( _ : IChatModel , attachments : IAttachment [ ] ) => {
68+ const attachmentChanged = ( _ : IInputModel , attachments : IAttachment [ ] ) => {
7069 setAttachments ( [ ...attachments ] ) ;
7170 } ;
72- model . inputAttachmentsChanged ?. connect ( attachmentChanged ) ;
71+ model . attachmentsChanged ?. connect ( attachmentChanged ) ;
7372
7473 return ( ) => {
7574 model . configChanged ?. disconnect ( configChanged ) ;
7675 model . focusInputSignal ?. disconnect ( focusInputElement ) ;
77- model . inputAttachmentsChanged ?. disconnect ( attachmentChanged ) ;
76+ model . attachmentsChanged ?. disconnect ( attachmentChanged ) ;
7877 } ;
7978 } , [ model ] ) ;
8079
@@ -160,15 +159,14 @@ ${selection.source}
160159` ;
161160 }
162161 props . onSend ( content ) ;
163- setInput ( '' ) ;
162+ model . value = '' ;
164163 }
165164
166165 /**
167166 * Triggered when cancelling edition.
168167 */
169168 function onCancel ( ) {
170- setInput ( props . value || '' ) ;
171- props . onCancel ! ( ) ;
169+ props . onCancel ?.( ) ;
172170 }
173171
174172 // Set the helper text based on whether Shift+Enter is used for sending.
@@ -218,6 +216,9 @@ ${selection.source}
218216 placeholder = "Start chatting"
219217 inputRef = { inputRef }
220218 sx = { { marginTop : '1px' } }
219+ onSelect = { ( ) =>
220+ ( model . cursorIndex = inputRef . current ?. selectionStart ?? null )
221+ }
221222 InputProps = { {
222223 ...params . InputProps ,
223224 endAdornment : (
@@ -247,10 +248,16 @@ ${selection.source}
247248 />
248249 ) }
249250 inputValue = { input }
250- onInputChange = { ( _ , newValue : string ) => {
251- setInput ( newValue ) ;
252- if ( typingNotification && model . inputChanged ) {
253- model . inputChanged ( newValue ) ;
251+ onInputChange = { (
252+ _ ,
253+ newValue : string ,
254+ reason : AutocompleteInputChangeReason
255+ ) => {
256+ // Do not update the value if the reason is 'reset', which should occur only
257+ // if an autocompletion command has been selected. In this case, the value is
258+ // set in the 'onChange()' callback of the autocompletion (to avoid conflicts).
259+ if ( reason !== 'reset' ) {
260+ model . value = newValue ;
254261 }
255262 } }
256263 />
@@ -269,11 +276,7 @@ export namespace ChatInput {
269276 /**
270277 * The chat model.
271278 */
272- model : IChatModel ;
273- /**
274- * The initial value of the input (default to '')
275- */
276- value ?: string ;
279+ model : IInputModel ;
277280 /**
278281 * The function to be called to send the message.
279282 */
0 commit comments