2
2
import { tcls } from '@/lib/tailwind' ;
3
3
import { filterOutNullable } from '@/lib/typescript' ;
4
4
import { Icon } from '@gitbook/icons' ;
5
- import { motion } from 'framer-motion' ;
6
- import { useEffect , useState } from 'react' ;
5
+ import { useEffect , useRef , useState } from 'react' ;
7
6
import { useVisitedPages } from '../Insights/useVisitedPages' ;
8
7
import { Button } from '../primitives' ;
9
8
import { isQuestion } from './isQuestion' ;
10
9
import { streamAISearchAnswer , streamAISearchSummary } from './server-actions' ;
10
+ import { useSearch } from './useSearch' ;
11
11
12
12
export function SearchChat ( props : { query : string } ) {
13
13
// const currentPage = usePageContext();
@@ -18,11 +18,15 @@ export function SearchChat(props: { query: string }) {
18
18
const visitedPages = useVisitedPages ( ( state ) => state . pages ) ;
19
19
const [ summary , setSummary ] = useState ( '' ) ;
20
20
const [ messages , setMessages ] = useState <
21
- { role : string ; content ?: string ; fetching ?: boolean } [ ]
21
+ { role : string ; content ?: string ; context ?: string ; fetching ?: boolean } [ ]
22
22
> ( [ ] ) ;
23
23
const [ followupQuestions , setFollowupQuestions ] = useState < string [ ] > ( ) ;
24
24
25
25
const [ responseId , setResponseId ] = useState < string | null > ( null ) ;
26
+ const [ searchState , setSearchState ] = useSearch ( ) ;
27
+
28
+ const containerRef = useRef < HTMLDivElement > ( null ) ;
29
+ const latestMessageRef = useRef < HTMLDivElement > ( null ) ;
26
30
27
31
useEffect ( ( ) => {
28
32
let cancelled = false ;
@@ -55,12 +59,9 @@ export function SearchChat(props: { query: string }) {
55
59
56
60
if ( query ) {
57
61
setMessages ( [
58
- {
59
- role : 'user' ,
60
- content : query ,
61
- } ,
62
62
{
63
63
role : 'assistant' ,
64
+ context : `You asked ${ isQuestion ( query ) ? '' : 'about' } "${ query } "` ,
64
65
fetching : true ,
65
66
} ,
66
67
] ) ;
@@ -97,8 +98,50 @@ export function SearchChat(props: { query: string }) {
97
98
}
98
99
} , [ query , responseId ] ) ;
99
100
101
+ useEffect ( ( ) => {
102
+ if ( latestMessageRef . current ) {
103
+ latestMessageRef . current . scrollIntoView ( {
104
+ behavior : 'smooth' ,
105
+ block : 'start' ,
106
+ } ) ;
107
+ }
108
+ } , [ messages ] ) ;
109
+
100
110
return (
101
- < motion . div layout = "position" className = "relative mx-auto h-full p-8" >
111
+ < div
112
+ className = { tcls (
113
+ 'mx-auto h-full justify-between overflow-y-auto p-8' ,
114
+ searchState ?. mode === 'chat' && 'md:px-20'
115
+ ) }
116
+ >
117
+ { searchState ?. mode === 'chat' ? (
118
+ < div className = "left-4 mb-8 md:absolute" >
119
+ < Button
120
+ label = "Show search results"
121
+ variant = "blank"
122
+ size = "small"
123
+ icon = "arrow-up-to-line"
124
+ className = "md:hidden"
125
+ onClick = { ( ) => {
126
+ setSearchState ( ( state ) =>
127
+ state ? { ...state , mode : 'both' , manual : true } : null
128
+ ) ;
129
+ } }
130
+ />
131
+ < Button
132
+ label = "Show search results"
133
+ iconOnly
134
+ variant = "blank"
135
+ icon = "arrow-left-to-line"
136
+ className = "hidden md:block"
137
+ onClick = { ( ) => {
138
+ setSearchState ( ( state ) =>
139
+ state ? { ...state , mode : 'both' , manual : true } : null
140
+ ) ;
141
+ } }
142
+ />
143
+ </ div >
144
+ ) : null }
102
145
< div className = "mx-auto flex w-full max-w-prose flex-col gap-4" >
103
146
< div >
104
147
< h5 className = "mb-1 flex items-center gap-1 font-semibold text-tint-subtle text-xs" >
@@ -124,21 +167,24 @@ export function SearchChat(props: { query: string }) {
124
167
) }
125
168
</ div >
126
169
127
- { messages . map ( ( message ) => (
170
+ { messages . map ( ( message , index ) => (
128
171
< div
129
172
key = { message . content }
173
+ ref = { index === messages . length - 1 ? latestMessageRef : null }
130
174
className = { tcls (
131
- 'flex flex-col gap-1' ,
132
- message . role === 'user' && 'items-end gap-1 self-end'
175
+ 'flex scroll-mt-20 scroll-mb-[100%] flex-col gap-1' ,
176
+ message . role === 'user' && 'items-end gap-1 self-end' ,
177
+ index === messages . length - 1 && 'mb-[45vh]'
133
178
) }
134
179
>
135
180
{ message . role === 'user' ? (
136
181
< h5 className = "flex items-center gap-1 font-semibold text-tint-subtle text-xs" >
137
- You asked { isQuestion ( query ) ? '' : 'about' }
182
+ { message . context ?? ` You asked $ {isQuestion ( query ) ? '' : 'about' } ` }
138
183
</ h5 >
139
184
) : (
140
185
< h5 className = "flex items-center gap-1 font-semibold text-tint-subtle text-xs" >
141
- < Icon icon = "sparkle" className = "mt-0.5 size-3" /> AI Answer
186
+ < Icon icon = "sparkle" className = "mt-0.5 size-3" /> { ' ' }
187
+ { message . context ?? 'AI Answer' }
142
188
</ h5 >
143
189
) }
144
190
{ message . fetching ? (
@@ -194,11 +240,17 @@ export function SearchChat(props: { query: string }) {
194
240
icon = "arrow-up"
195
241
size = "medium"
196
242
className = "shrink-0"
243
+ onClick = { ( ) => {
244
+ setMessages ( ( prev ) => [
245
+ ...prev ,
246
+ { role : 'user' , content : 'Hello' , fetching : false } ,
247
+ ] ) ;
248
+ } }
197
249
/>
198
250
</ div >
199
251
</ div >
200
252
</ div >
201
253
) : null }
202
- </ motion . div >
254
+ </ div >
203
255
) ;
204
256
}
0 commit comments