Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions src/components/Chat/Chat.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
height: calc(100%);

#chat-scroll-container {
height: calc(100% - 35px - 2.5rem);
height: calc(100% - 35px);
overflow-y: auto;
margin-bottom: 3px;
padding: 0.5rem;
padding-top: 1rem;
padding-bottom: 1.5rem;

pre {
white-space: pre-wrap;
Expand Down Expand Up @@ -59,6 +61,35 @@
text-align: right;
}
}

.condensed-chat {
background-color: #333;
padding: 0.5rem;
border-radius: 5px;
border: 1px solid #555;

margin-bottom: 1.5rem;

color: white;
position: relative;

> p:first-child {
margin-top: 0;
}

a {
font-size: 0.8rem;
cursor: pointer;
text-decoration: underline;
}

.show-all-content-container {
padding: 0.5rem;
padding-bottom: 0;
border-radius: 5px;
background-color: #222;
}
}
}

.chat-status-badge {
Expand All @@ -69,8 +100,10 @@
left: 0;
right: 0;
padding: 0.5rem;
background: #22222280;
background: linear-gradient(0deg, rgba(34,34,34,0.75) 0%, rgba(34,34,34,0) 100%);

:global(.mantine-Badge-root) {
box-shadow: 0 0 3px 3px rgba(0,0,0,0.3);
}
}


Expand Down
151 changes: 132 additions & 19 deletions src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,17 @@ import { useAppDispatch, useAppSelector } from 'redux/store';
import { tryParsingOutQuery } from 'utils/tryParsingOutQuery';

import styles from "./Chat.module.scss"
import { LinkQChatMessageType } from 'redux/chatHistorySlice';


export function Chat() {
const fullChatHistory = useAppSelector(state => state.chatHistory.fullChatHistory)
const simpleChatHistory = useAppSelector(state => state.chatHistory.simpleChatHistory)

const showFullChatHistory = useAppSelector(state => state.chatHistory.showFullChatHistory)
const chatHistoryDisplay = useAppSelector(state => state.chatHistory.chatHistoryDisplay)

//based on showFullChatHistory, decide which chat history to display to the user
const chatHistory = showFullChatHistory ? fullChatHistory : simpleChatHistory
//based on chatHistoryDisplay, decide which chat history to display to the user
const chatHistory = chatHistoryDisplay==="full" ? fullChatHistory : simpleChatHistory

const chatScrollBottomRef = useRef<HTMLDivElement>(null)
useEffect(() => {
Expand All @@ -55,20 +56,16 @@ export function Chat() {
<Settings/>

<div id={styles["chat-scroll-container"]}>
{chatHistory.map((c, i) => {
return (
<div key={i} className={`${styles["chat-row"]} ${styles[c.role]}`}>
<div className={styles["chat-justify"]}>
{showFullChatHistory && <p>{c.name}, chat #{c.chatId}</p>}
{
c.role === "assistant"
? <RenderLLMResponse text={c.content} setInputText={setInputText}/>
: <pre className={styles.chat}>{c.content}</pre>
}
</div>
</div>
)
})}
<br/>
{chatHistoryDisplay==="condensed" ? (
condenseChat(fullChatHistory).map((c,i) => (
<RenderCondensedMessage key={i} condensedChat={c} setInputText={setInputText}/>
))
) : (
chatHistory.map((c, i) => (
<RenderChatMessage key={i} chat={c} setInputText={setInputText}/>
))
)}
<div ref={chatScrollBottomRef}/>
</div>

Expand Down Expand Up @@ -99,6 +96,68 @@ export function Chat() {
)
}

function RenderCondensedMessage({
condensedChat,
setInputText,
}:{
condensedChat:CondensedChatType,
setInputText: React.Dispatch<React.SetStateAction<string>>,
}) {
const [showDetails, setShowDetails] = useState<boolean>(false)

const firstChatMessage = condensedChat[0]
return (
<div className={styles["condensed-chat"]}>
{firstChatMessage.stage && (
<>
<p><b>{firstChatMessage.stage.mainStage}</b></p>
<p>{firstChatMessage.stage.subStage}</p>
{firstChatMessage.stage.description && <p>{firstChatMessage.stage.description}</p>}
</>
)}
<div>
<a aria-label="Show Details" onClick={() => setShowDetails(!showDetails)}>
{showDetails ? "Hide Details" : "Show Full Details"}
</a>
</div>

{showDetails && (
<div>
<br/>
<div className={styles["show-all-content-container"]}>
{condensedChat.map((c, i) => (
<RenderChatMessage key={i} chat={c} setInputText={setInputText}/>
))}
</div>
</div>
)}
</div>
)
}

function RenderChatMessage({
chat,
setInputText,
}:{
chat:LinkQChatMessageType,
setInputText: React.Dispatch<React.SetStateAction<string>>,
}) {
const chatHistoryDisplay = useAppSelector(state => state.chatHistory.chatHistoryDisplay)

return (
<div className={`${styles["chat-row"]} ${styles[chat.role]}`}>
<div className={styles["chat-justify"]}>
{chatHistoryDisplay==="full"||chatHistoryDisplay==="condensed" && <p>{chat.name}, chat #{chat.chatId}</p>}
{
chat.role === "assistant"
? <RenderLLMResponse text={chat.content} setInputText={setInputText}/>
: <pre className={styles.chat}>{chat.content}</pre>
}
</div>
</div>
)
}

function RenderLLMResponse({
setInputText,
text,
Expand Down Expand Up @@ -220,6 +279,60 @@ function LinkQDetailedBadgeStatus() {
}

return (
<div className={styles["chat-status-badge"]}><Badge color={color}>{displayMessage}</Badge></div>
<div className={styles["chat-status-badge"]}>
<Badge color={color}>{displayMessage}</Badge>
</div>
)
}
}

//the condensed chat type is just one or two grouped chat messages
type CondensedChatType = LinkQChatMessageType[]

/**
* Converts the full chat history into the condensed/grouped view for better traceability.
* If two neighboring chat messages have the same stage details, they are condensed/grouped together
* @param fullChatHistory
* @returns array of condensed chat types
*/
function condenseChat(fullChatHistory: LinkQChatMessageType[]):CondensedChatType[] {
const condensedChat:CondensedChatType[] = [];

//loop through the full chat history
for(let i=0; i<fullChatHistory.length; ++i) {
const currentChatMessage = fullChatHistory[i]
const nextChatMessage = fullChatHistory.at(i + 1)
if(i === 0) { //HARDCODED ignore the initial system prompt
continue;
}
//else if the current and next chat message have the same stage details
else if(nextChatMessage && messagesHaveMatchingStages(currentChatMessage,nextChatMessage)) {
//condense these two messages together
condensedChat.push([
currentChatMessage,
nextChatMessage
]);
++i; //skip over this next message in the subsequent iteration
}
//else this chat message can stand alone by itself
else if(currentChatMessage.stage) {
condensedChat.push([ currentChatMessage ])
}
//else the chat message doesn't have stage info
}

return condensedChat
}

/**
* Checks whether two messages have the same main and sub stage
* @param chatMessage1
* @param chatMessage2
* @returns true if the main and sub stages match (including in the stage is undefined), else false
*/
function messagesHaveMatchingStages(
chatMessage1: LinkQChatMessageType,
chatMessage2: LinkQChatMessageType,
) {
return chatMessage1.stage?.mainStage===chatMessage2.stage?.mainStage
|| chatMessage1.stage?.subStage===chatMessage2.stage?.subStage
}
18 changes: 12 additions & 6 deletions src/components/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ErrorMessage } from "components/ErrorMessage"
import { useMainChatAPI } from "hooks/useMainChatAPI"

import { setApiKey, setBaseURL, setModel, toggleShowStateDiagramStatus } from "redux/settingsSlice"
import { toggleShowFullChatHistory } from "redux/chatHistorySlice"
import { CHAT_HISTORY_DISPLAY_OPTIONS, ChatHistoryDisplayType, setChatHistoryDisplay } from "redux/chatHistorySlice"
import { useAppDispatch, useAppSelector } from "redux/store"

import styles from "./Settings.module.scss"
Expand All @@ -22,7 +22,7 @@ export function Settings() {
const baseURL = useAppSelector(state => state.settings.baseURL)
const model = useAppSelector(state => state.settings.model)
const showStateDiagramStatus = useAppSelector(state => state.settings.showStateDiagramStatus)
const showFullChatHistory = useAppSelector(state => state.chatHistory.showFullChatHistory)
const chatHistoryDisplay = useAppSelector(state => state.chatHistory.chatHistoryDisplay)

const [showSettingsModal, setShowSettingsModal] = useState<boolean>(false)
const closeSettingsModal = () => setShowSettingsModal(false)
Expand All @@ -48,10 +48,16 @@ export function Settings() {
return (
<>
<Modal opened={showSettingsModal} onClose={closeSettingsModal} title="Settings">
<Checkbox
checked={showFullChatHistory}
onChange={() => dispatch(toggleShowFullChatHistory())}
label="Show full chat history"
<Select
label="Chat History View"
placeholder="Set chat history complexity"
data={Object.entries(CHAT_HISTORY_DISPLAY_OPTIONS).map(([value,label]) => ({
value,label
}))}
value={chatHistoryDisplay}
onChange={(value) => value && dispatch(setChatHistoryDisplay(
value as ChatHistoryDisplayType
))}
/>
<br/>
<Checkbox
Expand Down
22 changes: 15 additions & 7 deletions src/redux/chatHistorySlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@ export type LinkQChatMessageType = ChatCompletionMessageParam & {
stage?: StageType,
}

//"simple" is a bare-bones for novice users
//"condensed" shows more info for intermediate users
//"full" shows all the messages for expert users
export const CHAT_HISTORY_DISPLAY_OPTIONS = {
"simple": "Simple View",
"condensed": "Condensed View",
"full": "Full Chat History",
} as const
export type ChatHistoryDisplayType = keyof typeof CHAT_HISTORY_DISPLAY_OPTIONS

const initialState: {
chatIdCounter: number,
fullChatHistory: LinkQChatMessageType[],
simpleChatHistory: LinkQChatMessageType[],
showFullChatHistory: boolean,
chatHistoryDisplay: ChatHistoryDisplayType,
} = {
chatIdCounter: 1,

//state for the full chat history, including system messages and the LLM interfacing with the KG API
fullChatHistory: IS_DEMO_MODE ? DEMO_FULL_HISTORY : [],

//this option toggles showing the full chat history for an ML expert user
//vs hiding the system messages for a non-expert user
showFullChatHistory: true,
chatHistoryDisplay: "condensed",

//state for the filtered chat history (ie no behind-the-scenes system messages)
simpleChatHistory: IS_DEMO_MODE ? DEMO_SIMPLE_HISTORY : [],
Expand All @@ -45,8 +53,8 @@ const chatHistorySlice = createSlice({
incrementChatIdCounter: (state) => {
state.chatIdCounter++
},
toggleShowFullChatHistory: state => {
state.showFullChatHistory = !state.showFullChatHistory
setChatHistoryDisplay: (state, action: PayloadAction<ChatHistoryDisplayType>) => {
state.chatHistoryDisplay = action.payload
}
}
})
Expand All @@ -57,6 +65,6 @@ export const {
addMessagesToFullChatHistory,
addMessageToSimpleChatHistory,
incrementChatIdCounter,
toggleShowFullChatHistory,
setChatHistoryDisplay,
}
} = chatHistorySlice
Loading