You are on page 1of 10

import { useRef, useState, useEffect } from "react";

import { Checkbox, Panel, DefaultButton, TextField, SpinButton } from


"@fluentui/react";
import { SparkleFilled } from "@fluentui/react-icons";
import readNDJSONStream from "ndjson-readablestream";

import styles from "./Chat.module.css";

import {
chatApi,
configApi,
RetrievalMode,
ChatAppResponse,
ChatAppResponseOrError,
ChatAppRequest,
ResponseMessage,
VectorFieldOptions,
GPT4VInput
} from "../../api";
import { Answer, AnswerError, AnswerLoading } from "../../components/Answer";
import { QuestionInput } from "../../components/QuestionInput";
import { ExampleList } from "../../components/Example";
import { UserChatMessage } from "../../components/UserChatMessage";
import { AnalysisPanel, AnalysisPanelTabs } from "../../components/AnalysisPanel";
import { SettingsButton } from "../../components/SettingsButton";
import { ClearChatButton } from "../../components/ClearChatButton";
import { useLogin, getToken, isLoggedIn, requireAccessControl } from
"../../authConfig";
import { VectorSettings } from "../../components/VectorSettings";
import { useMsal } from "@azure/msal-react";
import { TokenClaimsDisplay } from "../../components/TokenClaimsDisplay";
import { GPT4VSettings } from "../../components/GPT4VSettings";

const Chat = () => {


const [isConfigPanelOpen, setIsConfigPanelOpen] = useState(false);
const [promptTemplate, setPromptTemplate] = useState<string>("");
const [retrieveCount, setRetrieveCount] = useState<number>(3);
const [retrievalMode, setRetrievalMode] =
useState<RetrievalMode>(RetrievalMode.Hybrid);
const [useSemanticRanker, setUseSemanticRanker] = useState<boolean>(true);
const [shouldStream, setShouldStream] = useState<boolean>(true);
const [useSemanticCaptions, setUseSemanticCaptions] = useState<boolean>(false);
const [excludeCategory, setExcludeCategory] = useState<string>("");
const [useSuggestFollowupQuestions, setUseSuggestFollowupQuestions] =
useState<boolean>(false);
const [vectorFieldList, setVectorFieldList] =
useState<VectorFieldOptions[]>([VectorFieldOptions.Embedding]);
const [useOidSecurityFilter, setUseOidSecurityFilter] =
useState<boolean>(false);
const [useGroupsSecurityFilter, setUseGroupsSecurityFilter] =
useState<boolean>(false);
const [gpt4vInput, setGPT4VInput] =
useState<GPT4VInput>(GPT4VInput.TextAndImages);
const [useGPT4V, setUseGPT4V] = useState<boolean>(false);

const lastQuestionRef = useRef<string>("");


const chatMessageStreamEnd = useRef<HTMLDivElement | null>(null);

const [isLoading, setIsLoading] = useState<boolean>(false);


const [isStreaming, setIsStreaming] = useState<boolean>(false);
const [error, setError] = useState<unknown>();

const [activeCitation, setActiveCitation] = useState<string>();


const [activeAnalysisPanelTab, setActiveAnalysisPanelTab] =
useState<AnalysisPanelTabs | undefined>(undefined);

const [selectedAnswer, setSelectedAnswer] = useState<number>(0);


const [answers, setAnswers] = useState<[user: string, response:
ChatAppResponse][]>([]);
console.log('Arun Printing Answers:', answers);
console.log('Arun Printing setAnswers:', setAnswers);
const [streamedAnswers, setStreamedAnswers] = useState<[user: string, response:
ChatAppResponse][]>([]);
console.log('Arun Printing Streamed Answers:', streamedAnswers);
console.log('Arun Printing setStreamedAnswers:', setStreamedAnswers);

const [showGPT4VOptions, setShowGPT4VOptions] = useState<boolean>(false);

const getConfig = async () => {


const token = client ? await getToken(client) : undefined;

configApi(token).then(config => {
setShowGPT4VOptions(config.showGPT4VOptions);
});
};

const handleAsyncRequest = async (question: string, answers: [string,


ChatAppResponse][], setAnswers: Function, responseBody: ReadableStream<any>) => {
console.log("\n Arun inside handleAsyncRequest Printing Question:",
question);
console.log("\n Arun inside handleAsyncRequest Printing Answers:",
answers);
console.log("\n Arun inside handleAsyncRequest Printing setAnswers
Function:", setAnswers);
console.log("\n Arun inside handleAsyncRequest Printing Response
Body:", responseBody);
let answer: string = "";
let askResponse: ChatAppResponse = {} as ChatAppResponse;

const updateState = (newContent: string) => {


return new Promise(resolve => {
setTimeout(() => {
answer += newContent;
const latestResponse: ChatAppResponse = {
...askResponse,
choices: [{ ...askResponse.choices[0], message: { content:
answer, role: askResponse.choices[0].message.role } }]
};
console.log("\n Arun Printing latestResponse:",
latestResponse);
setStreamedAnswers([...answers, [question,
latestResponse]]);
resolve(null);
}, 33);
});
};
try {
setIsStreaming(true);
for await (const event of readNDJSONStream(responseBody)) {
console.log('\n Arun Printing event:', event );
console.log('\n Arun Printing readNDJSONStream:',
readNDJSONStream(responseBody) );
if (event["choices"] && event["choices"][0]["context"] &&
event["choices"][0]["context"]["data_points"]) {
event["choices"][0]["message"] = event["choices"][0]["delta"];
askResponse = event;
console.log('\n Arun Printing askResponse1:',
askResponse);
} else if (event["choices"] && event["choices"][0]["delta"]
["content"]) {
setIsLoading(false);
await updateState(event["choices"][0]["delta"]["content"]);
} else if (event["choices"] && event["choices"][0]["context"]) {
// Update context with new keys from latest event
askResponse.choices[0].context =
{ ...askResponse.choices[0].context, ...event["choices"][0]["context"] };
console.log('\n Arun Printing
askResponse.choices.context:', askResponse.choices[0].context);
} else if (event["error"]) {
throw Error(event["error"]);
}
}
} finally {
setIsStreaming(false);
}
const fullResponse: ChatAppResponse = {
...askResponse,
choices: [{ ...askResponse.choices[0], message: { content: answer,
role: askResponse.choices[0].message.role } }]
};

console.log('\n Arun Printing Full Response:', fullResponse);


return fullResponse;
};

const client = useLogin ? useMsal().instance : undefined;

const makeApiRequest = async (question: string) => {


lastQuestionRef.current = question;

error && setError(undefined);


setIsLoading(true);
setActiveCitation(undefined);
setActiveAnalysisPanelTab(undefined);

const token = client ? await getToken(client) : undefined;

try {
const messages: ResponseMessage[] = answers.flatMap(a => [
{ content: a[0], role: "user" },
{ content: a[1].choices[0].message.content, role: "assistant" }
]);

const request: ChatAppRequest = {


messages: [...messages, { content: question, role: "user" }],
stream: shouldStream,
context: {
overrides: {
prompt_template: promptTemplate.length === 0 ? undefined :
promptTemplate,
exclude_category: excludeCategory.length === 0 ?
undefined : excludeCategory,
top: retrieveCount,
retrieval_mode: retrievalMode,
semantic_ranker: useSemanticRanker,
semantic_captions: useSemanticCaptions,
suggest_followup_questions: useSuggestFollowupQuestions,
use_oid_security_filter: useOidSecurityFilter,
use_groups_security_filter: useGroupsSecurityFilter,
vector_fields: vectorFieldList,
use_gpt4v: useGPT4V,
gpt4v_input: gpt4vInput
}
},
// ChatAppProtocol: Client must pass on any session state received
from the server
session_state: answers.length ? answers[answers.length - 1]
[1].choices[0].session_state : null
};

const response = await chatApi(request, token);


console.log('\n Arun inside Chat.tsx Printing request sent to
chatApi', request);
console.log('\n Arun inside Chat.tsx Printing token sent to
chatApi', token);
console.log('\n Arun inside Chat.tsx Printing response from
chatApi', response);
console.log('\n Arun inside Chat.tsx Printing response body from
chatApi', response.body);
if (!response.body) {
throw Error("No response body");
}
if (shouldStream) {
const parsedResponse: ChatAppResponse = await
handleAsyncRequest(question, answers, setAnswers, response.body);
setAnswers([...answers, [question, parsedResponse]]);
console.log('\n Arun inside Chat.tsx Printing parsed
Response', parsedResponse);
} else {
const parsedResponse: ChatAppResponseOrError = await
response.json();
if (response.status > 299 || !response.ok) {
throw Error(parsedResponse.error || "Unknown error");
}
setAnswers([...answers, [question, parsedResponse as
ChatAppResponse]]);
}
} catch (e) {
setError(e);
} finally {
setIsLoading(false);
}
};

const clearChat = () => {


lastQuestionRef.current = "";
error && setError(undefined);
setActiveCitation(undefined);
setActiveAnalysisPanelTab(undefined);
setAnswers([]);
setStreamedAnswers([]);
setIsLoading(false);
setIsStreaming(false);
};

useEffect(() => chatMessageStreamEnd.current?.scrollIntoView({ behavior:


"smooth" }), [isLoading]);
useEffect(() => chatMessageStreamEnd.current?.scrollIntoView({ behavior: "auto"
}), [streamedAnswers]);
useEffect(() => {
getConfig();
}, []);

const onPromptTemplateChange = (_ev?: React.FormEvent<HTMLInputElement |


HTMLTextAreaElement>, newValue?: string) => {
setPromptTemplate(newValue || "");
};

const onRetrieveCountChange = (_ev?: React.SyntheticEvent<HTMLElement, Event>,


newValue?: string) => {
setRetrieveCount(parseInt(newValue || "3"));
};

const onUseSemanticRankerChange = (_ev?: React.FormEvent<HTMLElement |


HTMLInputElement>, checked?: boolean) => {
setUseSemanticRanker(!!checked);
};

const onUseSemanticCaptionsChange = (_ev?: React.FormEvent<HTMLElement |


HTMLInputElement>, checked?: boolean) => {
setUseSemanticCaptions(!!checked);
};

const onShouldStreamChange = (_ev?: React.FormEvent<HTMLElement |


HTMLInputElement>, checked?: boolean) => {
setShouldStream(!!checked);
};

const onExcludeCategoryChanged = (_ev?: React.FormEvent, newValue?: string) =>


{
setExcludeCategory(newValue || "");
};

const onUseSuggestFollowupQuestionsChange = (_ev?: React.FormEvent<HTMLElement


| HTMLInputElement>, checked?: boolean) => {
setUseSuggestFollowupQuestions(!!checked);
};

const onUseOidSecurityFilterChange = (_ev?: React.FormEvent<HTMLElement |


HTMLInputElement>, checked?: boolean) => {
setUseOidSecurityFilter(!!checked);
};

const onUseGroupsSecurityFilterChange = (_ev?: React.FormEvent<HTMLElement |


HTMLInputElement>, checked?: boolean) => {
setUseGroupsSecurityFilter(!!checked);
};

const onExampleClicked = (example: string) => {


makeApiRequest(example);
};

const onShowCitation = (citation: string, index: number) => {


if (activeCitation === citation && activeAnalysisPanelTab ===
AnalysisPanelTabs.CitationTab && selectedAnswer === index) {
setActiveAnalysisPanelTab(undefined);
} else {
setActiveCitation(citation);
setActiveAnalysisPanelTab(AnalysisPanelTabs.CitationTab);
}

setSelectedAnswer(index);
};

const onToggleTab = (tab: AnalysisPanelTabs, index: number) => {


if (activeAnalysisPanelTab === tab && selectedAnswer === index) {
setActiveAnalysisPanelTab(undefined);
} else {
setActiveAnalysisPanelTab(tab);
}

setSelectedAnswer(index);
};

return (
<div className={styles.container}>
<div className={styles.commandsContainer}>
<ClearChatButton className={styles.commandButton}
onClick={clearChat} disabled={!lastQuestionRef.current || isLoading} />
<SettingsButton className={styles.commandButton} onClick={() =>
setIsConfigPanelOpen(!isConfigPanelOpen)} />
</div>
<div className={styles.chatRoot}>
<div className={styles.chatContainer}>
{!lastQuestionRef.current ? (
<div className={styles.chatEmptyState}>
<SparkleFilled fontSize={"120px"}
primaryFill={"rgba(115, 118, 225, 1)"} aria-hidden="true" aria-label="Chat logo" />
<h1 className={styles.chatEmptyStateTitle}>Chat with
your data</h1>
<h2 className={styles.chatEmptyStateSubtitle}>Ask
anything or try an example</h2>
<ExampleList onExampleClicked={onExampleClicked}
useGPT4V={useGPT4V} />
</div>
) : (
<div className={styles.chatMessageStream}>
{isStreaming &&

streamedAnswers.map((streamedAnswer, index) => {


const userChatMessageContent =
streamedAnswer[0]; // Content of UserChatMessage
const answerContent =
streamedAnswer[1]; // Content of Answer
// Log the content of
UserChatMessage and Answer to the console
console.log('UserChatMessage
content:', userChatMessageContent);
console.log('Answer content:',
answerContent);
return (
<div key={index}>
<UserChatMessage
message={userChatMessageContent} />
<div
className={styles.chatMessageGpt}>
<Answer
isStreaming={true}
key={index}

answer={answerContent}
isSelected={false}

onCitationClicked={c => onShowCitation(c, index)}

onThoughtProcessClicked={() =>
onToggleTab(AnalysisPanelTabs.ThoughtProcessTab, index)}

onSupportingContentClicked={() =>
onToggleTab(AnalysisPanelTabs.SupportingContentTab, index)}

onFollowupQuestionClicked={q => makeApiRequest(q)}

showFollowupQuestions={useSuggestFollowupQuestions && answers.length - 1 ===


index}
/>
</div>
</div>
);
})
}

{!isStreaming &&
answers.map((answer, index) => {
const userChatMessageContent =
answer[0]; // Content of UserChatMessage
const answerContent = answer[1]; //
Content of Answer

// Log the content of


UserChatMessage and Answer to the console
console.log('UserChatMessage
content:', userChatMessageContent);
console.log('Answer content:',
answerContent);

return (
<div key={index}>
<UserChatMessage
message={userChatMessageContent} />
<div
className={styles.chatMessageGpt}>
<Answer

isStreaming={false}
key={index}

answer={answerContent}

isSelected={selectedAnswer === index && activeAnalysisPanelTab !== undefined}

onCitationClicked={c => onShowCitation(c, index)}

onThoughtProcessClicked={() =>
onToggleTab(AnalysisPanelTabs.ThoughtProcessTab, index)}

onSupportingContentClicked={() =>
onToggleTab(AnalysisPanelTabs.SupportingContentTab, index)}

onFollowupQuestionClicked={q => makeApiRequest(q)}

showFollowupQuestions={useSuggestFollowupQuestions && answers.length - 1 ===


index}
/>
</div>
</div>
);
})
}

{isLoading && (
<>
<UserChatMessage
message={lastQuestionRef.current} />
<div className={styles.chatMessageGptMinWidth}>
<AnswerLoading />
</div>
</>
)}
{error ? (
<>
<UserChatMessage
message={lastQuestionRef.current} />
<div className={styles.chatMessageGptMinWidth}>
<AnswerError error={error.toString()}
onRetry={() => makeApiRequest(lastQuestionRef.current)} />
</div>
</>
) : null}
<div ref={chatMessageStreamEnd} />
</div>
)}

<div className={styles.chatInput}>
<QuestionInput
clearOnSend
placeholder="Type a new question (e.g. does my plan
cover annual eye exams?)"
disabled={isLoading}
onSend={question => makeApiRequest(question)}
/>
</div>
</div>

{answers.length > 0 && activeAnalysisPanelTab && (


<AnalysisPanel
className={styles.chatAnalysisPanel}
activeCitation={activeCitation}
onActiveTabChanged={x => onToggleTab(x, selectedAnswer)}
citationHeight="810px"
answer={answers[selectedAnswer][1]}
activeTab={activeAnalysisPanelTab}
/>
)}

<Panel
headerText="Configure answer generation"
isOpen={isConfigPanelOpen}
isBlocking={false}
onDismiss={() => setIsConfigPanelOpen(false)}
closeButtonAriaLabel="Close"
onRenderFooterContent={() => <DefaultButton onClick={() =>
setIsConfigPanelOpen(false)}>Close</DefaultButton>}
isFooterAtBottom={true}
>
<TextField
className={styles.chatSettingsSeparator}
defaultValue={promptTemplate}
label="Override prompt template"
multiline
autoAdjustHeight
onChange={onPromptTemplateChange}
/>

<SpinButton
className={styles.chatSettingsSeparator}
label="Retrieve this many search results:"
min={1}
max={50}
defaultValue={retrieveCount.toString()}
onChange={onRetrieveCountChange}
/>
<TextField className={styles.chatSettingsSeparator}
label="Exclude category" onChange={onExcludeCategoryChanged} />
<Checkbox
className={styles.chatSettingsSeparator}
checked={useSemanticRanker}
label="Use semantic ranker for retrieval"
onChange={onUseSemanticRankerChange}
/>
<Checkbox
className={styles.chatSettingsSeparator}
checked={useSemanticCaptions}
label="Use query-contextual summaries instead of whole
documents"
onChange={onUseSemanticCaptionsChange}
disabled={!useSemanticRanker}
/>
<Checkbox
className={styles.chatSettingsSeparator}
checked={useSuggestFollowupQuestions}
label="Suggest follow-up questions"
onChange={onUseSuggestFollowupQuestionsChange}
/>

{showGPT4VOptions && (
<GPT4VSettings
gpt4vInputs={gpt4vInput}
isUseGPT4V={useGPT4V}
updateUseGPT4V={useGPT4V => {
setUseGPT4V(useGPT4V);
}}
updateGPT4VInputs={inputs => setGPT4VInput(inputs)}
/>
)}

<VectorSettings
showImageOptions={useGPT4V && showGPT4VOptions}
updateVectorFields={(options: VectorFieldOptions[]) =>
setVectorFieldList(options)}
updateRetrievalMode={(retrievalMode: RetrievalMode) =>
setRetrievalMode(retrievalMode)}
/>

{useLogin && (
<Checkbox
className={styles.chatSettingsSeparator}
checked={useOidSecurityFilter || requireAccessControl}
label="Use oid security filter"
disabled={!isLoggedIn(client) || requireAccessControl}
onChange={onUseOidSecurityFilterChange}
/>
)}
{useLogin && (
<Checkbox
className={styles.chatSettingsSeparator}
checked={useGroupsSecurityFilter ||
requireAccessControl}
label="Use groups security filter"
disabled={!isLoggedIn(client) || requireAccessControl}
onChange={onUseGroupsSecurityFilterChange}
/>
)}

<Checkbox
className={styles.chatSettingsSeparator}
checked={shouldStream}
label="Stream chat completion responses"
onChange={onShouldStreamChange}
/>
{useLogin && <TokenClaimsDisplay />}
</Panel>
</div>
</div>
);
};

export default Chat;

You might also like