diff --git a/app/frontend/index.html b/app/frontend/index.html index a7aa7e9eff..a7b97445e2 100644 --- a/app/frontend/index.html +++ b/app/frontend/index.html @@ -3,8 +3,9 @@ + - GPT + Enterprise data | Sample + GPT Enterprise
diff --git a/app/frontend/package.json b/app/frontend/package.json index dfaf56bc32..7ad84def3c 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -12,30 +12,30 @@ "preview": "vite preview" }, "dependencies": { - "@azure/msal-react": "^2.0.6", "@azure/msal-browser": "^3.17.0", + "@azure/msal-react": "^2.0.6", "@fluentui/react": "^8.112.5", "@fluentui/react-components": "^9.37.3", "@fluentui/react-icons": "^2.0.221", "@react-spring/web": "^9.7.3", - "marked": "^13.0.0", "dompurify": "^3.0.6", + "marked": "^13.0.0", + "ndjson-readablestream": "^1.0.7", "react": "^18.3.1", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1", - "ndjson-readablestream": "^1.0.7", "react-syntax-highlighter": "^15.5.0", "scheduler": "^0.20.2" }, "devDependencies": { + "@types/dom-speech-recognition": "^0.0.4", "@types/dompurify": "^3.0.4", "@types/react": "^18.3.3", "@types/react-dom": "^18.2.14", + "@types/react-syntax-highlighter": "^15.5.7", "@vitejs/plugin-react": "^4.1.1", "prettier": "^3.0.3", "typescript": "^5.4.5", - "@types/react-syntax-highlighter": "^15.5.7", - "@types/dom-speech-recognition": "^0.0.4", "vite": "^4.5.3" } } diff --git a/app/frontend/public/pow_whiddon.svg b/app/frontend/public/pow_whiddon.svg new file mode 100644 index 0000000000..099070e17d --- /dev/null +++ b/app/frontend/public/pow_whiddon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/frontend/public/splash_screen.png b/app/frontend/public/splash_screen.png new file mode 100644 index 0000000000..ced4de0ad4 Binary files /dev/null and b/app/frontend/public/splash_screen.png differ diff --git a/app/frontend/public/splash_screen_svg.svg b/app/frontend/public/splash_screen_svg.svg new file mode 100644 index 0000000000..fe239db198 --- /dev/null +++ b/app/frontend/public/splash_screen_svg.svg @@ -0,0 +1,2648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css index 5bb03c848e..34e015fb03 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css @@ -56,9 +56,68 @@ border-radius: 10px; margin-bottom: 8px; } - .citationImg { - height: 450px; + height: 28.125rem; max-width: 100%; object-fit: contain; } + +.customModal { + overflow: hidden !important; + width: 50% !important; + max-width: none !important; + min-width: none !important; + padding: 20px; + background-color: white; + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + max-height: 80vh; /* Ensure the modal does not exceed 80% of the viewport height */ +} + +.customModal { + overflow: hidden !important; +} + +.customModal .ms-Dialog-main { + overflow: hidden !important; +} + +.customModal .ms-Modal-scrollableContent { + overflow: hidden !important; + max-height: none !important; +} + + +.modalHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 10px; + border-bottom: 1px solid #e5e5e5; +} + +.modalBody { + padding-top: 10px; + overflow: hidden !important; + flex: 1; + display: flex; + flex-direction: column; +} + +.pdfViewer { + flex: 1; + width: 100%; + height: 70vh; /* Set a specific height for the PDF viewer */ + border: none; +} + +/* Ensure parent containers do not cause overflow */ +.modalContainer { + overflow: hidden !important; +} + +.modalBackdrop { + overflow: hidden !important; +} diff --git a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx index 3f4853797a..a0b6385433 100644 --- a/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx +++ b/app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx @@ -1,16 +1,15 @@ -import { Stack, Pivot, PivotItem } from "@fluentui/react"; +import { Stack, Pivot, PivotItem, Modal, IconButton } from "@fluentui/react"; +import { useState, useEffect } from "react"; +import { useMsal } from "@azure/msal-react"; import styles from "./AnalysisPanel.module.css"; - import { SupportingContent } from "../SupportingContent"; import { ChatAppResponse } from "../../api"; import { AnalysisPanelTabs } from "./AnalysisPanelTabs"; import { ThoughtProcess } from "./ThoughtProcess"; import { MarkdownViewer } from "../MarkdownViewer"; -import { useMsal } from "@azure/msal-react"; import { getHeaders } from "../../api"; import { useLogin, getToken } from "../../authConfig"; -import { useState, useEffect } from "react"; interface Props { className: string; @@ -28,14 +27,14 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh const isDisabledSupportingContentTab: boolean = !answer.context.data_points; const isDisabledCitationTab: boolean = !activeCitation; const [citation, setCitation] = useState(""); + const [isModalOpen, setIsModalOpen] = useState(false); const client = useLogin ? useMsal().instance : undefined; + const fetchCitation = async () => { const token = client ? await getToken(client) : undefined; if (activeCitation) { - // Get hash from the URL as it may contain #page=N - // which helps browser PDF renderer jump to correct page N const originalHash = activeCitation.indexOf("#") ? activeCitation.split("#")[1] : ""; const response = await fetch(activeCitation, { method: "GET", @@ -43,16 +42,17 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh }); const citationContent = await response.blob(); let citationObjectUrl = URL.createObjectURL(citationContent); - // Add hash back to the new blob URL if (originalHash) { citationObjectUrl += "#" + originalHash; } setCitation(citationObjectUrl); + setIsModalOpen(true); } }; + useEffect(() => { fetchCitation(); - }, []); + }, [activeCitation]); const renderFileViewer = () => { if (!activeCitation) { @@ -71,32 +71,57 @@ export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeigh }; return ( - pivotItem && onActiveTabChanged(pivotItem.props.itemKey! as AnalysisPanelTabs)} - > - - - - - - - + pivotItem && onActiveTabChanged(pivotItem.props.itemKey! as AnalysisPanelTabs)} > - {renderFileViewer()} - - + {/* + + */} + + + + {/* + */} + + {/* setIsModalOpen(false)} isBlocking={false} containerClassName={styles.modalContainer}> +
+ setIsModalOpen(false)} /> +
+
{renderFileViewer()}
+
*/} + setIsModalOpen(false)} + isBlocking={false} + containerClassName={styles.customModal} + scrollableContentClassName={styles.noScrollModal} // Removes internal scroll +> +
+ setIsModalOpen(false)} /> +
+
+
+ {renderFileViewer()} +
+
+
+ + + ); }; diff --git a/app/frontend/src/components/Example/Example.tsx b/app/frontend/src/components/Example/Example.tsx index 82f01d3d0b..1456000cf8 100644 --- a/app/frontend/src/components/Example/Example.tsx +++ b/app/frontend/src/components/Example/Example.tsx @@ -3,12 +3,13 @@ import styles from "./Example.module.css"; interface Props { text: string; value: string; + bgColor: string; onClick: (value: string) => void; } -export const Example = ({ text, value, onClick }: Props) => { +export const Example = ({ text, value, bgColor, onClick }: Props) => { return ( -
onClick(value)}> +
onClick(value)} style={{ backgroundColor: bgColor }}>

{text}

); diff --git a/app/frontend/src/components/Example/ExampleList.tsx b/app/frontend/src/components/Example/ExampleList.tsx index 49c35cbd2d..fb77cd1e33 100644 --- a/app/frontend/src/components/Example/ExampleList.tsx +++ b/app/frontend/src/components/Example/ExampleList.tsx @@ -3,7 +3,7 @@ import { Example } from "./Example"; import styles from "./Example.module.css"; const DEFAULT_EXAMPLES: string[] = [ - "What is included in my Northwind Health Plus plan that is not in standard?", + "What is included in my Northwind Health Plus plan?", "What happens in a performance review?", "What does a Product Manager do?" ]; @@ -20,11 +20,15 @@ interface Props { } export const ExampleList = ({ onExampleClicked, useGPT4V }: Props) => { + const backgroundColors = ["#4ec0ad", "#f36f4c", "#e3e0d1"]; + return ( diff --git a/app/frontend/src/components/LoginButton/LoginButton.module.css b/app/frontend/src/components/LoginButton/LoginButton.module.css index f808ac94cf..a10eb4cc19 100644 --- a/app/frontend/src/components/LoginButton/LoginButton.module.css +++ b/app/frontend/src/components/LoginButton/LoginButton.module.css @@ -1,5 +1,20 @@ -.loginButton { +/* .loginButton { border-radius: 5px; padding: 30px 30px; font-weight: 100; +} */ + +.loginButton { + background-color: #ff6b4a; + color: white; + font-size: 1rem; + padding: 12px 24px; + border: none; + border-radius: 8px; + cursor: pointer; + margin-top: 20px; +} + +.loginButton:hover { + background-color: #e05a3d; } diff --git a/app/frontend/src/components/LoginButton/LoginButton.tsx b/app/frontend/src/components/LoginButton/LoginButton.tsx index 2f65e9089d..4c2cd72e27 100644 --- a/app/frontend/src/components/LoginButton/LoginButton.tsx +++ b/app/frontend/src/components/LoginButton/LoginButton.tsx @@ -5,7 +5,12 @@ import styles from "./LoginButton.module.css"; import { getRedirectUri, loginRequest } from "../../authConfig"; import { appServicesToken, appServicesLogout } from "../../authConfig"; -export const LoginButton = () => { +interface LoginButtonProps { + onLogin?: () => void; + onLogout?: () => void; +} + +export const LoginButton: React.FC = ({ onLogin, onLogout }) => { const { instance } = useMsal(); const activeAccount = instance.getActiveAccount(); const isLoggedIn = (activeAccount || appServicesToken) != null; @@ -21,6 +26,10 @@ export const LoginButton = () => { ...loginRequest, redirectUri: getRedirectUri() }) + .then(() => { + localStorage.setItem("isLoggedIn", "true"); + onLogin && onLogin(); // Notify MainLayout about login + }) .catch(error => console.log(error)); }; const handleLogoutPopup = () => { @@ -30,6 +39,10 @@ export const LoginButton = () => { mainWindowRedirectUri: "/", // redirects the top level app after logout account: instance.getActiveAccount() }) + .then(() => { + localStorage.removeItem("isLoggedIn"); + onLogout && onLogout(); // Notify MainLayout about logout + }) .catch(error => console.log(error)); } else { appServicesLogout(); @@ -37,10 +50,12 @@ export const LoginButton = () => { }; const logoutText = `Logout\n${activeAccount?.username ?? appServicesToken?.user_claims?.preferred_username}`; return ( - + // + ); }; diff --git a/app/frontend/src/components/QuestionInput/QuestionInput.module.css b/app/frontend/src/components/QuestionInput/QuestionInput.module.css index 419523e453..d70ea91c1a 100644 --- a/app/frontend/src/components/QuestionInput/QuestionInput.module.css +++ b/app/frontend/src/components/QuestionInput/QuestionInput.module.css @@ -1,21 +1,91 @@ -.questionInputContainer { - border-radius: 8px; +/* .questionInputContainer { + border-radius: 0.5rem; box-shadow: - 0px 8px 16px rgba(0, 0, 0, 0.14), - 0px 0px 2px rgba(0, 0, 0, 0.12); - height: 90px; + 0px 0.5rem 1rem rgba(0, 0, 0, 0.14), + 0px 0px 0.125rem rgba(0, 0, 0, 0.12); width: 100%; - padding: 15px; + padding: 0.8rem; background: white; } .questionInputTextArea { width: 100%; - line-height: 40px; + line-height: 2.5rem; } .questionInputButtonsContainer { display: flex; flex-direction: column; justify-content: flex-end; +} */ + +@media (min-width: 992px) { + .questionInputContainer { + height: 5.625rem; + } +} +/* Container wrapping the entire input field */ +.questionInputContainer { + width: 63%; + display: flex; + justify-content: center; + align-items: center; + padding: 16px; + background-color: #f2f2f2; /* Light gray background */ + position: fixed; + bottom: 0; + +} + +div > input{ + background-color: #f2f2f2 !important; +} + +/* Wrapper for input + send button */ +.inputWrapper { + width: 100%; + display: flex; + align-items: center; + background-color: #f2f2f2; /* Slightly darker than outer container */ + border-radius: 30px; /* Rounded edges */ + padding: 5px 15px; + box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Input field */ +.questionInputTextArea { + flex-grow: 1; + background: #f2f2f2; + border: none; + font-size: 16px; + font-family: 'Poppins', sans-serif; + color: #333; + padding: 12px; + border-radius: 25px; +} + +/* Hide Fluent UI default border */ +.questionInputTextArea:focus { + outline: none; +} + +/* Send Button */ +.sendButton { + display: flex; + justify-content: center; + align-items: center; + padding: 10px; + border-radius: 50%; + cursor: pointer; + transition: background 0.2s ease-in-out; +} + +.sendButton:hover { + background-color: #e0e0e0; +} + +/* Send Icon */ +.sendIcon { + color: #3a3a3a; /* Dark gray */ + font-size: 24px; } diff --git a/app/frontend/src/components/QuestionInput/QuestionInput.tsx b/app/frontend/src/components/QuestionInput/QuestionInput.tsx index f0667e37f8..febe55b492 100644 --- a/app/frontend/src/components/QuestionInput/QuestionInput.tsx +++ b/app/frontend/src/components/QuestionInput/QuestionInput.tsx @@ -60,24 +60,45 @@ export const QuestionInput = ({ onSend, disabled, placeholder, clearOnSend, init } return ( + // + // + //
+ // + //
+ // {showSpeechInput && } + //
+
+ {/* Styled Input Field */} -
- -
+ + ); }; diff --git a/app/frontend/src/components/SplashScreen/SplashScreen.module.css b/app/frontend/src/components/SplashScreen/SplashScreen.module.css new file mode 100644 index 0000000000..648bb9ed73 --- /dev/null +++ b/app/frontend/src/components/SplashScreen/SplashScreen.module.css @@ -0,0 +1,61 @@ +.splashScreen { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100vw; /* Ensure full width */ + background-color: #f0f0f0; + overflow: hidden; /* Prevent scrollbars */ +} + +.imageContainer { + position: relative; + width: 100%; + height: 100%; +} + +.splashImage { + width: 100%; + height: 100%; + object-fit: cover; /* Cover the entire screen without distortion */ + display: block; +} + +.textOverlay { + position: absolute; + top: 50%; + left: 26%; + transform: translate(-50%, -50%); + text-align: center; + color: white; + width: 80%; +} + +.title { + font-size: 2.5rem; + font-weight: 400; +} + +.bold { + font-weight: 700; +} + +.subtitle { + font-size: 1.2rem; + margin-top: 10px; +} + +.getStartedButton { + background-color: #ff6b4a; + color: white; + font-size: 1rem; + padding: 12px 24px; + border: none; + border-radius: 8px; + cursor: pointer; + margin-top: 20px; +} + +.getStartedButton:hover { + background-color: #e05a3d; +} diff --git a/app/frontend/src/components/SplashScreen/SplashScreen.tsx b/app/frontend/src/components/SplashScreen/SplashScreen.tsx new file mode 100644 index 0000000000..bbb306763b --- /dev/null +++ b/app/frontend/src/components/SplashScreen/SplashScreen.tsx @@ -0,0 +1,25 @@ +import styles from "./SplashScreen.module.css"; +import { LoginButton } from "../../components/LoginButton"; + +interface SplashScreenProps { + onLogin?: () => void; +} + +export const SplashScreen: React.FC = ({ onLogin }) => { + return ( +
+
+ Splash Screen + {/* Text overlay */} +
+ +
+
+
+ ); +}; diff --git a/app/frontend/src/components/SplashScreen/index.tsx b/app/frontend/src/components/SplashScreen/index.tsx new file mode 100644 index 0000000000..101d34aac6 --- /dev/null +++ b/app/frontend/src/components/SplashScreen/index.tsx @@ -0,0 +1 @@ +export * from "./SplashScreen"; \ No newline at end of file diff --git a/app/frontend/src/index.css b/app/frontend/src/index.css index 44e9bf2eea..b2e86f52ca 100644 --- a/app/frontend/src/index.css +++ b/app/frontend/src/index.css @@ -1,5 +1,6 @@ * { box-sizing: border-box; + font-family: 'Poppins', sans-serif; } html, diff --git a/app/frontend/src/index.tsx b/app/frontend/src/index.tsx index edd2f5eded..398f946220 100644 --- a/app/frontend/src/index.tsx +++ b/app/frontend/src/index.tsx @@ -4,46 +4,36 @@ import { createHashRouter, RouterProvider } from "react-router-dom"; import { initializeIcons } from "@fluentui/react"; import { MsalProvider } from "@azure/msal-react"; import { PublicClientApplication, EventType, AccountInfo } from "@azure/msal-browser"; -import { msalConfig, useLogin } from "./authConfig"; +import { msalConfig } from "./authConfig"; // Remove useLogin here import "./index.css"; - import Layout from "./pages/layout/Layout"; import Chat from "./pages/chat/Chat"; -var layout; -if (useLogin) { - var msalInstance = new PublicClientApplication(msalConfig); - - // Default to using the first account if no account is active on page load - if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) { - // Account selection logic is app dependent. Adjust as needed for different use cases. - msalInstance.setActiveAccount(msalInstance.getActiveAccount()); - } +// ✅ Always create MSAL instance +const msalInstance = new PublicClientApplication(msalConfig); - // Listen for sign-in event and set active account - msalInstance.addEventCallback(event => { - if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { - const account = event.payload as AccountInfo; - msalInstance.setActiveAccount(account); - } - }); - - layout = ( - - - - ); -} else { - layout = ; +// ✅ Select the first available account if no active account exists +const accounts = msalInstance.getAllAccounts(); +if (!msalInstance.getActiveAccount() && accounts.length > 0) { + msalInstance.setActiveAccount(accounts[0]); } +// ✅ Listen for sign-in events and set active account +msalInstance.addEventCallback(event => { + if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { + const account = event.payload as AccountInfo; + msalInstance.setActiveAccount(account); + } +}); + +// ✅ Wrap the entire app in MsalProvider initializeIcons(); const router = createHashRouter([ { path: "/", - element: layout, + element: , // Layout will handle authentication logic children: [ { index: true, @@ -63,6 +53,8 @@ const router = createHashRouter([ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - + + + ); diff --git a/app/frontend/src/pages/chat/Chat.module.css b/app/frontend/src/pages/chat/Chat.module.css index 4aaaa1ecb2..5719e6e7a8 100644 --- a/app/frontend/src/pages/chat/Chat.module.css +++ b/app/frontend/src/pages/chat/Chat.module.css @@ -29,10 +29,11 @@ } .chatEmptyStateTitle { - font-size: 4rem; - font-weight: 600; + font-size: 40px; + font-weight: 300; + line-height: '45px'; margin-top: 0; - margin-bottom: 30px; + margin-bottom: 1.875rem; } .chatEmptyStateSubtitle { diff --git a/app/frontend/src/pages/chat/Chat.tsx b/app/frontend/src/pages/chat/Chat.tsx index abd8725e04..1d30bef709 100644 --- a/app/frontend/src/pages/chat/Chat.tsx +++ b/app/frontend/src/pages/chat/Chat.tsx @@ -331,6 +331,10 @@ const Chat = () => { return (
+ +
+ +
{showUserUpload && } setIsConfigPanelOpen(!isConfigPanelOpen)} /> @@ -339,9 +343,9 @@ const Chat = () => {
{!lastQuestionRef.current ? (
-
) : ( @@ -413,7 +417,7 @@ const Chat = () => {
makeApiRequest(question)} showSpeechInput={showSpeechInput} diff --git a/app/frontend/src/pages/layout/Layout.module.css b/app/frontend/src/pages/layout/Layout.module.css index ebb860e216..4adad6b3c2 100644 --- a/app/frontend/src/pages/layout/Layout.module.css +++ b/app/frontend/src/pages/layout/Layout.module.css @@ -1,7 +1,7 @@ .layout { display: flex; - flex-direction: column; - height: 100%; + /* flex-direction: column; */ + height: 100vh; } .header { @@ -76,3 +76,156 @@ .githubLogo { height: 20px; } +/* Sidebar */ +.sidebar { + width: 200px; + background-color: #ffffff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + position: fixed; + height: 100%; + padding-top: 20px; + border-right: 1px solid #e0e0e0; +} + +/* Logo Container */ +.logoContainer { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding-bottom: 10px; +} + +.logo { + width: 80%; + height: auto; +} + + +/* Powered By Text */ +.poweredBy { + font-size: 12px; + color: #888; + margin-top: 10px; + margin-left: 10px; +} + +.poweredBy a { + text-decoration: none !important; /* Removes underline for any linked text */ + color: inherit; /* Keeps default color */ +} + + +/* Main Content */ +.mainContent { + margin-left: 200px; /* Offset for sidebar */ + flex-grow: 1; + display: flex; + flex-direction: column; +} + +/* Header */ +.header { + background-color: #ffffff; + display: flex; + align-items: center; + justify-content: space-between; + padding: 15px 20px; + border-bottom: 1px solid #e0e0e0; +} + +.headerTitle { + font-size: 20px; + font-weight: bold; + color: #053c23; + /* color: #1abc9c; */ +} + +/* Info Icon */ +.infoIcon { + font-size: 24px; + color: #1abc9c; + cursor: pointer; + margin-top: 10px; +} + +/* Page Content */ +.pageContent { + flex-grow: 1; + padding: 20px; + overflow-y: auto; +} + + +.landingPage { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background: url("/image.png") no-repeat center center; + background-size: cover; + color: white; + text-align: center; + flex-direction: column; + position: relative; +} + +.content { + z-index: 2; + text-align: center; + max-width: 500px; +} + +.boldText { + font-weight: bold; +} + +.subText { + font-size: 1.2rem; + margin-bottom: 20px; +} + +.getStartedButton { + background-color: #ff7f50; + color: white; + padding: 12px 24px; + font-size: 1.1rem; + border-radius: 8px; + cursor: pointer; + border: none; + transition: 0.3s ease; +} + +.getStartedButton:hover { + background-color: #ff6333; +} + +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); /* Dark overlay for better readability */ + z-index: 1; +} + +.logoutContainer +{ + display:flex; +} + +.logoutButton +{ + margin-top: 1px; + margin-left: 10px; + background-color: #f36f4c; + color: white; + border: 5px; + border-radius: 5px; + width: 10vh; + cursor: pointer; +} \ No newline at end of file diff --git a/app/frontend/src/pages/layout/Layout.tsx b/app/frontend/src/pages/layout/Layout.tsx index 157a6ffd3a..a757f02928 100644 --- a/app/frontend/src/pages/layout/Layout.tsx +++ b/app/frontend/src/pages/layout/Layout.tsx @@ -1,54 +1,101 @@ -import { Outlet, NavLink, Link } from "react-router-dom"; +import { Outlet, Link, useNavigate } from "react-router-dom"; +import { useState, useEffect } from "react"; +import { useMsal } from "@azure/msal-react"; +import { EventType } from "@azure/msal-browser"; // ✅ Import correct event type +import styles from "./Layout.module.css"; +import { LoginButton } from "../../components/LoginButton"; +import { SplashScreen } from "../../components/SplashScreen"; +import { Info24Regular } from "@fluentui/react-icons"; // Fluent UI info icon +import { appServicesToken, appServicesLogout } from "../../authConfig"; -import github from "../../assets/github.svg"; +const Layout = () => { + const { instance, accounts } = useMsal(); + const navigate = useNavigate(); + const [isLoggedIn, setIsLoggedIn] = useState(null); -import styles from "./Layout.module.css"; + useEffect(() => { + const checkAuthStatus = () => { + const activeAccount = instance.getActiveAccount(); + if (activeAccount || appServicesToken) { + setIsLoggedIn(true); + } else { + setIsLoggedIn(false); + } + }; -import { useLogin } from "../../authConfig"; + checkAuthStatus(); + + // ✅ Use MSAL event listener to detect login state change + const accountListener = instance.addEventCallback((event) => { + if (event.eventType === EventType.LOGIN_SUCCESS) { + setIsLoggedIn(true); + navigate("/", { replace: true }); // ✅ Navigate only after login + } else if (event.eventType === EventType.LOGOUT_SUCCESS) { + setIsLoggedIn(false); + navigate("/"); // ✅ Go back to splash screen on logout + } + }); -import { LoginButton } from "../../components/LoginButton"; + return () => { + if (accountListener) { + instance.removeEventCallback(accountListener); + } + }; + }, [instance, accounts, navigate]); -const Layout = () => { - return ( + const handleLogout = () => { + const activeAccount = instance.getActiveAccount(); + if (activeAccount) { + instance.logoutPopup({ + mainWindowRedirectUri: "/", // Redirects app after logout + account: activeAccount, + }); + } else { + appServicesLogout(); + } + setIsLoggedIn(false); + navigate("/"); // ✅ Ensure user is redirected to login after logout + }; + + if (isLoggedIn === null) { + return ; // ✅ Shows splash screen initially + } + + return isLoggedIn ? (
-
-
- -

GPT + Enterprise data | Sample

+ {/* Sidebar */} +
+ + + {/* Main Content */} +
+ {/* Header */} +
+

Assistant.AI

+
+ + +
+
- + {/* Page Content */} +
+ +
+
+ ) : ( + ); }; diff --git a/app/frontend/vite.config.ts b/app/frontend/vite.config.ts index 7fe15e7533..82df06ee68 100644 --- a/app/frontend/vite.config.ts +++ b/app/frontend/vite.config.ts @@ -25,16 +25,16 @@ export default defineConfig({ }, server: { proxy: { - "/content/": "http://localhost:50505", - "/auth_setup": "http://localhost:50505", - "/.auth/me": "http://localhost:50505", - "/ask": "http://localhost:50505", - "/chat": "http://localhost:50505", - "/speech": "http://localhost:50505", - "/config": "http://localhost:50505", - "/upload": "http://localhost:50505", - "/delete_uploaded": "http://localhost:50505", - "/list_uploaded": "http://localhost:50505" + "/content/": "http://127.0.0.1:50505", + "/auth_setup": "http://127.0.0.1:50505", + "/.auth/me": "http://127.0.0.1:50505", + "/ask": "http://127.0.0.1:50505", + "/chat": "http://127.0.0.1:50505", + "/speech": "http://127.0.0.1:50505", + "/config": "http://127.0.0.1:50505", + "/upload": "http://127.0.0.1:50505", + "/delete_uploaded": "http://127.0.0.1:50505", + "/list_uploaded": "http://127.0.0.1:50505" } } }); diff --git a/docs/deploy_private.md b/docs/deploy_private.md index c7f449c29a..c9a731381e 100644 --- a/docs/deploy_private.md +++ b/docs/deploy_private.md @@ -3,6 +3,8 @@ If you want to disable public access when deploying the Chat App, you can do so by setting `azd` environment values. +[📺 Watch a video overview of the VM provisioning process](https://www.youtube.com/watch?v=RbITd0a5who) + ## Before you begin Deploying with public access disabled adds additional cost to your deployment. Please see pricing for the following products: