Skip to content

Commit 77f0c20

Browse files
committed
Begin UI refactor towards unified rag & prompt
1 parent 56e2b86 commit 77f0c20

File tree

9 files changed

+284
-290
lines changed

9 files changed

+284
-290
lines changed

e2e/chat.spec.ts

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -161,32 +161,7 @@ testMatrix.forEach((testConfig) => {
161161
})
162162

163163
if (course) {
164-
test('Course chat RAG feature', async ({ page }) => {
165-
await acceptDisclaimer(page)
166-
await useMockModel(page)
167-
168-
const ragName = `rag-${test.info().workerIndex}-${testConfig.role}`
169-
await page.locator('#rag-index-selector').first().click()
170-
await page.getByRole('menuitem', { name: ragName, exact: true }).click()
171-
172-
await sendChatMessage(page, 'rag')
173-
await closeSendPreference(page)
174-
175-
// Shows file search loading indicator
176-
await expect(page.getByTestId('tool-call-message')).toBeVisible()
177-
178-
// Responds with RAG mock document text
179-
await expect(page.getByTestId('assistant-message')).toContainText('This is the first mock document')
180-
181-
// Source button is visible
182-
await expect(page.getByTestId('file-search-sources')).toBeVisible()
183-
184-
// Sources drawer has been opened and title is visible
185-
await expect(page.getByTestId('sources-header')).toBeVisible()
186-
187-
// Three source items should be visible
188-
await expect(page.getByTestId('sources-truncated-item')).toHaveCount(3)
189-
})
164+
// @todo test course chat RAG feature
190165
}
191166

192167
if (!course) {

src/client/components/ChatV2/ChatV2.tsx

Lines changed: 18 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { enqueueSnackbar } from 'notistack'
77
import { useCallback, useEffect, useRef, useState } from 'react'
88
import { useTranslation } from 'react-i18next'
99
import { useParams, useSearchParams } from 'react-router-dom'
10-
import { DEFAULT_ASSISTANT_INSTRUCTIONS, DEFAULT_MODEL, DEFAULT_MODEL_TEMPERATURE, FREE_MODEL, inProduction, validModels } from '../../../config'
10+
import { DEFAULT_MODEL, DEFAULT_MODEL_TEMPERATURE, FREE_MODEL, inProduction, validModels } from '../../../config'
1111
import type { ChatMessage, MessageGenerationInfo, ToolCallResultEvent } from '../../../shared/chat'
1212
import type { RagIndexAttributes } from '../../../shared/types'
1313
import { getLanguageValue } from '../../../shared/utils'
@@ -21,7 +21,7 @@ import { useCourseRagIndices } from '../../hooks/useRagIndices'
2121
import useRetryTimeout from '../../hooks/useRetryTimeout'
2222
import useUserStatus from '../../hooks/useUserStatus'
2323
import { useAnalyticsDispatch } from '../../stores/analytics'
24-
import type { Course, Prompt } from '../../types'
24+
import type { Course } from '../../types'
2525
import Footer from '../Footer'
2626
import { ChatBox } from './ChatBox'
2727
import { Conversation } from './Conversation'
@@ -32,13 +32,13 @@ import ToolResult from './ToolResult'
3232
import { OutlineButtonBlack } from './general/Buttons'
3333
import { ChatInfo } from './general/ChatInfo'
3434
import RagSelector, { RagSelectorDescription } from './RagSelector'
35-
import { SettingsModal, useUrlPromptId } from './SettingsModal'
35+
import { SettingsModal } from './SettingsModal'
3636
import { useChatStream } from './useChatStream'
37-
import { getCompletionStreamV3 } from './util'
37+
import { postCompletionStreamV3 } from './api'
3838
import PromptSelector from './PromptSelector'
39-
import { useQuery } from '@tanstack/react-query'
4039
import ModelSelector from './ModelSelector'
4140
import { ConversationSplash } from './general/ConversationSplash'
41+
import { PromptStateProvider, usePromptState } from './PromptState'
4242

4343
function useLocalStorageStateWithURLDefault(key: string, defaultValue: string, urlKey: string) {
4444
const [value, setValue] = useLocalStorageState(key, defaultValue)
@@ -58,7 +58,7 @@ function useLocalStorageStateWithURLDefault(key: string, defaultValue: string, u
5858
return [urlValue ?? value, modifiedSetValue] as const
5959
}
6060

61-
export const ChatV2 = () => {
61+
const ChatV2Content = () => {
6262
const { courseId } = useParams()
6363
const isEmbeddedMode = useIsEmbedded()
6464
const theme = useTheme()
@@ -76,8 +76,6 @@ export const ChatV2 = () => {
7676
const localStoragePrefix = courseId ? `course-${courseId}` : 'general'
7777
const [activeModel, setActiveModel] = useLocalStorageStateWithURLDefault('model-v2', DEFAULT_MODEL, 'model')
7878
const [disclaimerStatus, setDisclaimerStatus] = useLocalStorageState<boolean>('disclaimer-status', true)
79-
const [customSystemMessage, setCustomSystemMessage] = useLocalStorageState<string>(`${localStoragePrefix}-chat-instructions`, DEFAULT_ASSISTANT_INSTRUCTIONS)
80-
const [activePrompt, setActivePrompt] = useLocalStorageState<Prompt | undefined>(`${localStoragePrefix}-active-prompt`, undefined)
8179
const [modelTemperature, setModelTemperature] = useLocalStorageStateWithURLDefault(
8280
`${localStoragePrefix}-chat-model-temperature`,
8381
String(DEFAULT_MODEL_TEMPERATURE),
@@ -95,7 +93,7 @@ export const ChatV2 = () => {
9593
const [allowedModels, setAllowedModels] = useState<string[]>([])
9694
const [chatLeftSidePanelOpen, setChatLeftSidePanelOpen] = useState<boolean>(false)
9795
// RAG states
98-
const [ragIndexId, setRagIndexId] = useState<number | undefined>()
96+
const [ragIndexId, _setRagIndexId] = useState<number | undefined>()
9997
const [activeToolResult, setActiveToolResult0] = useState<ToolCallResultEvent | undefined>()
10098
const ragIndex = ragIndices?.find((index) => index.id === ragIndexId)
10199

@@ -125,6 +123,8 @@ export const ChatV2 = () => {
125123

126124
const { t, i18n } = useTranslation()
127125

126+
const { promptInfo } = usePromptState()
127+
128128
const disclaimerInfo = infoTexts?.find((infoText) => infoText.name === 'disclaimer')?.text[i18n.language] ?? null
129129

130130
const { processStream, completion, isStreaming, setIsStreaming, toolCalls, streamController, generationInfo } = useChatStream({
@@ -190,13 +190,11 @@ export const ChatV2 = () => {
190190

191191
const generationInfo: MessageGenerationInfo = {
192192
model: activeModel,
193-
promptInfo: activePrompt
194-
? { id: activePrompt.id, name: activePrompt.name, type: 'saved', systemMessage: activePrompt.systemMessage }
195-
: { type: 'custom', systemMessage: customSystemMessage },
193+
promptInfo,
196194
}
197195

198196
try {
199-
const { tokenUsageAnalysis, stream } = await getCompletionStreamV3({
197+
const { tokenUsageAnalysis, stream } = await postCompletionStreamV3({
200198
generationInfo,
201199
messages: newMessages,
202200
ragIndexId,
@@ -293,7 +291,6 @@ export const ChatV2 = () => {
293291
}
294292
}, [userStatus, course])
295293

296-
const showRagSelector = (ragIndices?.length ?? 0) > 0
297294
const rightMenuOpen = !!activeToolResult
298295
const rightMenuWidth = rightMenuOpen ? '300px' : '0px'
299296
const leftMenuWidth = !isEmbeddedMode ? { md: '250px', lg: '300px' } : { md: '0px', lg: '0px' }
@@ -380,20 +377,13 @@ export const ChatV2 = () => {
380377
}}
381378
>
382379
<LeftMenu
383-
course={course}
384380
handleReset={handleReset}
385381
onClose={() => {
386382
setChatLeftSidePanelOpen(false)
387383
}}
388384
setSettingsModalOpen={setSettingsModalOpen}
389385
setDisclaimerStatus={setDisclaimerStatus}
390-
showRagSelector={showRagSelector}
391-
ragIndex={ragIndex}
392-
setRagIndexId={setRagIndexId}
393-
ragIndices={ragIndices}
394386
messages={messages}
395-
activePrompt={activePrompt}
396-
setActivePrompt={setActivePrompt}
397387
currentModel={activeModel}
398388
setModel={setActiveModel}
399389
availableModels={allowedModels}
@@ -406,17 +396,10 @@ export const ChatV2 = () => {
406396
position: 'fixed',
407397
top: 0,
408398
}}
409-
course={course}
410399
handleReset={handleReset}
411400
setSettingsModalOpen={setSettingsModalOpen}
412401
setDisclaimerStatus={setDisclaimerStatus}
413-
showRagSelector={showRagSelector}
414-
ragIndex={ragIndex}
415-
setRagIndexId={setRagIndexId}
416-
ragIndices={ragIndices}
417402
messages={messages}
418-
activePrompt={activePrompt}
419-
setActivePrompt={setActivePrompt}
420403
currentModel={activeModel}
421404
setModel={setActiveModel}
422405
availableModels={allowedModels}
@@ -575,13 +558,8 @@ export const ChatV2 = () => {
575558
<SettingsModal
576559
open={settingsModalOpen}
577560
setOpen={setSettingsModalOpen}
578-
customSystemMessage={customSystemMessage}
579-
setCustomSystemMessage={setCustomSystemMessage}
580-
activePrompt={activePrompt}
581-
setActivePrompt={setActivePrompt}
582561
modelTemperature={parseFloat(modelTemperature)}
583562
setModelTemperature={(updatedTemperature) => setModelTemperature(String(updatedTemperature))}
584-
course={course}
585563
/>
586564

587565
<DisclaimerModal disclaimer={disclaimerInfo} disclaimerStatus={disclaimerStatus} setDisclaimerStatus={setDisclaimerStatus} />
@@ -596,13 +574,7 @@ const LeftMenu = ({
596574
onClose,
597575
setSettingsModalOpen,
598576
setDisclaimerStatus,
599-
showRagSelector,
600-
ragIndex,
601-
setRagIndexId,
602-
ragIndices,
603577
messages,
604-
activePrompt,
605-
setActivePrompt,
606578
currentModel,
607579
setModel,
608580
availableModels,
@@ -613,13 +585,7 @@ const LeftMenu = ({
613585
onClose?: () => void
614586
setSettingsModalOpen: React.Dispatch<React.SetStateAction<boolean>>
615587
setDisclaimerStatus: React.Dispatch<React.SetStateAction<boolean>>
616-
showRagSelector: boolean
617-
ragIndex?: RagIndexAttributes
618-
setRagIndexId: React.Dispatch<React.SetStateAction<number | undefined>>
619-
ragIndices?: RagIndexAttributes[]
620588
messages: ChatMessage[]
621-
activePrompt: Prompt | undefined
622-
setActivePrompt: (prompt: Prompt | undefined) => void
623589
currentModel: string
624590
setModel: (model: string) => void
625591
availableModels: string[]
@@ -628,11 +594,6 @@ const LeftMenu = ({
628594
const { courseId } = useParams()
629595
const { userStatus, isLoading: statusLoading } = useUserStatus(courseId)
630596
const [isTokenLimitExceeded, setIsTokenLimitExceeded] = useState<boolean>(false)
631-
const urlPromptId = useUrlPromptId()
632-
const { data: myPrompts } = useQuery<Prompt[]>({
633-
queryKey: ['/prompts/my-prompts'],
634-
initialData: [],
635-
})
636597

637598
useEffect(() => {
638599
if (!userStatus) return
@@ -661,28 +622,14 @@ const LeftMenu = ({
661622
{t('chat:emptyConversation')}
662623
</OutlineButtonBlack>
663624
<ModelSelector currentModel={currentModel} setModel={setModel} availableModels={availableModels} isTokenLimitExceeded={isTokenLimitExceeded} />
664-
<PromptSelector
665-
sx={{ width: '100%' }}
666-
coursePrompts={course?.prompts ?? []}
667-
myPrompts={myPrompts}
668-
activePrompt={activePrompt}
669-
setActivePrompt={setActivePrompt}
670-
mandatoryPrompt={course?.prompts.find((p) => p.mandatory)}
671-
urlPrompt={course?.prompts.find((p) => p.id === urlPromptId)}
672-
/>
625+
<PromptSelector sx={{ width: '100%' }} />
673626
<EmailButton messages={messages} disabled={!messages?.length} />
674627
<OutlineButtonBlack startIcon={<Tune />} onClick={() => setSettingsModalOpen(true)} data-testid="settings-button">
675628
{t('chat:settings')}
676629
</OutlineButtonBlack>
677630
<OutlineButtonBlack startIcon={<HelpIcon />} onClick={() => setDisclaimerStatus(true)} data-testid="help-button">
678631
{t('info:title')}
679632
</OutlineButtonBlack>
680-
{course && showRagSelector && (
681-
<>
682-
<RagSelectorDescription />
683-
<RagSelector currentRagIndex={ragIndex} setRagIndex={setRagIndexId} ragIndices={ragIndices ?? []} />
684-
</>
685-
)}
686633
</Box>
687634
</Box>
688635
{onClose && (
@@ -694,3 +641,9 @@ const LeftMenu = ({
694641
</Box>
695642
)
696643
}
644+
645+
export const ChatV2 = () => (
646+
<PromptStateProvider>
647+
<ChatV2Content />
648+
</PromptStateProvider>
649+
)

src/client/components/ChatV2/PromptSelector.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,17 @@ import { useState } from 'react'
44
import { useTranslation } from 'react-i18next'
55
import type { Prompt } from '../../types'
66
import { OutlineButtonBlack } from './general/Buttons'
7+
import { usePromptState } from './PromptState'
8+
9+
const PromptSelector = ({ sx = {}, handleDeletePrompt }: { sx?: object; handleDeletePrompt?: (prompt: Prompt) => void }) => {
10+
const { activePrompt, handleChangePrompt, mandatoryPrompt, coursePrompts, myPrompts } = usePromptState()
711

8-
const PromptSelector = ({
9-
sx = {},
10-
coursePrompts,
11-
myPrompts,
12-
activePrompt,
13-
setActivePrompt,
14-
handleDeletePrompt,
15-
mandatoryPrompt,
16-
}: {
17-
sx?: object
18-
coursePrompts: Prompt[]
19-
myPrompts: Prompt[]
20-
activePrompt?: Prompt
21-
setActivePrompt: (prompt: Prompt | undefined) => void
22-
handleDeletePrompt?: (prompt: Prompt) => void
23-
mandatoryPrompt?: Prompt
24-
urlPrompt?: Prompt
25-
}) => {
2612
const { t } = useTranslation()
2713

2814
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
2915

3016
const handleSelect = (prompt?: Prompt) => {
31-
setActivePrompt(prompt)
17+
handleChangePrompt(prompt)
3218
setAnchorEl(null)
3319
}
3420

0 commit comments

Comments
 (0)