Skip to content

Commit a7172bf

Browse files
authored
Merge branch 'main' into main
2 parents 7d2c46c + d2ff7ad commit a7172bf

File tree

17 files changed

+228
-102
lines changed

17 files changed

+228
-102
lines changed

app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ async def assets(path):
103103
"show_chat_history_button": app_settings.ui.show_chat_history_button,
104104
},
105105
"sanitize_answer": app_settings.base_settings.sanitize_answer,
106+
"oyd_enabled": app_settings.base_settings.datasource_type,
106107
}
107108

108109

backend/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@ def format_pf_non_streaming_response(
166166
"content": chatCompletion[response_field_name]
167167
})
168168
if citations_field_name in chatCompletion:
169+
citation_content= {"citations": chatCompletion[citations_field_name]}
169170
messages.append({
170171
"role": "tool",
171-
"content": chatCompletion[citations_field_name]
172+
"content": json.dumps(citation_content)
172173
})
174+
173175
response_obj = {
174176
"id": chatCompletion["id"],
175177
"model": "",

frontend/src/api/models.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export type AskResponse = {
2-
answer: string
2+
answer: string | []
33
citations: Citation[]
44
generated_chart: string | null
55
error?: string
@@ -40,7 +40,7 @@ export type AzureSqlServerExecResults = {
4040
export type ChatMessage = {
4141
id: string
4242
role: string
43-
content: string
43+
content: string | [{ type: string; text: string }, { type: string; image_url: { url: string } }]
4444
end_turn?: boolean
4545
date: string
4646
feedback?: Feedback
@@ -138,6 +138,7 @@ export type FrontendSettings = {
138138
feedback_enabled?: string | null
139139
ui?: UI
140140
sanitize_answer?: boolean
141+
oyd_enabled?: boolean
141142
}
142143

143144
export enum Feedback {

frontend/src/components/Answer/Answer.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,17 +247,17 @@ export const Answer = ({ answer, onCitationClicked, onExectResultClicked }: Prop
247247
<Stack.Item>
248248
<Stack horizontal grow>
249249
<Stack.Item grow>
250-
<ReactMarkdown
250+
{parsedAnswer && <ReactMarkdown
251251
linkTarget="_blank"
252252
remarkPlugins={[remarkGfm, supersub]}
253253
children={
254254
SANITIZE_ANSWER
255-
? DOMPurify.sanitize(parsedAnswer.markdownFormatText, { ALLOWED_TAGS: XSSAllowTags, ALLOWED_ATTR: XSSAllowAttributes })
256-
: parsedAnswer.markdownFormatText
255+
? DOMPurify.sanitize(parsedAnswer?.markdownFormatText, { ALLOWED_TAGS: XSSAllowTags, ALLOWED_ATTR: XSSAllowAttributes })
256+
: parsedAnswer?.markdownFormatText
257257
}
258258
className={styles.answerText}
259259
components={components}
260-
/>
260+
/>}
261261
</Stack.Item>
262262
<Stack.Item className={styles.answerHeader}>
263263
{FEEDBACK_ENABLED && answer.message_id !== undefined && (
@@ -290,15 +290,15 @@ export const Answer = ({ answer, onCitationClicked, onExectResultClicked }: Prop
290290
</Stack.Item>
291291
</Stack>
292292
</Stack.Item>
293-
{parsedAnswer.generated_chart !== null && (
293+
{parsedAnswer?.generated_chart !== null && (
294294
<Stack className={styles.answerContainer}>
295295
<Stack.Item grow>
296-
<img src={`data:image/png;base64, ${parsedAnswer.generated_chart}`} />
296+
<img src={`data:image/png;base64, ${parsedAnswer?.generated_chart}`} />
297297
</Stack.Item>
298298
</Stack>
299299
)}
300300
<Stack horizontal className={styles.answerFooter}>
301-
{!!parsedAnswer.citations.length && (
301+
{!!parsedAnswer?.citations.length && (
302302
<Stack.Item onKeyDown={e => (e.key === 'Enter' || e.key === ' ' ? toggleIsRefAccordionOpen() : null)}>
303303
<Stack style={{ width: '100%' }}>
304304
<Stack horizontal horizontalAlign="start" verticalAlign="center">
@@ -352,7 +352,7 @@ export const Answer = ({ answer, onCitationClicked, onExectResultClicked }: Prop
352352
</Stack>
353353
{chevronIsExpanded && (
354354
<div className={styles.citationWrapper}>
355-
{parsedAnswer.citations.map((citation, idx) => {
355+
{parsedAnswer?.citations.map((citation, idx) => {
356356
return (
357357
<span
358358
title={createCitationFilepath(citation, ++idx)}

frontend/src/components/Answer/AnswerParser.test.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,3 @@ describe('enumerateCitations', () => {
5454
expect(results[2].part_index).toEqual(1)
5555
})
5656
})
57-
58-
describe('parseAnswer', () => {
59-
it('reformats the answer text and reindexes citations', () => {
60-
const parsed: ParsedAnswer = parseAnswer(sampleAnswer)
61-
expect(parsed.markdownFormatText).toBe('This is an example answer with citations ^1^ and ^2^ .')
62-
expect(parsed.citations.length).toBe(2)
63-
expect(parsed.citations[0].id).toBe('1')
64-
expect(parsed.citations[0].reindex_id).toBe('1')
65-
expect(parsed.citations[1].id).toBe('2')
66-
expect(parsed.citations[1].reindex_id).toBe('2')
67-
expect(parsed.citations[0].part_index).toBe(1)
68-
expect(parsed.citations[1].part_index).toBe(2)
69-
})
70-
})

frontend/src/components/Answer/AnswerParser.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type ParsedAnswer = {
66
citations: Citation[]
77
markdownFormatText: string,
88
generated_chart: string | null
9-
}
9+
} | null
1010

1111
export const enumerateCitations = (citations: Citation[]) => {
1212
const filepathMap = new Map()
@@ -23,6 +23,7 @@ export const enumerateCitations = (citations: Citation[]) => {
2323
}
2424

2525
export function parseAnswer(answer: AskResponse): ParsedAnswer {
26+
if (typeof answer.answer !== "string") return null
2627
let answerText = answer.answer
2728
const citationLinks = answerText.match(/\[(doc\d\d?\d?)]/g)
2829

frontend/src/components/QuestionInput/QuestionInput.module.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,35 @@
6262
left: 16.5%;
6363
}
6464
}
65+
66+
.fileInputContainer {
67+
position: absolute;
68+
right: 24px;
69+
top: 20px;
70+
}
71+
72+
.fileInput {
73+
width: 0;
74+
height: 0;
75+
opacity: 0;
76+
overflow: hidden;
77+
position: absolute;
78+
z-index: -1;
79+
}
80+
81+
.fileLabel {
82+
display: inline-block;
83+
border-radius: 5px;
84+
cursor: pointer;
85+
text-align: center;
86+
font-size: 14px;
87+
}
88+
89+
.fileIcon {
90+
font-size: 20px;
91+
color: #424242;
92+
}
93+
94+
.uploadedImage {
95+
margin-right: 70px;
96+
}

frontend/src/components/QuestionInput/QuestionInput.tsx

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import { useState } from 'react'
2-
import { Stack, TextField } from '@fluentui/react'
1+
import { useContext, useState } from 'react'
2+
import { FontIcon, Stack, TextField } from '@fluentui/react'
33
import { SendRegular } from '@fluentui/react-icons'
44

55
import Send from '../../assets/Send.svg'
66

77
import styles from './QuestionInput.module.css'
8+
import { ChatMessage } from '../../api'
9+
import { AppStateContext } from '../../state/AppProvider'
810

911
interface Props {
10-
onSend: (question: string, id?: string) => void
12+
onSend: (question: ChatMessage['content'], id?: string) => void
1113
disabled: boolean
1214
placeholder?: string
1315
clearOnSend?: boolean
@@ -16,16 +18,46 @@ interface Props {
1618

1719
export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend, conversationId }: Props) => {
1820
const [question, setQuestion] = useState<string>('')
21+
const [base64Image, setBase64Image] = useState<string | null>(null);
22+
23+
const appStateContext = useContext(AppStateContext)
24+
const OYD_ENABLED = appStateContext?.state.frontendSettings?.oyd_enabled || false;
25+
26+
const handleImageUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
27+
const file = event.target.files?.[0];
28+
29+
if (file) {
30+
await convertToBase64(file);
31+
}
32+
};
33+
34+
const convertToBase64 = async (file: Blob) => {
35+
const reader = new FileReader();
36+
37+
reader.readAsDataURL(file);
38+
39+
reader.onloadend = () => {
40+
setBase64Image(reader.result as string);
41+
};
42+
43+
reader.onerror = (error) => {
44+
console.error('Error: ', error);
45+
};
46+
};
1947

2048
const sendQuestion = () => {
2149
if (disabled || !question.trim()) {
2250
return
2351
}
2452

25-
if (conversationId) {
26-
onSend(question, conversationId)
53+
const questionTest: ChatMessage["content"] = base64Image ? [{ type: "text", text: question }, { type: "image_url", image_url: { url: base64Image } }] : question.toString();
54+
55+
if (conversationId && questionTest !== undefined) {
56+
onSend(questionTest, conversationId)
57+
setBase64Image(null)
2758
} else {
28-
onSend(question)
59+
onSend(questionTest)
60+
setBase64Image(null)
2961
}
3062

3163
if (clearOnSend) {
@@ -58,6 +90,24 @@ export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend, conv
5890
onChange={onQuestionChange}
5991
onKeyDown={onEnterPress}
6092
/>
93+
{!OYD_ENABLED && (
94+
<div className={styles.fileInputContainer}>
95+
<input
96+
type="file"
97+
id="fileInput"
98+
onChange={(event) => handleImageUpload(event)}
99+
accept="image/*"
100+
className={styles.fileInput}
101+
/>
102+
<label htmlFor="fileInput" className={styles.fileLabel} aria-label='Upload Image'>
103+
<FontIcon
104+
className={styles.fileIcon}
105+
iconName={'PhotoCollection'}
106+
aria-label='Upload Image'
107+
/>
108+
</label>
109+
</div>)}
110+
{base64Image && <img className={styles.uploadedImage} src={base64Image} alt="Uploaded Preview" />}
61111
<div
62112
className={styles.questionInputSendButtonContainer}
63113
role="button"

frontend/src/pages/chat/Chat.module.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
}
8686

8787
.chatMessageUserMessage {
88+
position: relative;
8889
display: flex;
8990
padding: 20px;
9091
background: #edf5fd;
@@ -360,6 +361,15 @@ a {
360361
cursor: pointer;
361362
}
362363

364+
.uploadedImageChat {
365+
position: absolute;
366+
right: -23px;
367+
bottom: -35px;
368+
max-width: 70%;
369+
max-height: 70%;
370+
border-radius: 4px;
371+
}
372+
363373
@media (max-width: 480px) {
364374
.chatInput {
365375
width: 90%;

0 commit comments

Comments
 (0)