Skip to content

Commit b742f10

Browse files
committed
show file search results properly
1 parent 5dad24b commit b742f10

File tree

8 files changed

+84
-93
lines changed

8 files changed

+84
-93
lines changed

src/client/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const App = () => {
9999
<AppContext.Provider value={appRef}>
100100
<Box minHeight="100vh" display="flex" flexDirection="column" ref={appRef}>
101101
<NavBar />
102-
<Container component="main" sx={{ mt: '4rem', mb: '10rem' }}>
102+
<Container component="main" sx={{ mt: '4rem', mb: '10rem' }} maxWidth="xl">
103103
<Outlet />
104104
</Container>
105105
<Footer />

src/client/components/ChatV2/ChatV2.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import useLocalStorageState from '../../hooks/useLocalStorageState'
66
import { DEFAULT_MODEL } from '../../../config'
77
import useInfoTexts from '../../hooks/useInfoTexts'
88
import { Message } from '../../types'
9-
import { FileCitation, ResponseStreamEventData } from '../../../shared/types'
9+
import { FileCitation, FileSearchResult, ResponseStreamEventData } from '../../../shared/types'
1010
import useRetryTimeout from '../../hooks/useRetryTimeout'
1111
import { useTranslation } from 'react-i18next'
1212
import { handleCompletionStreamError } from './error'
@@ -49,7 +49,7 @@ export const ChatV2 = () => {
4949
const [activePromptId, setActivePromptId] = useState('')
5050
const [fileName, setFileName] = useState<string>('')
5151
const [completion, setCompletion] = useState('')
52-
const [citations, setCitations] = useState<FileCitation[]>([])
52+
const [fileSearchResult, setFileSearchResult] = useLocalStorageState<FileSearchResult>('last-file-search', null)
5353
const [streamController, setStreamController] = useState<AbortController>()
5454
const [alertOpen, setAlertOpen] = useState(false)
5555
const [disallowedFileType, setDisallowedFileType] = useState('')
@@ -75,7 +75,7 @@ export const ChatV2 = () => {
7575
const reader = stream.getReader()
7676

7777
let content = ''
78-
const citations: FileCitation[] = []
78+
let fileSearchResult: FileSearchResult
7979

8080
while (true) {
8181
const { value, done } = await reader.read()
@@ -86,7 +86,12 @@ export const ChatV2 = () => {
8686
for (const chunk of data.split('\n')) {
8787
if (!chunk || chunk.trim().length === 0) continue
8888

89-
const parsedChunk: ResponseStreamEventData = JSON.parse(chunk)
89+
let parsedChunk: ResponseStreamEventData = null
90+
try {
91+
parsedChunk = JSON.parse(chunk)
92+
} catch (_e) {
93+
console.error('Could not parse the chunk:', chunk)
94+
}
9095

9196
switch (parsedChunk.type) {
9297
case 'writing':
@@ -96,8 +101,11 @@ export const ChatV2 = () => {
96101

97102
case 'annotation':
98103
console.log('Received annotation:', parsedChunk.annotation)
99-
setCitations((prev) => [...prev, parsedChunk.annotation])
100-
citations.push(parsedChunk.annotation)
104+
break
105+
106+
case 'fileSearchDone':
107+
fileSearchResult = parsedChunk.fileSearch
108+
setFileSearchResult(parsedChunk.fileSearch)
101109
break
102110

103111
case 'complete':
@@ -115,8 +123,7 @@ export const ChatV2 = () => {
115123
}
116124
}
117125

118-
setMessages((prev: Message[]) => prev.concat({ role: 'assistant', content, citations }))
119-
setCitations([])
126+
setMessages((prev: Message[]) => prev.concat({ role: 'assistant', content, fileSearchResult }))
120127
} catch (err: any) {
121128
handleCompletionStreamError(err, fileName)
122129
} finally {
@@ -135,7 +142,7 @@ export const ChatV2 = () => {
135142
setMessage({ content: '' })
136143
setPrevResponse({ id: '' })
137144
setCompletion('')
138-
setCitations([])
145+
setFileSearchResult(undefined)
139146
setStreamController(new AbortController())
140147
setRetryTimeout(() => {
141148
if (streamController) {
@@ -176,6 +183,7 @@ export const ChatV2 = () => {
176183
setMessage({ content: '' })
177184
setPrevResponse({ id: '' })
178185
setCompletion('')
186+
setFileSearchResult(null)
179187
setStreamController(undefined)
180188
setTokenUsageWarning('')
181189
setTokenWarningVisible(false)
@@ -218,8 +226,8 @@ export const ChatV2 = () => {
218226
{courseId ? <Link to={'/v2'}>CurreChat</Link> : <Link to={'/v2/sandbox'}>Ohtu Sandbox</Link>}
219227
</Box>
220228
<Box sx={{ display: 'flex' }}>
221-
<Box ref={chatContainerRef}>
222-
<Conversation messages={messages} completion={completion} citations={citations} />
229+
<Box ref={chatContainerRef} flex={1}>
230+
<Conversation messages={messages} completion={completion} fileSearchResult={fileSearchResult} />
223231
<ChatBox
224232
disabled={false}
225233
onSubmit={(message) => {
@@ -230,7 +238,11 @@ export const ChatV2 = () => {
230238
}}
231239
/>
232240
</Box>
233-
{ragIndex && <CitationsBox messages={messages} citations={citations} ragIndex={ragIndex} />}
241+
{ragIndex && (
242+
<Box flex={1}>
243+
<CitationsBox messages={messages} fileSearchResult={fileSearchResult} />
244+
</Box>
245+
)}
234246
</Box>
235247
</Box>
236248
)
Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,51 @@
11
import { Box, Paper, Typography } from '@mui/material'
2-
import { FileCitation, RagFileAttributes, RagIndexAttributes } from '../../../shared/types'
2+
import { FileSearchResult } from '../../../shared/types'
33
import { Message } from '../../types'
4-
import { useQuery } from '@tanstack/react-query'
54
import Markdown from 'react-markdown'
65

7-
const useFileCitationText = (citation: FileCitation, ragIndex: RagIndexAttributes) => {
8-
const ragFileId = ragIndex?.ragFiles?.find((file) => file.filename === citation.filename)?.id
9-
10-
const { data, ...rest } = useQuery<RagFileAttributes & { fileContent: string }>({
11-
queryKey: ['file-citation-content', ragIndex.id, citation.filename],
12-
queryFn: async () => {
13-
const response = await fetch(`/api/rag/indices/${ragIndex.id}/files/${ragFileId}`)
14-
if (!response.ok) {
15-
throw new Error('Failed to fetch file text')
16-
}
17-
return response.json()
18-
},
19-
retry: false,
20-
enabled: !!ragFileId,
21-
})
22-
23-
return {
24-
ragFile: data,
25-
...rest,
26-
}
27-
}
28-
29-
const Citation = ({ citation, ragIndex }: { citation: FileCitation; ragIndex: RagIndexAttributes }) => {
30-
const { ragFile, isLoading, error } = useFileCitationText(citation, ragIndex)
31-
32-
const contentAtIdx = ragFile?.fileContent?.substring(citation.index, citation.index + 800) || ''
6+
type FileItem = FileSearchResult['results'][number]
337

8+
const FileItemComponent = ({ fileItem }: { fileItem: FileItem }) => {
349
return (
35-
<Box sx={{ mb: 2, pl: 1, borderLeft: '2px solid #ccc' }}>
10+
<Paper sx={{ p: 1, mt: 2 }}>
3611
<Typography variant="body2" color="textSecondary">
37-
{citation.filename} (Index: {citation.index})
12+
{fileItem.filename} (score: {fileItem.score})
3813
</Typography>
39-
{isLoading ? (
40-
<Typography variant="body2" color="textSecondary">
41-
Loading citation text...
42-
</Typography>
43-
) : error ? (
44-
<Typography variant="body2" color="error">
45-
Error loading citation text: {error.message}
46-
</Typography>
47-
) : (
48-
ragFile && <Markdown>{contentAtIdx}</Markdown>
49-
)}
50-
</Box>
14+
<Markdown>{fileItem.text}</Markdown>
15+
</Paper>
5116
)
5217
}
5318

54-
const MessageCitations = ({ citations, ragIndex }: { citations: FileCitation[]; ragIndex: RagIndexAttributes }) => {
19+
const MessageFileSearchResult = ({ fileSearchResult }: { fileSearchResult: FileSearchResult }) => {
5520
return (
5621
<Box sx={{ mt: 1 }}>
57-
{citations.map((citation, index) => (
58-
<Citation key={index} citation={citation} ragIndex={ragIndex} />
22+
<Typography>Searched for:</Typography>
23+
<Box display="flex" gap={2}>
24+
{fileSearchResult.queries.map((q, idx) => (
25+
<Typography variant="body2" key={idx}>
26+
"{q}"
27+
</Typography>
28+
))}
29+
</Box>
30+
31+
{fileSearchResult.results.map((result, key) => (
32+
<FileItemComponent key={key} fileItem={result} />
5933
))}
6034
</Box>
6135
)
6236
}
6337

64-
export const CitationsBox = ({ messages, citations, ragIndex }: { messages: Message[]; citations: FileCitation[]; ragIndex: RagIndexAttributes }) => {
65-
const messageCitations = [...messages.map((message) => (Array.isArray(message.citations) ? message.citations : [])), citations]
38+
export const CitationsBox = ({ messages, fileSearchResult }: { messages: Message[]; fileSearchResult?: FileSearchResult }) => {
39+
const messageCitations = [...messages.map((m) => m.fileSearchResult).filter(Boolean)]
40+
if (fileSearchResult) {
41+
messageCitations.push(fileSearchResult)
42+
}
6643

6744
return (
68-
<Paper>
69-
<Box sx={{ width: 400, padding: 2 }}>
70-
{messageCitations.map((c, index) => (
71-
<MessageCitations key={index} citations={c} ragIndex={ragIndex} />
72-
))}
73-
</Box>
74-
</Paper>
45+
<Box p={2}>
46+
{messageCitations.map((c, index) => (
47+
<MessageFileSearchResult key={index} fileSearchResult={fileSearchResult} />
48+
))}
49+
</Box>
7550
)
7651
}

src/client/components/ChatV2/Conversation.tsx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,19 @@ import { Message } from '../../types'
33
import ReactMarkdown from 'react-markdown'
44
import remarkGfm from 'remark-gfm'
55
import { Assistant } from '@mui/icons-material'
6-
import { FileCitation } from '../../../shared/types'
6+
import { FileCitation, FileSearchResult } from '../../../shared/types'
77

88
const MessageItem = ({ message }: { message: Message }) => (
99
<Paper
10-
elevation={3}
10+
elevation={message.role === 'assistant' ? 0 : 3}
1111
sx={{
1212
my: '2rem',
1313
ml: message.role === 'assistant' ? '0' : '2rem',
1414
mr: message.role === 'assistant' ? '2rem' : '0',
1515
p: '1rem',
16-
backgroundColor: message.role === 'user' ? '#ffffff' : '#e0f7fa',
17-
borderRadius: message.role === 'assistant' ? '0 1rem 1rem 1rem' : '1rem 0 1rem 1rem',
1816
}}
1917
>
2018
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
21-
{message.citations && message.citations.length > 0 && (
22-
<Box sx={{ mt: 1, fontSize: '0.875rem', color: 'gray' }}>
23-
Citations:
24-
{message.citations.map((citation, index) => (
25-
<Box key={index} sx={{ display: 'block' }}>
26-
{citation.filename}
27-
</Box>
28-
))}
29-
</Box>
30-
)}
3119
</Paper>
3220
)
3321

@@ -52,12 +40,12 @@ const PöhinäLogo = () => (
5240
</Box>
5341
)
5442

55-
export const Conversation = ({ messages, completion, citations }: { messages: Message[]; completion: string; citations: FileCitation[] }) => (
43+
export const Conversation = ({ messages, completion, fileSearchResult }: { messages: Message[]; completion: string; fileSearchResult: FileSearchResult }) => (
5644
<Box sx={{ flex: 1, overflowY: 'auto', gap: 2 }}>
5745
{messages.map((message, idx) => (
5846
<MessageItem key={idx} message={message} />
5947
))}
60-
{completion && <MessageItem message={{ role: 'assistant', content: completion, citations }} />}
48+
{completion && <MessageItem message={{ role: 'assistant', content: completion, fileSearchResult }} />}
6149
{messages.length === 0 && <PöhinäLogo />}
6250
</Box>
6351
)

src/client/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FileCitation } from '../shared/types'
1+
import { FileSearchResult } from "../shared/types"
22

33
export type SetState<T> = React.Dispatch<React.SetStateAction<T>>
44

@@ -7,7 +7,7 @@ export type Role = 'system' | 'assistant' | 'user'
77
export interface Message {
88
role: Role
99
content: string
10-
citations?: FileCitation[]
10+
fileSearchResult?: FileSearchResult
1111
}
1212

1313
interface Term {

src/server/routes/openai.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,13 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
197197

198198
res.setHeader('content-type', 'text/event-stream')
199199

200-
const completion = await responsesClient.handleResponse({
200+
const result = await responsesClient.handleResponse({
201201
events,
202202
encoding,
203203
res,
204204
})
205205

206-
tokenCount += completion.tokenCount
206+
tokenCount += result.tokenCount
207207

208208
let userToCharge = user
209209
if (inProduction && req.hijackedBy) {
@@ -228,10 +228,11 @@ openaiRouter.post('/stream/v2', upload.single('file'), async (r, res) => {
228228
console.log('consentToSave', options.saveConsent, user.username)
229229

230230
if (consentToSave) {
231+
// @todo: should file search results also be saved?
231232
const discussion = {
232233
userId: user.id,
233234
courseId,
234-
response: completion.response,
235+
response: result.response,
235236
metadata: options,
236237
}
237238
await Discussion.create(discussion)

src/server/util/azure/ResponsesAPI.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,20 @@ export class ResponsesClient {
104104
console.log('file search completed')
105105
break
106106

107-
case 'response.output_item.done':
108-
console.log('OUTPUT_ITEM DONE???', JSON.stringify(event, null, 2))
109-
return {
110-
tokenCount,
111-
response: contents.join(''),
107+
case 'response.output_item.done': {
108+
if (event.item.type === 'file_search_call') {
109+
this.write(
110+
{
111+
type: 'fileSearchDone',
112+
fileSearch: event.item,
113+
},
114+
res,
115+
)
112116
}
113117

118+
break
119+
}
120+
114121
case 'response.output_text.annotation.added':
115122
console.log(event)
116123
this.write(

src/shared/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ResponseFileSearchToolCall } from 'openai/resources/responses/responses'
2+
13
export type RagIndexMetadata = {
24
name: string
35
dim?: number
@@ -26,6 +28,8 @@ export type FileCitation = {
2628
type: 'file_citation'
2729
}
2830

31+
export type FileSearchResult = ResponseFileSearchToolCall
32+
2933
export type ResponseStreamEventData =
3034
| {
3135
type: 'start'
@@ -39,6 +43,10 @@ export type ResponseStreamEventData =
3943
type: 'complete'
4044
prevResponseId: string
4145
}
46+
| {
47+
type: 'fileSearchDone'
48+
fileSearch: FileSearchResult
49+
}
4250
| {
4351
type: 'error'
4452
error: any

0 commit comments

Comments
 (0)