Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
L
Liriae Viewer
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
pub
ecolab
LIRIAe
Liriae Viewer
Commits
8a242b6d
Commit
8a242b6d
authored
3 months ago
by
Andréas Livet
Browse files
Options
Downloads
Patches
Plain Diff
Various chat improvements
Avoid loosing chat state when switching tabs Handle response abort
parent
a955e858
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/components/SimpleChat.tsx
+104
-86
104 additions, 86 deletions
src/components/SimpleChat.tsx
src/components/TabComponent.tsx
+20
-6
20 additions, 6 deletions
src/components/TabComponent.tsx
with
124 additions
and
92 deletions
src/components/SimpleChat.tsx
+
104
−
86
View file @
8a242b6d
...
...
@@ -15,6 +15,7 @@ import AlertContext from "@/shared/contexts/AlertContext";
import
Markdown
from
"
react-markdown
"
;
import
{
capitalize
}
from
"
lodash
"
;
import
{
Link
}
from
"
react-router-dom
"
;
import
{
CustomAbortDOMException
}
from
"
@/chat/exceptions/CustomAbortDOMException
"
;
type
ChatResponse
=
{
query
:
string
;
...
...
@@ -42,19 +43,21 @@ export const SimpleChat = () => {
const
[
currentChatResponse
,
setCurrentChatResponse
]
=
useState
<
ChatResponse
|
null
>
(
null
);
const
[
inputValue
,
setInputValue
]
=
useState
(
""
);
// We'll use this controller to cancel the user prompt request
const
[
chatResponseController
,
setChatResponseController
]
=
useState
<
AbortController
|
null
>
(
null
);
const
currentQueryRef
=
useRef
<
ChatQuery
|
null
>
(
null
);
const
handleCancelSubmission
=
()
=>
{
// formSubmitController?.abort(
// new CustomAbortDOMException(
// "La génération de réponse a été interrompue.",
// "Arrêt",
// "info",
// "formSubmitCancelButton"
// )
// );
// Fixme: maybe send negative feedback to API when the request is aborted
chatResponseController
?.
abort
(
new
CustomAbortDOMException
(
"
La génération de réponse a été interrompue.
"
,
"
Arrêt
"
,
"
info
"
,
"
formSubmitCancelButton
"
)
);
};
const
createQuery
=
async
(
...
...
@@ -92,9 +95,6 @@ export const SimpleChat = () => {
};
const
handleSubmitForm
=
async
()
=>
{
// Right after form submission, we reset the message field
// setValue("query", "");
// We resize the message input to its original size
// if (inputMessageRef.current) {
// inputMessageRef.current.style.height = "auto";
...
...
@@ -120,6 +120,9 @@ export const SimpleChat = () => {
try
{
setIsChatRequestRunning
(
true
);
// We'll use the AbortController to cancel the response if neede
const
abortController
=
new
AbortController
();
setChatResponseController
(
abortController
);
// We can now send the complete user prompt request to our API
const
response
=
await
fetch
(
forgeUrl
(
`/chat/query/
${
currentQueryRef
.
current
.
id
}
/message`
),
...
...
@@ -129,6 +132,7 @@ export const SimpleChat = () => {
query
:
chatResponse
.
query
,
collection_ids
:
[
currentCollection
!
.
id
],
}),
signal
:
abortController
.
signal
,
// signal: formSubmitAbortController.signal,
headers
:
{
Accept
:
"
text/event-stream
"
,
...
...
@@ -161,7 +165,7 @@ export const SimpleChat = () => {
setIsSubmitting
(
false
);
const
query
=
currentQueryRef
.
current
;
if
(
query
)
{
query
.
history
=
currentChatResponse
;
query
.
history
=
[
currentChatResponse
]
;
fetch
(
forgeUrl
(
`/chat/query/
${
query
.
id
}
`
),
{
headers
:
{
Authorization
:
accessToken
!
,
...
...
@@ -388,7 +392,6 @@ export const SimpleChat = () => {
{
capitalize
(
currentChatResponse
.
errorMessage
)
}
</
div
>
)
}
{
currentChatResponse
?.
errorMessage
}
</>
)
}
</
div
>
...
...
@@ -400,80 +403,95 @@ export const SimpleChat = () => {
</
div
>
{
/* Message input */
}
<
div
className
=
{
classes
.
inputContainer
}
>
{
/* Message input */
}
<
div
>
<
Input
label
=
{
null
}
disabled
=
{
!
(
isProcessingServiceAvailable
&&
isDocumentsProcessingStatusOk
)
||
isSubmitting
}
textArea
className
=
{
cx
(
classes
.
input
)
}
// state={errors?.message && 'error'}
// stateRelatedMessage={errors?.message?.message}
nativeTextAreaProps
=
{
{
placeholder
:
getPromptPlaceholderText
(),
rows
:
1
,
// onChange: (e) => {
// if (inputMessageRef.current) {
// inputMessageRef.current.style.height = "auto";
// inputMessageRef.current.style.height = `${e.target.scrollHeight}px`;
// }
// },
onChange
:
(
e
)
=>
{
setInputValue
(
e
.
target
.
value
);
},
onKeyPress
:
(
e
)
=>
{
// We'll submit the form when pressing 'Enter' without the 'Shift' key
if
(
e
.
key
===
"
Enter
"
&&
!
e
.
shiftKey
)
{
setIsSubmitting
(
!
isSubmitting
);
handleOnSubmit
();
{
!
currentChatResponse
||
isSubmitting
?
(
<>
{
/* Message input */
}
<
div
>
<
Input
label
=
{
null
}
disabled
=
{
!
(
isProcessingServiceAvailable
&&
isDocumentsProcessingStatusOk
)
||
isSubmitting
}
},
value
:
inputValue
,
}
}
/>
</
div
>
textArea
className
=
{
cx
(
classes
.
input
)
}
// state={errors?.message && 'error'}
// stateRelatedMessage={errors?.message?.message}
nativeTextAreaProps
=
{
{
placeholder
:
getPromptPlaceholderText
(),
rows
:
1
,
// onChange: (e) => {
// if (inputMessageRef.current) {
// inputMessageRef.current.style.height = "auto";
// inputMessageRef.current.style.height = `${e.target.scrollHeight}px`;
// }
// },
onChange
:
(
e
)
=>
{
setInputValue
(
e
.
target
.
value
);
},
onKeyPress
:
(
e
)
=>
{
// We'll submit the form when pressing 'Enter' without the 'Shift' key
if
(
e
.
key
===
"
Enter
"
&&
!
e
.
shiftKey
)
{
setIsSubmitting
(
!
isSubmitting
);
handleOnSubmit
();
}
},
value
:
inputValue
,
}
}
/>
</
div
>
{
/* Send message button */
}
<
div
className
=
{
cx
(
classes
.
inputButtonContainer
,
classes
.
sendMessageButtonContainer
)
}
>
{
/* Send message loading spinner */
}
<
ActionButton
isLoading
=
{
isSubmitting
}
sx
=
{
{
color
:
fr
.
colors
.
decisions
.
background
.
default
.
grey
.
default
,
}
}
/>
{
/* Send message icon button */
}
<
Button
iconId
=
{
isSubmitting
?
"
fr-icon-stop-circle-fill
"
:
"
fr-icon-send-plane-fill
"
}
disabled
=
{
!
isProcessingServiceAvailable
||
!
isDocumentsProcessingStatusOk
}
className
=
{
cx
(
classes
.
inputIcon
,
classes
.
sendMessageIcon
)
}
title
=
{
isSubmitting
?
"
Arrêt de l'envoi du message
"
:
"
Envoi du message
"
}
onClick
=
{
()
=>
{
setIsSubmitting
(
!
isSubmitting
);
handleOnSubmit
();
}
}
/>
</
div
>
{
/* Send message button */
}
<
div
className
=
{
cx
(
classes
.
inputButtonContainer
,
classes
.
sendMessageButtonContainer
)
}
>
{
/* Send message loading spinner */
}
<
ActionButton
isLoading
=
{
isSubmitting
}
sx
=
{
{
color
:
fr
.
colors
.
decisions
.
background
.
default
.
grey
.
default
,
}
}
/>
{
/* Send message icon button */
}
<
Button
iconId
=
{
isSubmitting
?
"
fr-icon-stop-circle-fill
"
:
"
fr-icon-send-plane-fill
"
}
disabled
=
{
!
isProcessingServiceAvailable
||
!
isDocumentsProcessingStatusOk
}
className
=
{
cx
(
classes
.
inputIcon
,
classes
.
sendMessageIcon
)
}
title
=
{
isSubmitting
?
"
Arrêt de l'envoi du message
"
:
"
Envoi du message
"
}
onClick
=
{
()
=>
{
setIsSubmitting
(
!
isSubmitting
);
handleOnSubmit
();
}
}
/>
</
div
>
</>
)
:
(
<>
<
Button
iconId
=
"fr-icon-add-circle-line"
onClick
=
{
()
=>
setCurrentChatResponse
(
null
)
}
>
Nouvelle conversation
</
Button
>
</>
)
}
</
div
>
</
div
>
</
div
>
...
...
This diff is collapsed.
Click to expand it.
src/components/TabComponent.tsx
+
20
−
6
View file @
8a242b6d
import
React
,
{
ReactElement
,
useState
}
from
"
react
"
;
import
React
,
{
ReactElement
,
useEffect
,
useState
}
from
"
react
"
;
import
{
BrowserRouter
as
Router
,
Route
,
...
...
@@ -27,6 +27,14 @@ export const TabComponent = ({ tabs }: { tabs: Tab[] }) => {
setActiveTabIndex
(
index
);
};
// useEffect(() => {
// for (const [index, tab] of tabs.entries()) {
// if (tab.isActive && activeTabIndex !== index) {
// setActiveTabIndex(index);
// }
// }
// }, [tabs]);
return
(
<
div
>
<
nav
className
=
"fr-nav"
role
=
"navigation"
data-fr-js-navigation
=
"true"
>
...
...
@@ -48,7 +56,7 @@ export const TabComponent = ({ tabs }: { tabs: Tab[] }) => {
handleTabClick
(
index
);
}
}
data-discover
=
"true"
aria-current
=
{
tab
.
isActive
&&
"
page
"
}
aria-current
=
{
activeTabIndex
===
index
&&
"
page
"
}
>
{
tab
.
text
}
</
a
>
...
...
@@ -56,10 +64,16 @@ export const TabComponent = ({ tabs }: { tabs: Tab[] }) => {
))
}
</
ul
>
</
nav
>
<
div
className
=
{
styles
.
tabContent
}
>
{
tabs
[
activeTabIndex
].
content
}
</
div
>
{
/* Avoid loosing React state of the components */
}
{
tabs
.
map
((
tab
,
index
)
=>
(
<
div
key
=
{
index
}
className
=
{
styles
.
tabContent
}
style
=
{
{
display
:
activeTabIndex
===
index
?
"
block
"
:
"
none
"
}
}
>
{
tab
.
content
}
</
div
>
))
}
</
div
>
);
};
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment