diff --git a/infra/main_custom.bicep b/infra/main_custom.bicep index 9c71835f8..26cffaa5b 100644 --- a/infra/main_custom.bicep +++ b/infra/main_custom.bicep @@ -1102,6 +1102,10 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { {name: 'AZURE-OPENAI-PREVIEW-API-VERSION', value: azureOpenaiAPIVersion} {name: 'AZURE-OPEN-AI-DEPLOYMENT-MODEL', value: gptModelName} {name: 'TENANT-ID', value: subscription().tenantId} + { + name: 'AZURE-AI-AGENT-ENDPOINT' + value: aiFoundryAiProjectEndpoint + } ] } dependsOn:[ @@ -1160,7 +1164,8 @@ module webSite 'modules/web-sites.bicep' = { { name: 'appsettings' properties: { - SCM_DO_BUILD_DURING_DEPLOYMENT: 'false' + SCM_DO_BUILD_DURING_DEPLOYMENT: 'True' + ENABLE_ORYX_BUILD: 'True' AUTH_ENABLED: 'false' AZURE_SEARCH_SERVICE: aiSearch.outputs.name AZURE_SEARCH_INDEX: azureSearchIndex diff --git a/infra/scripts/index_scripts/02_process_data.py b/infra/scripts/index_scripts/02_process_data.py index 4b01e3f17..12fa617e5 100644 --- a/infra/scripts/index_scripts/02_process_data.py +++ b/infra/scripts/index_scripts/02_process_data.py @@ -177,6 +177,6 @@ def prepare_search_doc(content, document_id): docs = [] if docs != []: - results = search_client.upload_documents(documents=docs) + search_client.upload_documents(documents=docs) print(f'{str(counter)} files processed.') diff --git a/scripts/data_utils.py b/scripts/data_utils.py index 1e8dc44cf..e5cf535be 100644 --- a/scripts/data_utils.py +++ b/scripts/data_utils.py @@ -157,7 +157,13 @@ def extract_caption(self, text): def mask_urls_and_imgs(self, text) -> Tuple[Dict[str, str], str]: def find_urls(string): - regex = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^()\s<>]+|\(([^()\s<>]+|(\([^()\s<>]+\)))*\))+(?:\(([^()\s<>]+|(\([^()\s<>]+\)))*\)|[^()\s`!()\[\]{};:'\".,<>?«»“”‘’]))" + regex = ( + r"(?i)\b(" + r"(?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)" + r"(?:[^()\s<>]+|\(([^()\s<>]+|(\([^()\s<>]+\)))*\))+" + r"(?:\(([^()\s<>]+|(\([^()\s<>]+\)))*\)|[^()\s`!()\[\]{};:'\".,<>?«»“”‘’])" + r")" + ) urls = re.findall(regex, string) return [x[0] for x in urls] @@ -693,7 +699,9 @@ def extract_pdf_content(file_path, form_recognizer_client, use_layout=False): page_map = [] model = "prebuilt-layout" if use_layout else "prebuilt-read" - base64file = base64.b64encode(open(file_path, "rb").read()).decode() + with open(file_path, "rb") as f: + file_bytes = f.read() + base64file = base64.b64encode(file_bytes).decode() poller = form_recognizer_client.begin_analyze_document( model, AnalyzeDocumentRequest(bytes_source=base64file) ) @@ -1048,7 +1056,8 @@ def image_content_to_tag(image_content: str) -> str: def get_caption(image_path, captioning_model_endpoint, captioning_model_key): - encoded_image = base64.b64encode(open(image_path, "rb").read()).decode("ascii") + with open(image_path, "rb") as image_file: + encoded_image = base64.b64encode(image_file.read()).decode("ascii") file_ext = image_path.split(".")[-1] headers = { "Content-Type": "application/json", diff --git a/src/app.py b/src/app.py index a7bf5ffea..7416c472d 100644 --- a/src/app.py +++ b/src/app.py @@ -167,7 +167,6 @@ async def init_ai_foundry_client(): return ai_foundry_client except Exception as e: logging.exception("Exception in AI Foundry initialization", e) - ai_foundry_client = None raise e @@ -197,7 +196,6 @@ def init_cosmosdb_client(): if span is not None: span.record_exception(e) span.set_status(Status(StatusCode.ERROR, str(e))) - cosmos_conversation_client = None raise e else: logging.debug("CosmosDB not configured") diff --git a/src/backend/history/cosmosdbservice.py b/src/backend/history/cosmosdbservice.py index 9add46ea2..66e19b514 100644 --- a/src/backend/history/cosmosdbservice.py +++ b/src/backend/history/cosmosdbservice.py @@ -110,7 +110,7 @@ async def delete_messages(self, conversation_id, user_id): item=message["id"], partition_key=user_id ) response_list.append(resp) - return response_list + return response_list async def get_conversations(self, user_id, limit, sort_order="DESC", offset=0): parameters = [{"name": "@userId", "value": user_id}] diff --git a/src/backend/settings.py b/src/backend/settings.py index 206a7d3b7..87c589c86 100644 --- a/src/backend/settings.py +++ b/src/backend/settings.py @@ -249,8 +249,9 @@ def split_contexts( class DatasourcePayloadConstructor(BaseModel, ABC): _settings: "_AppSettings" = PrivateAttr() - def __init__(self, settings: "_AppSettings", **data): - super().__init__(**data) + def __init__(self, *args, settings: "_AppSettings", **data): + # Call next __init__ in MRO to allow cooperative multiple inheritance + super().__init__(*args, **data) self._settings = settings @abstractmethod @@ -302,6 +303,10 @@ class _AzureSearchSettings(BaseSettings, DatasourcePayloadConstructor): fields_mapping: Optional[dict] = None filter: Optional[str] = Field(default=None, exclude=True) + def __init__(self, settings: "_AppSettings", **data): + # Ensure both BaseSettings and DatasourcePayloadConstructor are initialized + super().__init__(settings=settings, **data) + @field_validator("content_columns", "vector_columns", mode="before") @classmethod def split_columns(cls, comma_separated_string: str) -> List[str]: @@ -439,6 +444,7 @@ def set_datasource_settings(self) -> Self: logging.warning( "No datasource configuration found in the environment -- calls will be made to Azure OpenAI without grounding data." ) + return self app_settings = _AppSettings() diff --git a/src/frontend/__mocks__/react-markdown.tsx b/src/frontend/__mocks__/react-markdown.tsx index 9e5efd5dc..df4c3bad2 100644 --- a/src/frontend/__mocks__/react-markdown.tsx +++ b/src/frontend/__mocks__/react-markdown.tsx @@ -2,15 +2,9 @@ import React from 'react'; -// Mock implementation of react-markdown -const mockNode = { - children: [{ value: 'console.log("Test Code");' }] -}; -const mockProps = { className: 'language-javascript' }; - const ReactMarkdown: React.FC<{ children: React.ReactNode , components: any }> = ({ children,components }) => { return
- {/* {components && components.code({ node: mockNode, ...mockProps })} */} + {/* {components && components.code({ node: { children: [{ value: 'console.log("Test Code");' }] }, ...mockProps })} */} {children}
; // Simply render the children }; diff --git a/src/frontend/src/api/models.ts b/src/frontend/src/api/models.ts index af2d89850..511783357 100644 --- a/src/frontend/src/api/models.ts +++ b/src/frontend/src/api/models.ts @@ -1,5 +1,3 @@ -import Plotly from 'react-plotly.js' - export type AskResponse = { answer: string citations: Citation[] diff --git a/src/frontend/src/components/Answer/Answer.test.tsx b/src/frontend/src/components/Answer/Answer.test.tsx index bf1a509c4..7e4a3817d 100644 --- a/src/frontend/src/components/Answer/Answer.test.tsx +++ b/src/frontend/src/components/Answer/Answer.test.tsx @@ -1,11 +1,9 @@ -import { renderWithContext, screen, waitFor, fireEvent, act, logRoles } from '../../test/test.utils'; +import { renderWithContext, screen, waitFor, fireEvent, act } from '../../test/test.utils'; import { Answer } from './Answer' -import { AppStateContext } from '../../state/AppProvider' import {AskResponse, Citation, Feedback, historyMessageFeedback } from '../../api'; //import { Feedback, AskResponse, Citation } from '../../api/models' import { cloneDeep } from 'lodash' import userEvent from '@testing-library/user-event'; -import { CitationPanel } from '../../pages/chat/Components/CitationPanel'; // Mock required modules and functions jest.mock('../../api/api', () => ({ @@ -27,9 +25,6 @@ jest.mock('remark-gfm', () => jest.fn()); jest.mock('rehype-raw', () => jest.fn()); jest.mock('remark-supersub', () => jest.fn()); -const mockDispatch = jest.fn(); -const mockOnCitationClicked = jest.fn(); - // Mock context provider values let mockAppState = { frontendSettings: { feedback_enabled: true, sanitize_answer: true }, @@ -360,7 +355,6 @@ describe('Answer Component', () => { it('should open and submit negative feedback dialog', async () => { userEvent.setup(); renderComponent(); - const handleChange = jest.fn(); const dislikeButton = screen.getByLabelText('Dislike this response'); // Click dislike to open dialog @@ -368,7 +362,7 @@ describe('Answer Component', () => { expect(screen.getByText("Why wasn't this response helpful?")).toBeInTheDocument(); // Select feedback and submit - const checkboxEle = await screen.findByLabelText(/Citations are wrong/i) + const checkboxEle = await screen.findByLabelText(/Citations are wrong/i); //logRoles(checkboxEle) await waitFor(() => { userEvent.click(checkboxEle); @@ -382,12 +376,8 @@ describe('Answer Component', () => { it('calls resetFeedbackDialog and setFeedbackState with Feedback.Neutral on dialog dismiss', async () => { - const resetFeedbackDialogMock = jest.fn(); - const setFeedbackStateMock = jest.fn(); - userEvent.setup(); renderComponent(); - const handleChange = jest.fn(); const dislikeButton = screen.getByLabelText('Dislike this response'); // Click dislike to open dialog @@ -410,7 +400,6 @@ describe('Answer Component', () => { it('Dialog Options should be able to select and unSelect', async () => { userEvent.setup(); renderComponent(); - const handleChange = jest.fn(); const dislikeButton = screen.getByLabelText('Dislike this response'); // Click dislike to open dialog @@ -419,7 +408,7 @@ describe('Answer Component', () => { expect(screen.getByText("Why wasn't this response helpful?")).toBeInTheDocument(); // Select feedback and submit - const checkboxEle = await screen.findByLabelText(/Citations are wrong/i) + const checkboxEle = await screen.findByLabelText(/Citations are wrong/i); expect(checkboxEle).not.toBeChecked(); await userEvent.click(checkboxEle); @@ -427,7 +416,7 @@ describe('Answer Component', () => { expect(checkboxEle).toBeChecked(); }); - const checkboxEle1 = await screen.findByLabelText(/Citations are wrong/i) + const checkboxEle1 = await screen.findByLabelText(/Citations are wrong/i); await userEvent.click(checkboxEle1); await waitFor(() => { @@ -439,7 +428,6 @@ describe('Answer Component', () => { it('Should able to show ReportInappropriateFeedbackContent form while click on "InappropriateFeedback" button ', async () => { userEvent.setup(); renderComponent(); - const handleChange = jest.fn(); const dislikeButton = screen.getByLabelText('Dislike this response'); // Click dislike to open dialog @@ -514,7 +502,6 @@ describe('Answer Component', () => { feedbackState: { '123': Feedback.OtherHarmful }, } renderComponent(answerWithMissingFeedback, extraMockState); - const handleChange = jest.fn(); const dislikeButton = screen.getByLabelText('Dislike this response'); // Click dislike to open dialog @@ -529,10 +516,6 @@ describe('Answer Component', () => { tempMockCitation[0].filepath = ''; tempMockCitation[0].reindex_id = ''; - const answerWithMissingFeedback = { - ...mockAnswerProps, - CitationPanel: [...tempMockCitation] - } renderComponent(); diff --git a/src/frontend/src/components/Answer/Answer.tsx b/src/frontend/src/components/Answer/Answer.tsx index dcf5574d0..2a3c290ed 100644 --- a/src/frontend/src/components/Answer/Answer.tsx +++ b/src/frontend/src/components/Answer/Answer.tsx @@ -39,7 +39,6 @@ export const Answer = ({ answer, onCitationClicked }: Props) => { } const [isRefAccordionOpen, { toggle: toggleIsRefAccordionOpen }] = useBoolean(false) - const filePathTruncationLimit = 50 const parsedAnswer = useMemo(() => parseAnswer(answer), [answer]) const [chevronIsExpanded, setChevronIsExpanded] = useState(isRefAccordionOpen) @@ -156,7 +155,7 @@ export const Answer = ({ answer, onCitationClicked }: Props) => { const onLikeResponseClicked = async () => { if (answer.message_id == undefined) return - let newFeedbackState = feedbackState + let newFeedbackState: Feedback.Neutral | Feedback.Positive // Set or unset the thumbs up state if (feedbackState == Feedback.Positive) { newFeedbackState = Feedback.Neutral @@ -176,7 +175,7 @@ export const Answer = ({ answer, onCitationClicked }: Props) => { const onDislikeResponseClicked = async () => { if (answer.message_id == undefined) return - let newFeedbackState = feedbackState + let newFeedbackState: Feedback.Neutral | Feedback.Negative if (feedbackState === undefined || feedbackState === Feedback.Neutral || feedbackState === Feedback.Positive) { newFeedbackState = Feedback.Negative setFeedbackState(newFeedbackState) diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx b/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx index d4cff2bf0..b14b50039 100644 --- a/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx +++ b/src/frontend/src/components/ChatHistory/ChatHistoryListItem.tsx @@ -81,10 +81,6 @@ export const ChatHistoryListItemCell: React.FC = ( styles: { main: { maxWidth: 450 } } } - const tooltipStyles: Partial = { - root: { display: 'inline-block', maxWidth: '80%' } - }; - if (!item) { return null } diff --git a/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx b/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx index a3b31c6c7..37fd7f27f 100644 --- a/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx +++ b/src/frontend/src/components/ChatHistory/ChatHistoryPanel.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import { AppStateContext } from '../../state/AppProvider'; import { ChatHistoryPanel } from './ChatHistoryPanel'; -import { ChatHistoryLoadingState, ChatMessage, Conversation, CosmosDBStatus, Feedback, historyDeleteAll,historyList } from '../../api'; +import { ChatHistoryLoadingState, ChatMessage, Conversation, CosmosDBStatus, Feedback } from '../../api'; import * as api from '../../api'; import {defaultMockState} from '../../test/test.utils'; diff --git a/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx b/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx index 7fe93195c..a7eccb4c9 100644 --- a/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx +++ b/src/frontend/src/components/ChatHistory/chatHistoryListItem.test.tsx @@ -1,10 +1,9 @@ -import { renderWithContext, screen, waitFor, fireEvent, act, findByText, render } from '../../test/test.utils' +import { renderWithContext, screen, waitFor, fireEvent, act } from '../../test/test.utils' import { ChatHistoryListItemCell, ChatHistoryListItemGroups } from './ChatHistoryListItem' import { Conversation } from '../../api/models' import { historyRename, historyDelete, historyList, historyRead } from '../../api' -import React, { useEffect } from 'react' +import React from 'react' import userEvent from '@testing-library/user-event' -import { AppStateContext } from '../../state/AppProvider' // Mock API jest.mock('../../api/api', () => ({ @@ -555,13 +554,6 @@ describe('ChatHistoryListItemCell', () => { test('shows error when trying to rename to an existing title', async () => { const existingTitle = 'Existing Chat Title' - const conversationWithExistingTitle: Conversation = { - id: '2', - title: existingTitle, - messages: [], - date: new Date().toISOString() - } - ; (historyRename as jest.Mock).mockResolvedValueOnce({ ok: false, json: async () => ({ message: 'Title already exists' }) diff --git a/src/frontend/src/components/DraftCards/TitleCard.test.tsx b/src/frontend/src/components/DraftCards/TitleCard.test.tsx index 04ebb4425..ce4a2b73a 100644 --- a/src/frontend/src/components/DraftCards/TitleCard.test.tsx +++ b/src/frontend/src/components/DraftCards/TitleCard.test.tsx @@ -4,16 +4,6 @@ import { render, screen, fireEvent } from '@testing-library/react'; import TitleCard from './TitleCard'; import { AppStateContext } from '../../state/AppProvider'; -const contextValue = { - state: { - draftedDocumentTitle: null, - isChatHistoryOpen: false, - chatHistoryLoadingState: 'idle', - isCosmosDBAvailable: true, - chatHistory: [], - }, - }; - const mockDispatch = jest.fn(); const renderWithContext = (contextValue : any) => { diff --git a/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx b/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx index 75ac5ba37..d01da5f95 100644 --- a/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx +++ b/src/frontend/src/components/FeatureCard/FeatureCard.test.tsx @@ -35,7 +35,7 @@ describe('FeatureCard', () => { }; test('renders correctly with the provided props', () => { - const { getByText, getByRole } = renderFeatureCard(props); + const { getByText } = renderFeatureCard(props); // Check if title and description are rendered correctly expect(getByText(props.title)).toBeInTheDocument(); @@ -108,7 +108,7 @@ describe('FeatureCard', () => { // Mock the useNavigate hook to return our mock function require('react-router-dom').useNavigate.mockReturnValue(mockNavigate); - const { getByText, queryByText } = renderFeatureCard({ + const { getByText } = renderFeatureCard({ icon: props.icon, urlSuffix: props.urlSuffix, title: "Feature Card", diff --git a/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx b/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx index e1d4e1e97..c2c26906f 100644 --- a/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx +++ b/src/frontend/src/components/QuestionInput/QuestionInput.test.tsx @@ -1,6 +1,5 @@ import { render, fireEvent, screen } from '@testing-library/react' import { QuestionInput } from './QuestionInput' -import { SendRegular } from '@fluentui/react-icons' // Mocking the Send SVG import jest.mock('../../assets/Send.svg', () => 'mock-send-svg') diff --git a/src/frontend/src/components/Sidebar/Sidebar.test.tsx b/src/frontend/src/components/Sidebar/Sidebar.test.tsx index 7f4ff89bf..b9264ef6a 100644 --- a/src/frontend/src/components/Sidebar/Sidebar.test.tsx +++ b/src/frontend/src/components/Sidebar/Sidebar.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { render, screen,fireEvent,act } from '@testing-library/react'; +import { render, screen,fireEvent } from '@testing-library/react'; import { AppStateContext } from '../../state/AppProvider'; import Sidebar from './Sidebar'; import { ChatHistoryLoadingState } from '../../api/models'; -import { BrowserRouter as Router, useLocation ,useNavigate} from 'react-router-dom'; +import { BrowserRouter as Router, useLocation } from 'react-router-dom'; import { getUserInfo } from '../../api'; import {defaultMockState} from '../../test/test.utils'; @@ -187,7 +187,6 @@ describe('Sidebar', () => { }); it('returns the correct view based on the current URL', () => { - const mockUseLocation = jest.fn(); (useLocation as jest.Mock).mockReturnValue({ pathname: '/draft' }); renderSidebar(); diff --git a/src/frontend/src/components/Sidebar/Sidebar.tsx b/src/frontend/src/components/Sidebar/Sidebar.tsx index 9bde2c9a1..6acc09a84 100644 --- a/src/frontend/src/components/Sidebar/Sidebar.tsx +++ b/src/frontend/src/components/Sidebar/Sidebar.tsx @@ -2,12 +2,8 @@ import React, { useEffect, useState, useContext } from 'react' import { Stack, Text } from '@fluentui/react' import { Book28Regular, - Book32Regular, - BookRegular, News28Regular, - NewsRegular, Notepad28Regular, - Notepad32Regular } from '@fluentui/react-icons' import { Button, Avatar } from '@fluentui/react-components' import styles from './Sidebar.module.css' @@ -85,15 +81,7 @@ const Sidebar = (): JSX.Element => { const navigate = useNavigate() const location = useLocation() const [name, setName] = useState('') - useEffect(() => { - if(appStateContext?.state.isRequestInitiated == true){ - NavigationButtonStates.Disabled - } - else{ - NavigationButtonStates.Active - } -}) - + useEffect(() => { if (!appStateContext) { throw new Error('useAppState must be used within a AppStateProvider') diff --git a/src/frontend/src/constants/chatHistory.test.tsx b/src/frontend/src/constants/chatHistory.test.tsx index e5cef1e8b..27e033620 100644 --- a/src/frontend/src/constants/chatHistory.test.tsx +++ b/src/frontend/src/constants/chatHistory.test.tsx @@ -1,5 +1,4 @@ import { chatHistorySampleData } from './chatHistory'; -import { Conversation } from '../api/models'; describe('chatHistorySampleData', () => { it('should be an array of Conversation objects', () => { diff --git a/src/frontend/src/helpers/helpers.ts b/src/frontend/src/helpers/helpers.ts index 8816726e5..1ed90ed3a 100644 --- a/src/frontend/src/helpers/helpers.ts +++ b/src/frontend/src/helpers/helpers.ts @@ -1,4 +1,4 @@ -import { Conversation, ChatMessage, ToolMessageContent, Citation } from '../api/models' +import { ChatMessage } from '../api/models' // -------------Chat.tsx------------- diff --git a/src/frontend/src/pages/chat/Chat.test.tsx b/src/frontend/src/pages/chat/Chat.test.tsx index a4f2f7240..5355a816a 100644 --- a/src/frontend/src/pages/chat/Chat.test.tsx +++ b/src/frontend/src/pages/chat/Chat.test.tsx @@ -1,6 +1,6 @@ import { renderWithContext, screen, waitFor, fireEvent, act } from '../../test/test.utils' import Chat from './Chat' -import { ChatHistoryLoadingState,ChatType } from '../../api/models' +import { ChatType } from '../../api/models' import * as ReactRouterDom from 'react-router-dom'; import { getUserInfo, @@ -9,13 +9,11 @@ import { historyClear, ChatMessage, Citation, - historyUpdate, - CosmosDBStatus + historyUpdate } from '../../api' import userEvent from '@testing-library/user-event' -import { AIResponseContent, decodedConversationResponseWithCitations } from '../../../__mocks__/mockAPIData' -import { CitationPanel } from './Components/CitationPanel' +import { decodedConversationResponseWithCitations } from '../../../__mocks__/mockAPIData' // import { BuildingCheckmarkRegular } from '@fluentui/react-icons'; @@ -108,8 +106,6 @@ jest.mock('../../components/ChatHistory/ChatHistoryPanel', () => ({ ChatHistoryPanel: jest.fn(() =>
ChatHistoryPanelMock
) })) - -const mockDispatch = jest.fn() const originalHostname = window.location.hostname const mockState = { @@ -316,8 +312,6 @@ const addToExistResponse = { //-----ConversationAPI Response -const response4 = {} - let originalFetch: typeof global.fetch describe('Chat Component', () => { @@ -567,7 +561,6 @@ describe('Chat Component', () => { mockCallConversationApi.mockResolvedValueOnce({ ...mockResponse }) } - const setIsVisible = jest.fn() beforeEach(() => { jest.clearAllMocks() originalFetch = global.fetch diff --git a/src/frontend/src/pages/chat/Chat.tsx b/src/frontend/src/pages/chat/Chat.tsx index 185f2ed8c..a13b5b569 100644 --- a/src/frontend/src/pages/chat/Chat.tsx +++ b/src/frontend/src/pages/chat/Chat.tsx @@ -6,15 +6,13 @@ import { Dialog, DialogType, Stack, - Modal, IStackTokens, mergeStyleSets, IModalStyles, - PrimaryButton, Spinner, SpinnerSize } from '@fluentui/react' -import { SquareRegular, ShieldLockRegular, ErrorCircleRegular } from '@fluentui/react-icons' +import { SquareRegular } from '@fluentui/react-icons' import uuid from 'react-uuid' import { isEmpty } from 'lodash' @@ -26,7 +24,6 @@ import { ConversationRequest, conversationApi, Citation, - ToolMessageContent, ChatResponse, getUserInfo, Conversation, @@ -55,50 +52,10 @@ const enum messageStatus { Done = 'Done' } -// Define stack tokens for spacing -const stackTokens: IStackTokens = { childrenGap: 20 } - -// Define custom styles for the modal -const modalStyles: IModalStyles = { - main: { - maxWidth: '80%', - minHeight: '40%', - padding: '20px', - backgroundColor: '#f3f2f1', - borderRadius: '8px', - overflowY: 'hidden' - }, - root: undefined, - scrollableContent: { - minWidth: '800px' - }, - layer: undefined, - keyboardMoveIconContainer: undefined, - keyboardMoveIcon: undefined -} - -// Define custom styles for the content inside the modal -const contentStyles = mergeStyleSets({ - iframe: { - width: '100%', - height: '55vh', - border: 'none' - }, - closeButton: { - marginTop: '20px', - alignSelf: 'flex-end' - } -}) - interface Props { type?: ChatType } - -const renderLink = (props: any) => { - return ; -}; - const Chat = ({ type = ChatType.Browse }: Props) => { const location = useLocation() @@ -114,21 +71,18 @@ const Chat = ({ type = ChatType.Browse }: Props) => { const abortFuncs = useRef([] as AbortController[]) const [showAuthMessage, setShowAuthMessage] = useState() const [messages, setMessages] = useState([]) - const [jsonDraftDocument, setJSONDraftDocument] = useState('') + const [, setJSONDraftDocument] = useState('') const [draftDocument, setDraftDocument] = useState() const [processMessages, setProcessMessages] = useState(messageStatus.NotRunning) const [clearingChat, setClearingChat] = useState(false) const [hideErrorDialog, { toggle: toggleErrorDialog }] = useBoolean(true) const [errorMsg, setErrorMsg] = useState() - const [isModalOpen, setIsModalOpen] = useState(false) - const [modalUrl, setModalUrl] = useState(''); - const [finalMessages, setFinalMessages] = useState([]) + const [, setModalUrl] = useState(''); if (!appStateContext || !appStateContext.state) { console.error("AppStateContext is undefined. Ensure AppProvider wraps this component."); return null; // Prevents execution if context is missing } const [loadingState, setLoadingState] = useState(appStateContext.state.isLoading); - const { currentChat } = appStateContext?.state; const errorDialogContentProps = { type: DialogType.close, @@ -575,13 +529,7 @@ const Chat = ({ type = ChatType.Browse }: Props) => { ? resultConversation.messages.push(assistantMessage) : resultConversation.messages.push(toolMessage, assistantMessage) } - if (!resultConversation) { - setIsLoading(false) - appStateContext?.dispatch({ type: 'GENERATE_ISLODING', payload: false }) - setShowLoadingMessage(false) - abortFuncs.current = abortFuncs.current.filter(a => a !== abortController) - return - } + appStateContext?.dispatch({ type: 'UPDATE_CURRENT_CHAT', payload: resultConversation }) isEmpty(toolMessage) ? setMessages([...messages, assistantMessage]) @@ -644,13 +592,7 @@ const Chat = ({ type = ChatType.Browse }: Props) => { } resultConversation.messages.push(errorChatMsg) } - if (!resultConversation) { - setIsLoading(false) - appStateContext?.dispatch({ type: 'GENERATE_ISLODING', payload: false }) - setShowLoadingMessage(false) - abortFuncs.current = abortFuncs.current.filter(a => a !== abortController) - return - } + appStateContext?.dispatch({ type: 'UPDATE_CURRENT_CHAT', payload: resultConversation }) setMessages([...messages, errorChatMsg]) } else { @@ -810,12 +752,6 @@ const Chat = ({ type = ChatType.Browse }: Props) => { const onShowCitation = (citation: Citation) => { const url = citation.url setModalUrl(url ?? '') - setIsModalOpen(true) - } - - const onCloseModal = () => { - setIsModalOpen(false) - setModalUrl('') } const onViewSource = (citation: Citation) => { diff --git a/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx b/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx index a47a1e4d3..fbb818bc0 100644 --- a/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx +++ b/src/frontend/src/pages/chat/Components/AuthNotConfigure.test.tsx @@ -2,7 +2,6 @@ import React from 'react' import { render, screen } from '@testing-library/react' import '@testing-library/jest-dom' import { AuthNotConfigure } from './AuthNotConfigure' -import styles from '../Chat.module.css' // Mock the Fluent UI icons jest.mock('@fluentui/react-icons', () => ({ diff --git a/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx b/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx index ce4083322..35bfdb5e0 100644 --- a/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx +++ b/src/frontend/src/pages/chat/Components/ChatMessageContainer.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { ChatMessageContainer } from './ChatMessageContainer'; -import { ChatMessage, Citation,ChatType } from '../../../api/models'; +import { ChatMessage, ChatType } from '../../../api/models'; import { Answer } from '../../../components/Answer'; jest.mock('../../../components/Answer', () => ({ @@ -74,7 +74,7 @@ describe('ChatMessageContainer', () => { "date": "2024-11-07T09:37:30.581Z" }, - ] + ]; it('renders user and assistant messages correctly', () => { render( @@ -199,6 +199,6 @@ describe('ChatMessageContainer', () => { /> ); expect(screen.getByText(/Generating template...this may take up to 30 seconds./)).toBeInTheDocument(); - expect(screen.getByText(/Generate promissory note with a proposed $100,000 for Washington State/)).toBeInTheDocument(); + expect(screen.getByText(/Generate promissory note with a proposed \$100,000 for Washington State/)).toBeInTheDocument(); }); }); diff --git a/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx b/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx index 5e38e4b48..0c3e2ae6d 100644 --- a/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx +++ b/src/frontend/src/pages/chat/Components/ChatMessageContainer.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect, useContext, useLayoutEffect, forwardRef } from 'react'; +import { forwardRef } from 'react'; import styles from '../Chat.module.css'; import { Answer } from '../../../components/Answer'; import { parseCitationFromMessage, generateTemplateSections } from '../../../helpers/helpers'; diff --git a/src/frontend/src/pages/document/Document.tsx b/src/frontend/src/pages/document/Document.tsx index ea49adbb6..a22d94733 100644 --- a/src/frontend/src/pages/document/Document.tsx +++ b/src/frontend/src/pages/document/Document.tsx @@ -11,7 +11,6 @@ interface DocumentData { const Document = (): JSX.Element => { const params = useParams(); const [document, setDocument] = useState(null); - const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); // Step 1 useEffect(() => { diff --git a/src/frontend/src/pages/draft/Draft.test.tsx b/src/frontend/src/pages/draft/Draft.test.tsx index abcd558f6..0c963b934 100644 --- a/src/frontend/src/pages/draft/Draft.test.tsx +++ b/src/frontend/src/pages/draft/Draft.test.tsx @@ -1,13 +1,10 @@ import { render, screen, fireEvent, act, waitFor } from '@testing-library/react' -import { BrowserRouter } from 'react-router-dom' import { AppStateContext } from '../../state/AppProvider' import Draft from './Draft' -import { Section } from '../../api/models' import { saveAs } from 'file-saver' -import { defaultMockState } from '../../test/test.utils'; import { MemoryRouter } from 'react-router-dom'; -import { Document, Packer, Paragraph, TextRun } from 'docx' +import { Document} from 'docx' // Mocks for third-party components and modules diff --git a/src/frontend/src/pages/draft/Draft.tsx b/src/frontend/src/pages/draft/Draft.tsx index a02fa3e14..da6ebfe01 100644 --- a/src/frontend/src/pages/draft/Draft.tsx +++ b/src/frontend/src/pages/draft/Draft.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useMemo, useState } from 'react' import styles from './Draft.module.css' -import { useLocation, useNavigate } from 'react-router-dom' +import { useNavigate } from 'react-router-dom' import TitleCard from '../../components/DraftCards/TitleCard' import SectionCard from '../../components/DraftCards/SectionCard' import { Document, Packer, Paragraph, TextRun } from 'docx' @@ -11,7 +11,6 @@ import { Section } from '../../api/models' const Draft = (): JSX.Element => { const appStateContext = useContext(AppStateContext) - const location = useLocation() const navigate = useNavigate() // get draftedDocument from context diff --git a/src/frontend/src/pages/landing/Landing.tsx b/src/frontend/src/pages/landing/Landing.tsx index 2fcb5b450..44332762a 100644 --- a/src/frontend/src/pages/landing/Landing.tsx +++ b/src/frontend/src/pages/landing/Landing.tsx @@ -1,4 +1,4 @@ -import { useRef, useState, useEffect, useContext, useLayoutEffect } from 'react' +import { useContext } from 'react' import { Stack } from '@fluentui/react' import styles from './Landing.module.css' import Contoso from '../../assets/Contoso.svg' diff --git a/src/frontend/src/state/AppProvider.tsx b/src/frontend/src/state/AppProvider.tsx index 7189731c9..f0238bc40 100644 --- a/src/frontend/src/state/AppProvider.tsx +++ b/src/frontend/src/state/AppProvider.tsx @@ -10,8 +10,7 @@ import { Feedback, FrontendSettings, frontendSettings, - historyEnsure, - historyList + historyEnsure } from '../api' import { appStateReducer } from './AppReducer' diff --git a/src/frontend/src/state/AppReducer.tsx b/src/frontend/src/state/AppReducer.tsx index 8e9ebd967..4a6712324 100644 --- a/src/frontend/src/state/AppReducer.tsx +++ b/src/frontend/src/state/AppReducer.tsx @@ -108,16 +108,16 @@ export const appStateReducer = (state: AppState, action: Action): AppState => { case 'SET_IS_REQUEST_INITIATED' : return {...state, isRequestInitiated : action.payload} case 'ADD_FAILED_SECTION': - var tempFailedSections = [...state.failedSections]; - const exists = tempFailedSections.some((item) => item.title === action.payload.title); - if (!exists) + const tempFailedSections = [...state.failedSections]; + const exists = tempFailedSections.some((item) => item.title === action.payload.title); + if (!exists) { tempFailedSections.push(action.payload); - return { ...state , failedSections : [...tempFailedSections] } - case 'REMOVED_FAILED_SECTION' : - var tempFailedSections = [...state.failedSections]; - tempFailedSections = state.failedSections.filter((item) => item.title !== action.payload.section.title); - return { ...state , failedSections : [...tempFailedSections] } - case 'UPDATE_SECTION_API_REQ_STATUS' : + } + return { ...state, failedSections: [...tempFailedSections] } + case 'REMOVED_FAILED_SECTION': + const filteredFailedSections = state.failedSections.filter((item) => item.title !== action.payload.section.title); + return { ...state, failedSections: filteredFailedSections } + case 'UPDATE_SECTION_API_REQ_STATUS': return {...state, isFailedReqInitiated : action.payload} case 'UPDATE_IS_LOADED_SECTIONS' : diff --git a/src/frontend/src/test/test.utils.tsx b/src/frontend/src/test/test.utils.tsx index 34f1291c4..b9bfb767c 100644 --- a/src/frontend/src/test/test.utils.tsx +++ b/src/frontend/src/test/test.utils.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, RenderResult } from '@testing-library/react'; import { AppStateContext } from '../state/AppProvider'; -import { Conversation, ChatHistoryLoadingState } from '../api/models'; +import { ChatHistoryLoadingState } from '../api/models'; // Default mock state export const defaultMockState = { isChatHistoryOpen: true, diff --git a/tests/e2e-test/pages/browsePage.py b/tests/e2e-test/pages/browsePage.py index a88233db6..5de638429 100644 --- a/tests/e2e-test/pages/browsePage.py +++ b/tests/e2e-test/pages/browsePage.py @@ -10,7 +10,7 @@ class BrowsePage(BasePage): CLOSE_BUTTON = "//button[.='Close']" def __init__(self, page): - self.page = page + super().__init__(page) def enter_a_question(self, text): # Type a question in the text area diff --git a/tests/e2e-test/pages/draftPage.py b/tests/e2e-test/pages/draftPage.py index ead577aec..b6e96bded 100644 --- a/tests/e2e-test/pages/draftPage.py +++ b/tests/e2e-test/pages/draftPage.py @@ -13,7 +13,7 @@ class DraftPage(BasePage): invalid_response1 = "There was an issue fetching your data. Please try again." def __init__(self, page): - self.page = page + super().__init__(page) def validate_draft_sections_loaded(self): max_wait_time = 180 # seconds diff --git a/tests/e2e-test/pages/generatePage.py b/tests/e2e-test/pages/generatePage.py index 92a55d062..2fbccd42d 100644 --- a/tests/e2e-test/pages/generatePage.py +++ b/tests/e2e-test/pages/generatePage.py @@ -19,7 +19,7 @@ class GeneratePage(BasePage): CHAT_HISTORY_CLOSE = "//i[@data-icon-name='Cancel']" def __init__(self, page): - self.page = page + super().__init__(page) def enter_a_question(self, text): # Type a question in the text area diff --git a/tests/e2e-test/pages/homePage.py b/tests/e2e-test/pages/homePage.py index 9bacc0934..bda0333d9 100644 --- a/tests/e2e-test/pages/homePage.py +++ b/tests/e2e-test/pages/homePage.py @@ -15,7 +15,7 @@ class HomePage(BasePage): ) def __init__(self, page): - self.page = page + super().__init__(page) def click_browse_button(self): # click on BROWSE