Skip to content

Commit a3af665

Browse files
authoredNov 18, 2024··
chore: updated strucutre of main page (#396)
* updated strucutre of main page * updated the names * modifed main folder * removed some variables from context and refined some names * added different useEffect for each case * added default model selection * removed a console log
1 parent d086977 commit a3af665

File tree

14 files changed

+896
-1
lines changed

14 files changed

+896
-1
lines changed
 

‎frontend/src/router/DocsQA.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ScreenFallbackLoader from '@/components/base/molecules/ScreenFallbackLoad
66
import DataHub from '@/screens/dashboard/docsqa/DataSources'
77
import NavBar from '@/screens/dashboard/docsqa/Navbar'
88
import Applications from '@/screens/dashboard/docsqa/Applications'
9-
const DocsQA = lazy(() => import('@/screens/dashboard/docsqa'))
9+
const DocsQA = lazy(() => import('@/screens/dashboard/docsqa/main'))
1010
const DocsQAChatbot = lazy(() => import('@/screens/dashboard/docsqa/Chatbot'))
1111
const DocsQASettings = lazy(() => import('@/screens/dashboard/docsqa/settings'))
1212

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { useState } from 'react'
2+
3+
import Spinner from '@/components/base/atoms/Spinner/Spinner'
4+
import ApplicationModal from './components/ApplicationModal'
5+
import NoCollections from '../NoCollections'
6+
import { useDocsQAContext } from './context'
7+
import ConfigSidebar from './components/ConfigSidebar'
8+
import Chat from './components/Chat'
9+
10+
const DocsQA = () => {
11+
const { selectedCollection, isCollectionsLoading } = useDocsQAContext()
12+
13+
const [isCreateApplicationModalOpen, setIsCreateApplicationModalOpen] =
14+
useState(false)
15+
16+
return (
17+
<>
18+
{isCreateApplicationModalOpen && (
19+
<ApplicationModal
20+
isCreateApplicationModalOpen={isCreateApplicationModalOpen}
21+
setIsCreateApplicationModalOpen={setIsCreateApplicationModalOpen}
22+
/>
23+
)}
24+
<div className="flex gap-5 h-[calc(100vh-6.5rem)] w-full">
25+
{isCollectionsLoading ? (
26+
<div className="h-full w-full flex items-center">
27+
<Spinner center big />
28+
</div>
29+
) : selectedCollection ? (
30+
<>
31+
<ConfigSidebar
32+
setIsCreateApplicationModalOpen={setIsCreateApplicationModalOpen}
33+
/>
34+
<Chat />
35+
</>
36+
) : (
37+
<NoCollections fullWidth />
38+
)}
39+
</div>
40+
</>
41+
)
42+
}
43+
44+
export default DocsQA
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react'
2+
3+
import SourceDocsPreview from '../../DocsQA/SourceDocsPreview'
4+
import IconProvider from '@/components/assets/IconProvider'
5+
import Markdown from 'react-markdown'
6+
import { useDocsQAContext } from '../context'
7+
8+
const Answer = (props: any) => {
9+
const { sourceDocs, answer } = useDocsQAContext()
10+
11+
return (
12+
<div className="overflow-y-auto flex flex-col gap-4 mt-7 h-[calc(100%-70px)]">
13+
<div className="max-h-[60%] h-full overflow-y-auto flex gap-4">
14+
<div className="bg-indigo-400 w-6 h-6 rounded-full flex items-center justify-center mt-0.5">
15+
<IconProvider icon="message" className="text-white" />
16+
</div>
17+
<div className="w-full font-inter text-base">
18+
<div className="font-bold text-lg">Answer:</div>
19+
<Markdown>{answer}</Markdown>
20+
</div>
21+
</div>
22+
{sourceDocs && <SourceDocsPreview sourceDocs={sourceDocs} />}
23+
</div>
24+
)
25+
}
26+
27+
export default Answer
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import React, { useState } from 'react'
2+
3+
import { LightTooltip } from '@/components/base/atoms/Tooltip'
4+
import { useCreateApplicationMutation } from '@/stores/qafoundry'
5+
import notify from '@/components/base/molecules/Notify'
6+
import Button from '@/components/base/atoms/Button'
7+
import Modal from '@/components/base/atoms/Modal'
8+
import Input from '@/components/base/atoms/Input'
9+
import { useDocsQAContext } from '../context'
10+
11+
const ApplicationModal = (props: any) => {
12+
const {
13+
allEnabledModels,
14+
selectedCollection,
15+
modelConfig,
16+
retrieverConfig,
17+
selectedQueryModel,
18+
selectedRetriever,
19+
promptTemplate,
20+
selectedQueryController,
21+
} = useDocsQAContext()
22+
23+
const [createApplication, { isLoading: isCreateApplicationLoading }] =
24+
useCreateApplicationMutation()
25+
26+
const { isCreateApplicationModalOpen, setIsCreateApplicationModalOpen } =
27+
props
28+
29+
const [applicationName, setApplicationName] = useState('')
30+
const [questions, setQuestions] = useState<string[]>([])
31+
32+
const pattern = /^[a-z][a-z0-9-]*$/
33+
const isValidApplicationName = pattern.test(applicationName)
34+
35+
const createChatApplication = async (
36+
applicationName: string,
37+
questions: string[],
38+
setApplicationName: (name: string) => void,
39+
) => {
40+
if (!applicationName) {
41+
return notify('error', 'Application name is required')
42+
}
43+
const selectedModel = allEnabledModels.find(
44+
(model: any) => model.name == selectedQueryModel,
45+
)
46+
47+
try {
48+
await createApplication({
49+
name: `${applicationName}-rag-app`,
50+
config: {
51+
collection_name: selectedCollection,
52+
model_configuration: {
53+
name: selectedModel.name,
54+
provider: selectedModel.provider,
55+
...JSON.parse(modelConfig),
56+
},
57+
retriever_name: selectedRetriever?.name ?? '',
58+
retriever_config: JSON.parse(retrieverConfig),
59+
prompt_template: promptTemplate,
60+
query_controller: selectedQueryController,
61+
},
62+
questions,
63+
}).unwrap()
64+
setApplicationName('')
65+
setIsCreateApplicationModalOpen(false)
66+
notify('success', 'Application created successfully')
67+
} catch (err: any) {
68+
notify('error', 'Failed to create application', err?.data?.detail)
69+
}
70+
}
71+
72+
return (
73+
<Modal
74+
open={isCreateApplicationModalOpen}
75+
onClose={() => {
76+
setApplicationName('')
77+
setQuestions([])
78+
setIsCreateApplicationModalOpen(false)
79+
}}
80+
>
81+
<div className="modal-box">
82+
<div className="text-center font-medium text-xl mb-2">
83+
Create Application
84+
</div>
85+
<div>
86+
<div className="text-sm">Enter the name of the application</div>
87+
<Input
88+
value={applicationName}
89+
onChange={(e) => setApplicationName(e.target.value)}
90+
className="py-1 input-sm mt-1"
91+
placeholder="E.g. query-bot"
92+
/>
93+
{applicationName && !isValidApplicationName ? (
94+
<div className="text-sm text-error mt-1">
95+
Application name should start with a lowercase letter and can only
96+
contain lowercase letters, numbers and hyphens
97+
</div>
98+
) : applicationName ? (
99+
<div className="text-sm mt-1">
100+
The application name will be generated as{' '}
101+
<span className="font-medium">"{applicationName}-rag-app"</span>
102+
</div>
103+
) : (
104+
<></>
105+
)}
106+
<div className="mt-2 text-sm">Questions (Optional)</div>
107+
{questions.map((question: any, index: any) => (
108+
<div className="flex items-center gap-2 mt-2 w-full">
109+
<div className="flex-1">
110+
<Input
111+
key={index}
112+
value={question}
113+
onChange={(e) => {
114+
const updatedQuestions = [...questions]
115+
updatedQuestions[index] = e.target.value
116+
setQuestions(updatedQuestions)
117+
}}
118+
className="py-1 input-sm w-full"
119+
placeholder={`Question ${index + 1}`}
120+
maxLength={100}
121+
/>
122+
</div>
123+
<Button
124+
icon="trash-alt"
125+
className="btn-sm hover:bg-red-600 hover:border-white hover:text-white"
126+
onClick={() => {
127+
setQuestions(questions.filter((_, i) => i !== index))
128+
}}
129+
/>
130+
</div>
131+
))}
132+
<LightTooltip
133+
title={
134+
questions.length === 4 ? 'Maximum 4 questions are allowed' : ''
135+
}
136+
size="fit"
137+
>
138+
<div className="w-fit">
139+
<Button
140+
text="Add Question"
141+
white
142+
disabled={questions.length == 4}
143+
className="text-sm font-medium text-gray-1000 hover:bg-white mt-2"
144+
onClick={() => {
145+
if (questions.length < 4) {
146+
setQuestions([...questions, ''])
147+
}
148+
}}
149+
/>
150+
</div>
151+
</LightTooltip>
152+
</div>
153+
<div className="flex justify-end w-full mt-4 gap-2">
154+
<Button
155+
text="Cancel"
156+
className="btn-sm"
157+
onClick={() => {
158+
setApplicationName('')
159+
setQuestions([])
160+
setIsCreateApplicationModalOpen(false)
161+
}}
162+
/>
163+
<Button
164+
text="Create"
165+
className="btn-sm btn-neutral"
166+
loading={isCreateApplicationLoading}
167+
onClick={() =>
168+
createChatApplication(
169+
applicationName,
170+
questions,
171+
setApplicationName,
172+
)
173+
}
174+
/>
175+
</div>
176+
</div>
177+
</Modal>
178+
)
179+
}
180+
181+
export default ApplicationModal
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { useState } from 'react'
2+
3+
import Spinner from '@/components/base/atoms/Spinner'
4+
import { useDocsQAContext } from '../context'
5+
import PromptForm from './PromptForm'
6+
import ErrorAnswer from './ErrorAnswer'
7+
import Answer from './Answer'
8+
import NoAnswer from './NoAnswer'
9+
10+
const Right = () => {
11+
const { errorMessage, answer } = useDocsQAContext()
12+
13+
const [isRunningPrompt, setIsRunningPrompt] = useState(false)
14+
15+
return (
16+
<div className="h-full border rounded-lg border-[#CEE0F8] w-[calc(100%-25rem)] bg-white p-4">
17+
<PromptForm
18+
isRunningPrompt={isRunningPrompt}
19+
setIsRunningPrompt={setIsRunningPrompt}
20+
/>
21+
{answer ? (
22+
<Answer />
23+
) : isRunningPrompt ? (
24+
<div className="overflow-y-auto flex flex-col justify-center items-center gap-2 h-[calc(100%-4.375rem)]">
25+
<Spinner center medium />
26+
<div className="text-center">Fetching Answer...</div>
27+
</div>
28+
) : errorMessage ? (
29+
<ErrorAnswer />
30+
) : (
31+
<NoAnswer />
32+
)}
33+
</div>
34+
)
35+
}
36+
37+
export default Right
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react'
2+
3+
import { MenuItem, Select } from '@mui/material'
4+
5+
interface ConfigProps {
6+
title: string
7+
placeholder: string
8+
initialValue: string
9+
data: any[] | undefined
10+
handleOnChange: (e: any) => void
11+
renderItem?: (e: any) => React.ReactNode
12+
className?: string
13+
}
14+
15+
const ConfigSelector = (props: ConfigProps) => {
16+
const {
17+
title,
18+
placeholder,
19+
initialValue,
20+
data,
21+
className,
22+
handleOnChange,
23+
renderItem,
24+
} = props
25+
return (
26+
<div className={`flex justify-between items-center ${className}`}>
27+
<div className="text-sm">{title}:</div>
28+
<Select
29+
value={initialValue}
30+
onChange={(e) => handleOnChange(e)}
31+
placeholder={placeholder + '...'}
32+
sx={{
33+
background: 'white',
34+
height: '2rem',
35+
width: '13.1875rem',
36+
border: '1px solid #CEE0F8 !important',
37+
outline: 'none !important',
38+
'& fieldset': {
39+
border: 'none !important',
40+
},
41+
}}
42+
>
43+
{data?.map((item: any) =>
44+
renderItem ? (
45+
renderItem(item)
46+
) : (
47+
<MenuItem value={item} key={item}>
48+
{item}
49+
</MenuItem>
50+
),
51+
)}
52+
</Select>
53+
</div>
54+
)
55+
}
56+
57+
export default ConfigSelector
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { MenuItem, Switch, TextareaAutosize } from '@mui/material'
2+
import React from 'react'
3+
4+
import SimpleCodeEditor from '@/components/base/molecules/SimpleCodeEditor'
5+
import ConfigSelector from './ConfigSelector'
6+
import Button from '@/components/base/atoms/Button'
7+
import { useDocsQAContext } from '../context'
8+
9+
const defaultModelConfig = `{
10+
"parameters": {
11+
"temperature": 0.1
12+
}
13+
}`
14+
15+
const Left = (props: any) => {
16+
const {
17+
selectedCollection,
18+
selectedQueryController,
19+
selectedQueryModel,
20+
selectedRetriever,
21+
retrieverConfig,
22+
promptTemplate,
23+
isInternetSearchEnabled,
24+
collections,
25+
allQueryControllers,
26+
allEnabledModels,
27+
allRetrieverOptions,
28+
setSelectedQueryModel,
29+
setIsInternetSearchEnabled,
30+
setSelectedCollection,
31+
setSelectedQueryController,
32+
setSelectedRetriever,
33+
setModelConfig,
34+
setRetrieverConfig,
35+
setPromptTemplate,
36+
resetQA,
37+
} = useDocsQAContext()
38+
39+
const { setIsCreateApplicationModalOpen } = props
40+
41+
return (
42+
<div className="h-full border rounded-lg border-[#CEE0F8] w-[23.75rem] bg-white p-4 overflow-auto">
43+
<div className="flex flex-col gap-3">
44+
<ConfigSelector
45+
title="Collection"
46+
placeholder="Select Collection..."
47+
initialValue={selectedCollection}
48+
data={collections}
49+
handleOnChange={(e) => {
50+
resetQA()
51+
setSelectedCollection(e.target.value)
52+
}}
53+
/>
54+
<ConfigSelector
55+
title="Query Controller"
56+
placeholder="Select Query Controller..."
57+
initialValue={selectedQueryController}
58+
data={allQueryControllers}
59+
handleOnChange={(e) => {
60+
resetQA()
61+
setSelectedQueryController(e.target.value)
62+
}}
63+
/>
64+
<ConfigSelector
65+
title="Model"
66+
placeholder="Select Model..."
67+
initialValue={selectedQueryModel}
68+
data={allEnabledModels}
69+
handleOnChange={(e) => {
70+
resetQA()
71+
setSelectedQueryModel(e.target.value)
72+
}}
73+
renderItem={(item) => (
74+
<MenuItem key={item.name} value={item.name}>
75+
{item.name}
76+
</MenuItem>
77+
)}
78+
/>
79+
</div>
80+
81+
<div className="mb-1 mt-3 text-sm">Model Configuration:</div>
82+
<SimpleCodeEditor
83+
language="json"
84+
height={130}
85+
defaultValue={defaultModelConfig}
86+
onChange={(updatedConfig) => setModelConfig(updatedConfig ?? '')}
87+
/>
88+
{allRetrieverOptions && selectedRetriever?.key && (
89+
<ConfigSelector
90+
title="Retriever"
91+
placeholder="Select Retriever..."
92+
initialValue={selectedRetriever?.summary}
93+
data={allRetrieverOptions}
94+
className="mt-4"
95+
handleOnChange={(e) => {
96+
const retriever = allRetrieverOptions.find(
97+
(retriever) => retriever.key === e.target.value,
98+
)
99+
setSelectedRetriever(retriever)
100+
setPromptTemplate(retriever?.promptTemplate)
101+
}}
102+
renderItem={(item) => (
103+
<MenuItem key={item.key} value={item.summary}>
104+
{item.summary}
105+
</MenuItem>
106+
)}
107+
/>
108+
)}
109+
<div className="mb-1 mt-3 text-sm">Retrievers Configuration:</div>
110+
<SimpleCodeEditor
111+
language="json"
112+
height={140}
113+
value={retrieverConfig}
114+
onChange={(updatedConfig) => setRetrieverConfig(updatedConfig ?? '')}
115+
/>
116+
117+
<div className="flex justify-between items-center mt-1.5">
118+
<div className="text-sm">Internet Search</div>
119+
<Switch
120+
checked={isInternetSearchEnabled}
121+
onChange={(e) => setIsInternetSearchEnabled(e.target.checked)}
122+
/>
123+
</div>
124+
125+
<div className="mb-1 mt-2 text-sm">Prompt Template:</div>
126+
<TextareaAutosize
127+
className="w-full h-20 bg-[#f0f7ff] border border-[#CEE0F8] rounded-lg p-2 text-sm"
128+
placeholder="Enter Prompt Template..."
129+
minRows={3}
130+
value={promptTemplate}
131+
onChange={(e) => setPromptTemplate(e.target.value)}
132+
/>
133+
<Button
134+
text="Create Application"
135+
className="w-full btn-sm mt-4"
136+
onClick={() => setIsCreateApplicationModalOpen(true)}
137+
/>
138+
</div>
139+
)
140+
}
141+
142+
export default Left
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react'
2+
import IconProvider from '@/components/assets/IconProvider'
3+
4+
const ErrorAnswer = () => {
5+
return (
6+
<div className="overflow-y-auto flex gap-4 mt-7">
7+
<div className="bg-error w-6 h-6 rounded-full flex items-center justify-center mt-0.5">
8+
<IconProvider icon="message" className="text-white" />
9+
</div>
10+
<div className="w-full font-inter text-base text-error">
11+
<div className="font-bold text-lg">Error</div>
12+
We failed to get answer for your query, please try again by resending
13+
query or try again in some time.
14+
</div>
15+
</div>
16+
)
17+
}
18+
19+
export default ErrorAnswer
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react'
2+
3+
import DocsQaInformation from '../../DocsQaInformation'
4+
5+
const NoAnswer = () => {
6+
return (
7+
<div className="h-[calc(100%-3.125rem)] flex justify-center items-center overflow-y-auto">
8+
<div className="min-h-[23rem]">
9+
<DocsQaInformation
10+
header={'Welcome to DocsQA'}
11+
subHeader={
12+
<>
13+
<p className="text-center max-w-[28.125rem] mt-2">
14+
Select a collection from sidebar,
15+
<br /> review all the settings and start asking Questions
16+
</p>
17+
</>
18+
}
19+
/>
20+
</div>
21+
</div>
22+
)
23+
}
24+
25+
export default NoAnswer
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import React, { useState } from 'react'
2+
import { SSE } from 'sse.js'
3+
4+
import { baseQAFoundryPath, CollectionQueryDto } from '@/stores/qafoundry'
5+
import Input from '@/components/base/atoms/Input'
6+
import Button from '@/components/base/atoms/Button'
7+
import { useDocsQAContext } from '../context'
8+
import { notifyError } from '@/utils/error'
9+
10+
const Form = (props: any) => {
11+
const {
12+
setErrorMessage,
13+
setSourceDocs,
14+
setAnswer,
15+
setPrompt,
16+
selectedQueryModel,
17+
allEnabledModels,
18+
modelConfig,
19+
retrieverConfig,
20+
selectedCollection,
21+
selectedRetriever,
22+
promptTemplate,
23+
prompt,
24+
isInternetSearchEnabled,
25+
selectedQueryController,
26+
} = useDocsQAContext()
27+
28+
const { isRunningPrompt, setIsRunningPrompt } = props
29+
30+
const handlePromptSubmit = async () => {
31+
setIsRunningPrompt(true)
32+
setAnswer('')
33+
setSourceDocs([])
34+
setErrorMessage(false)
35+
try {
36+
const selectedModel = allEnabledModels.find(
37+
(model: any) => model.name == selectedQueryModel,
38+
)
39+
if (!selectedModel) {
40+
throw new Error('Model not found')
41+
}
42+
try {
43+
JSON.parse(modelConfig)
44+
} catch (err: any) {
45+
throw new Error('Invalid Model Configuration')
46+
}
47+
try {
48+
JSON.parse(retrieverConfig)
49+
} catch (err: any) {
50+
throw new Error('Invalid Retriever Configuration')
51+
}
52+
53+
const params: CollectionQueryDto = Object.assign(
54+
{
55+
collection_name: selectedCollection,
56+
query: prompt,
57+
model_configuration: {
58+
name: selectedModel.name,
59+
provider: selectedModel.provider,
60+
...JSON.parse(modelConfig),
61+
},
62+
retriever_name: selectedRetriever?.name ?? '',
63+
retriever_config: JSON.parse(retrieverConfig),
64+
prompt_template: promptTemplate,
65+
internet_search_enabled: isInternetSearchEnabled,
66+
},
67+
{},
68+
)
69+
70+
const sseRequest = new SSE(
71+
`${baseQAFoundryPath}/retrievers/${selectedQueryController}/answer`,
72+
{
73+
payload: JSON.stringify({
74+
...params,
75+
stream: true,
76+
}),
77+
headers: {
78+
'Content-Type': 'application/json',
79+
},
80+
},
81+
)
82+
83+
sseRequest.addEventListener('data', (event: any) => {
84+
try {
85+
const parsed = JSON.parse(event.data)
86+
if (parsed?.type === 'answer') {
87+
setAnswer((prevAnswer: string) => prevAnswer + parsed.content)
88+
setIsRunningPrompt(false)
89+
} else if (parsed?.type === 'docs') {
90+
setSourceDocs((prevDocs) => [...prevDocs, ...parsed.content])
91+
}
92+
} catch (err: any) {
93+
throw new Error('An error occurred while processing the response.')
94+
}
95+
})
96+
97+
sseRequest.addEventListener('end', (event: any) => {
98+
sseRequest.close()
99+
})
100+
101+
sseRequest.addEventListener('error', (event: any) => {
102+
sseRequest.close()
103+
setPrompt('')
104+
setIsRunningPrompt(false)
105+
const message = JSON.parse(event.data).detail[0].msg
106+
notifyError('Failed to retrieve answer', { message })
107+
})
108+
} catch (err: any) {
109+
setPrompt('')
110+
setIsRunningPrompt(false)
111+
notifyError('Failed to retrieve answer', err)
112+
}
113+
}
114+
115+
return (
116+
<div className="flex gap-4 items-center">
117+
<form className="w-full relative" onSubmit={(e) => e.preventDefault()}>
118+
<Input
119+
className="w-full min-h-[2.75rem] text-sm pr-14"
120+
placeholder="Ask any question related to this document"
121+
value={prompt}
122+
onChange={(e) => setPrompt(e.target.value)}
123+
/>
124+
<Button
125+
icon="paper-plane-top"
126+
className="btn-sm btn-neutral absolute right-2 top-[0.375rem]"
127+
onClick={handlePromptSubmit}
128+
loading={isRunningPrompt}
129+
disabled={!prompt || !selectedQueryModel}
130+
/>
131+
</form>
132+
</div>
133+
)
134+
}
135+
136+
export default Form
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import React, {
2+
createContext,
3+
useState,
4+
useEffect,
5+
useMemo,
6+
ReactNode,
7+
useContext,
8+
} from 'react'
9+
10+
import {
11+
SourceDocs,
12+
useGetAllEnabledChatModelsQuery,
13+
useGetCollectionNamesQuery,
14+
useGetOpenapiSpecsQuery,
15+
} from '@/stores/qafoundry'
16+
17+
import { DocsQAContextType, SelectedRetrieverType } from './types'
18+
19+
interface DocsQAProviderProps {
20+
children: ReactNode
21+
}
22+
23+
const defaultRetrieverConfig = `{
24+
"search_type": "similarity",
25+
"k": 20,
26+
"fetch_k": 20,
27+
"filter": {}
28+
}`
29+
30+
const defaultModelConfig = `{
31+
"parameters": {
32+
"temperature": 0.1
33+
}
34+
}`
35+
36+
const defaultPrompt =
37+
'Answer the question based only on the following context:\nContext: {context} \nQuestion: {question}'
38+
39+
const DocsQAContext = createContext<DocsQAContextType | undefined>(undefined)
40+
41+
export const DocsQAProvider: React.FC<DocsQAProviderProps> = ({ children }) => {
42+
const [selectedQueryModel, setSelectedQueryModel] = React.useState('')
43+
const [selectedCollection, setSelectedCollection] = useState('')
44+
const [selectedQueryController, setSelectedQueryController] = useState('')
45+
const [selectedRetriever, setSelectedRetriever] = useState<
46+
SelectedRetrieverType | undefined
47+
>()
48+
49+
const [isInternetSearchEnabled, setIsInternetSearchEnabled] = useState(false)
50+
const [retrieverConfig, setRetrieverConfig] = useState(defaultRetrieverConfig)
51+
const [modelConfig, setModelConfig] = useState(defaultModelConfig)
52+
const [promptTemplate, setPromptTemplate] = useState(defaultPrompt)
53+
const [sourceDocs, setSourceDocs] = useState<SourceDocs[]>([])
54+
const [errorMessage, setErrorMessage] = useState(false)
55+
const [answer, setAnswer] = useState('')
56+
const [prompt, setPrompt] = useState('')
57+
58+
const { data: collections, isLoading: isCollectionsLoading } =
59+
useGetCollectionNamesQuery()
60+
const { data: allEnabledModels } = useGetAllEnabledChatModelsQuery()
61+
const { data: openapiSpecs } = useGetOpenapiSpecsQuery()
62+
63+
const allQueryControllers = useMemo(() => {
64+
if (!openapiSpecs?.paths) return []
65+
return Object.keys(openapiSpecs?.paths)
66+
.filter((path) => path.includes('/retrievers/'))
67+
.map((str) => {
68+
var parts = str.split('/')
69+
return parts[2]
70+
})
71+
}, [openapiSpecs])
72+
73+
const allRetrieverOptions = useMemo(() => {
74+
const queryControllerPath = `/retrievers/${selectedQueryController}/answer`
75+
const examples =
76+
openapiSpecs?.paths[queryControllerPath]?.post?.requestBody?.content?.[
77+
'application/json'
78+
]?.examples
79+
if (!examples) return []
80+
return Object.entries(examples).map(([key, value]: [string, any]) => ({
81+
key,
82+
name: value.value.retriever_name,
83+
summary: value.summary,
84+
config: value.value.retriever_config,
85+
promptTemplate: value.value.prompt_template ?? defaultPrompt,
86+
}))
87+
}, [selectedQueryController, openapiSpecs])
88+
89+
const resetQA = () => {
90+
setAnswer('')
91+
setErrorMessage(false)
92+
setPrompt('')
93+
}
94+
95+
useEffect(() => {
96+
if (collections && collections.length) setSelectedCollection(collections[0])
97+
}, [collections])
98+
99+
useEffect(() => {
100+
if (allQueryControllers && allQueryControllers.length)
101+
setSelectedQueryController(allQueryControllers[0])
102+
}, [allQueryControllers])
103+
104+
useEffect(() => {
105+
if (allEnabledModels && allEnabledModels.length) {
106+
setSelectedQueryModel(allEnabledModels[0].name)
107+
}
108+
}, [allEnabledModels])
109+
110+
useEffect(() => {
111+
if (allRetrieverOptions && allRetrieverOptions.length) {
112+
setSelectedRetriever(allRetrieverOptions[0])
113+
setPromptTemplate(allRetrieverOptions[0].promptTemplate)
114+
}
115+
}, [allRetrieverOptions])
116+
117+
useEffect(() => {
118+
if (selectedRetriever)
119+
setRetrieverConfig(JSON.stringify(selectedRetriever.config, null, 2))
120+
}, [selectedRetriever])
121+
122+
const value = {
123+
selectedQueryModel,
124+
selectedCollection,
125+
selectedQueryController,
126+
selectedRetriever,
127+
prompt,
128+
answer,
129+
sourceDocs,
130+
errorMessage,
131+
modelConfig,
132+
retrieverConfig,
133+
promptTemplate,
134+
isInternetSearchEnabled,
135+
collections,
136+
isCollectionsLoading,
137+
allEnabledModels,
138+
allQueryControllers,
139+
allRetrieverOptions,
140+
setSelectedQueryModel,
141+
setSelectedCollection,
142+
setSelectedQueryController,
143+
setSelectedRetriever,
144+
setSourceDocs,
145+
setErrorMessage,
146+
setModelConfig,
147+
setRetrieverConfig,
148+
setPromptTemplate,
149+
setIsInternetSearchEnabled,
150+
resetQA,
151+
setPrompt,
152+
setAnswer,
153+
}
154+
155+
return (
156+
<DocsQAContext.Provider value={value}>{children}</DocsQAContext.Provider>
157+
)
158+
}
159+
160+
export const useDocsQAContext = () => {
161+
const context = useContext(DocsQAContext)
162+
if (!context) {
163+
throw new Error('useDocsQAContext must be used within a DocsQAProvider')
164+
}
165+
return context
166+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
import { DocsQAProvider } from './context'
3+
import DocsQA from './DocsQA'
4+
5+
const index = () => {
6+
return (
7+
<DocsQAProvider>
8+
<DocsQA />
9+
</DocsQAProvider>
10+
)
11+
}
12+
13+
export default index
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export interface SelectedRetrieverType {
2+
key: string
3+
name: string
4+
summary: string
5+
config: any
6+
}
7+
8+
export interface DocsQAContextType {
9+
selectedQueryModel: string
10+
selectedCollection: string
11+
selectedQueryController: string
12+
selectedRetriever: SelectedRetrieverType | undefined
13+
prompt: string
14+
answer: string
15+
sourceDocs: any[]
16+
errorMessage: boolean
17+
modelConfig: string
18+
retrieverConfig: string
19+
promptTemplate: string
20+
isInternetSearchEnabled: boolean
21+
collections: any[] | undefined
22+
isCollectionsLoading: boolean
23+
allEnabledModels: any
24+
allQueryControllers: string[]
25+
allRetrieverOptions: SelectedRetrieverType[]
26+
27+
setSelectedQueryModel: React.Dispatch<React.SetStateAction<string>>
28+
setSelectedCollection: React.Dispatch<React.SetStateAction<string>>
29+
setSelectedQueryController: React.Dispatch<React.SetStateAction<string>>
30+
setSelectedRetriever: React.Dispatch<
31+
React.SetStateAction<SelectedRetrieverType | undefined>
32+
>
33+
setAnswer: React.Dispatch<React.SetStateAction<string>>
34+
setPrompt: React.Dispatch<React.SetStateAction<string>>
35+
setSourceDocs: React.Dispatch<React.SetStateAction<any[]>>
36+
setErrorMessage: React.Dispatch<React.SetStateAction<boolean>>
37+
setModelConfig: React.Dispatch<React.SetStateAction<string>>
38+
setRetrieverConfig: React.Dispatch<React.SetStateAction<string>>
39+
setPromptTemplate: React.Dispatch<React.SetStateAction<string>>
40+
setIsInternetSearchEnabled: React.Dispatch<React.SetStateAction<boolean>>
41+
resetQA: () => void
42+
}

‎frontend/src/types/retrieverTypes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface SelectedRetrieverType {
2+
key: string
3+
name: string
4+
summary: string
5+
config: any
6+
}

0 commit comments

Comments
 (0)
Please sign in to comment.