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
8 changes: 4 additions & 4 deletions app-frontend/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"test": "vitest"
},
"dependencies": {
"@mantine/charts": "7.13.4",
"@mantine/core": "^7.13.4",
"@mantine/hooks": "^7.13.4",
"@mantine/notifications": "^7.13.4",
"@mantine/charts": "7.17.2",
"@mantine/core": "^7.17.2",
"@mantine/hooks": "^7.17.2",
"@mantine/notifications": "^7.17.2",
"@microsoft/fetch-event-source": "^2.0.1",
"@reduxjs/toolkit": "^2.2.5",
"@tabler/icons-react": "3.7.0",
Expand Down
263 changes: 137 additions & 126 deletions app-frontend/react/src/components/Conversation/Conversation.tsx
Original file line number Diff line number Diff line change
@@ -1,134 +1,132 @@
import { KeyboardEventHandler, SyntheticEvent, useEffect, useRef, useState } from 'react'
import styleClasses from "./conversation.module.scss"
import { ActionIcon, Group, rem, Slider, Stack, Text, Textarea, Title, Tooltip } from '@mantine/core'
import { IconArrowRight, IconFilePlus, IconMessagePlus } from '@tabler/icons-react'
import { conversationSelector, doConversation, newConversation } from '../../redux/Conversation/ConversationSlice'
import { ConversationMessage } from '../Message/conversationMessage'
import { useAppDispatch, useAppSelector } from '../../redux/store'
import { Message, MessageRole } from '../../redux/Conversation/Conversation'
import { UiFeatures } from '../../common/Sandbox'
import { getCurrentTimeStamp } from '../../common/util'
import { useDisclosure } from '@mantine/hooks'
import DataSource from './DataSource'
import { ConversationSideBar } from './ConversationSideBar'
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

type ConversationProps = {
title: string
enabledUiFeatures: UiFeatures
}

const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
const [prompt, setPrompt] = useState<string>("")
const [systemPrompt, setSystemPrompt] = useState<string>("You are a helpful assistant.")
const promptInputRef = useRef<HTMLTextAreaElement>(null)
const [fileUploadOpened, { open: openFileUpload, close: closeFileUpload }] = useDisclosure(false)
import { KeyboardEventHandler, SyntheticEvent, useEffect, useRef, useState } from 'react';
import styleClasses from "./conversation.module.scss";
import { ActionIcon, Button, Collapse, Group, rem, Slider, Stack, Text, Textarea, Title, Tooltip } from '@mantine/core';
import { IconArrowRight, IconChevronDown, IconChevronUp, IconFilePlus, IconMessagePlus } from '@tabler/icons-react';

const { conversations, onGoingResult, selectedConversationId } = useAppSelector(conversationSelector)
const dispatch = useAppDispatch()
const selectedConversation = conversations.find(x => x.conversationId === selectedConversationId)
const scrollViewport = useRef<HTMLDivElement>(null)
import { conversationSelector, doConversation, newConversation, isAgentSelector, getCurrentAgentSteps } from '../../redux/Conversation/ConversationSlice';
import { ConversationMessage } from '../Message/conversationMessage';
import { useAppDispatch, useAppSelector } from '../../redux/store';
import { Message, MessageRole } from '../../redux/Conversation/Conversation';
import { UiFeatures } from '../../common/Sandbox';
import { getCurrentTimeStamp } from '../../common/util';
import { useDisclosure } from '@mantine/hooks';
import DataSource from './DataSource';
import { ConversationSideBar } from './ConversationSideBar';

const [tokenLimit, setTokenLimit] = useState<number>(50)
const [temperature, setTemperature] = useState<number>(0.30)
type ConversationProps = {
title: string;
enabledUiFeatures: UiFeatures;
};

// State for tracking tokens and message processing time
const [messageTokenData, setMessageTokenData] = useState<{ [key: string]: { tokens: number; rate: number, time: number } }>({})
const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
const [prompt, setPrompt] = useState<string>("");
const [systemPrompt, setSystemPrompt] = useState<string>("You are a helpful assistant.");
const promptInputRef = useRef<HTMLTextAreaElement>(null);
const [fileUploadOpened, { open: openFileUpload, close: closeFileUpload }] = useDisclosure(false);

const [currentMessageIndex, setCurrentMessageIndex] = useState<number>(-1)

// New state to track the start time for calculating tokens per second
const [startTime, setStartTime] = useState<number | null>(null)
const { conversations, onGoingResult, selectedConversationId } = useAppSelector(conversationSelector);
const isAgent = useAppSelector(isAgentSelector);
const dispatch = useAppDispatch();
const selectedConversation = conversations.find(x => x.conversationId === selectedConversationId);
const scrollViewport = useRef<HTMLDivElement>(null);

// New state to manage the assistant's message placeholder
const [isAssistantTyping, setIsAssistantTyping] = useState<boolean>(false)
const [tokenLimit, setTokenLimit] = useState<number>(50);
const [temperature, setTemperature] = useState<number>(0.30);

const toSend = "Enter"
const [messageTokenData, setMessageTokenData] = useState<{ [key: string]: { tokens: number; rate: number; time: number } }>({});
const [currentMessageIndex, setCurrentMessageIndex] = useState<number>(-1);
const [startTime, setStartTime] = useState<number | null>(null);
const [isAssistantTyping, setIsAssistantTyping] = useState<boolean>(false);
const [showInferenceParams, setShowInferenceParams] = useState<boolean>(true);

// const systemPrompt: Partial<Message> = {
// role: MessageRole.System,
// content: "You are a helpful assistant",
// }
const toSend = "Enter";

const handleSubmit = () => {
const userPrompt: Message = {
role: MessageRole.User,
content: prompt,
time: getCurrentTimeStamp(),
}
let messages: Partial<Message>[] = []
};

let messages: Partial<Message>[] = [];
if (selectedConversation) {
messages = selectedConversation.Messages.map((message) => {
return { role: message.role, content: message.content }
})
return { role: message.role, content: message.content };
});
}

messages = [{ role: MessageRole.System, content: systemPrompt }, ...messages]

// Initialize token data for the new message

messages = [{ role: MessageRole.System, content: systemPrompt }, ...messages];

setMessageTokenData((prev) => ({
...prev,
[`${selectedConversationId}-${selectedConversation?.Messages.length}`]: { tokens: 0, rate: 0, time: 0 },
}))

// Set the current message index for tracking
setCurrentMessageIndex(selectedConversation?.Messages.length || 0)

}));

setCurrentMessageIndex(selectedConversation?.Messages.length || 0);

doConversation({
conversationId: selectedConversationId,
userPrompt,
messages,
maxTokens: tokenLimit,
temperature: temperature,
model: "Intel/neural-chat-7b-v3-3",
})
setPrompt("")
setStartTime(Date.now()) // Set start time when the user submits the message
setIsAssistantTyping(true) // Show the assistant's typing placeholder immediately
}
conversationId: selectedConversationId,
userPrompt,
messages,
maxTokens: tokenLimit,
temperature: temperature,
model: "Intel/neural-chat-7b-v3-3",
});
setPrompt("");
setStartTime(Date.now());
setIsAssistantTyping(true);
};

const scrollToBottom = () => {
scrollViewport.current!.scrollTo({ top: scrollViewport.current!.scrollHeight })
}
scrollViewport.current!.scrollTo({ top: scrollViewport.current!.scrollHeight });
};

useEffect(() => {
// Update token data for the current message
if (onGoingResult && startTime && currentMessageIndex !== -1) {
const tokenLength = onGoingResult.split(" ").length // Estimate tokens based on words
const currentTimestamp = Date.now()

const elapsedTime = (currentTimestamp - startTime) / 1000 // seconds
const tokenRate = elapsedTime > 0 ? tokenLength / elapsedTime : 0

// Update token data for the current message
let tokenLength: number;
if (isAgent) {
const currentSteps = getCurrentAgentSteps();
const allContent = currentSteps.flatMap(step => step.content).join(" ");
tokenLength = allContent.split(" ").length;
} else {
tokenLength = onGoingResult.split(" ").length;
}

const currentTimestamp = Date.now();
const elapsedTime = (currentTimestamp - startTime) / 1000;
const tokenRate = elapsedTime > 0 ? tokenLength / elapsedTime : 0;

setMessageTokenData((prev) => ({
...prev,
[`${selectedConversationId}-${currentMessageIndex}`]: { tokens: tokenLength, rate: tokenRate, time: elapsedTime },
}))
setIsAssistantTyping(false)
}));

setIsAssistantTyping(false);
}
scrollToBottom()
}, [onGoingResult, startTime, selectedConversation?.Messages, currentMessageIndex])

scrollToBottom();
}, [onGoingResult, startTime, selectedConversation?.Messages, currentMessageIndex, isAgent]);

const handleKeyDown: KeyboardEventHandler = (event) => {
if (!event.shiftKey && event.key === toSend) {
handleSubmit()
handleSubmit();
setTimeout(() => {
setPrompt("")
}, 1)
setPrompt("");
}, 1);
}
}
};

const handleNewConversation = () => {
dispatch(newConversation())
}
dispatch(newConversation());
};

const handleChange = (event: SyntheticEvent) => {
event.preventDefault()
setPrompt((event.target as HTMLTextAreaElement).value)
}
event.preventDefault();
setPrompt((event.target as HTMLTextAreaElement).value);
};

return (
<div className={styleClasses.conversationWrapper}>
Expand All @@ -140,11 +138,10 @@ const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
<span className={styleClasses.spacer}></span>
<Group>
{selectedConversation && selectedConversation?.Messages.length > 0 && (
<ActionIcon onClick={handleNewConversation} disabled={onGoingResult != ""} size={32} variant="default">
<ActionIcon onClick={handleNewConversation} disabled={onGoingResult !== ""} size={32} variant="default">
<IconMessagePlus />
</ActionIcon>
)}

<Tooltip
label={enabledUiFeatures.dataprep ? "Upload File" : "Data Prep node is not found in the flow."}
color={enabledUiFeatures.dataprep ? "blue" : "red"}
Expand All @@ -167,13 +164,11 @@ const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
)}

{selectedConversation?.Messages.map((message, index) => {
const messageKey = `${selectedConversationId}-${index-1}`
const tokenData = messageTokenData[messageKey]
const elapsedTime = tokenData?.time ?? 0
const tokens = tokenData?.tokens ?? 0
const rate = tokenData?.rate ?? 0

console.log("Message: ", message, "Message Key: ", messageKey, "Token Data: ", tokenData)
const messageKey = `${selectedConversationId}-${index - 1}`;
const tokenData = messageTokenData[messageKey];
const elapsedTime = tokenData?.time ?? 0;
const tokens = tokenData?.tokens ?? 0;
const rate = tokenData?.rate ?? 0;

return (
<ConversationMessage
Expand All @@ -184,19 +179,21 @@ const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
elapsedTime={message.role === MessageRole.Assistant ? elapsedTime : undefined}
tokenCount={message.role === MessageRole.Assistant ? tokens : undefined}
tokenRate={message.role === MessageRole.Assistant ? rate : undefined}
agentSteps={message.agentSteps || []}
/>
)
);
})}

{selectedConversation && isAssistantTyping && (
<ConversationMessage
key={`_ai_placeholder`}
date={Date.now()}
human={false}
message={"..."} // Placeholder text while the response is being generated
elapsedTime={0} // Start with 0 seconds
tokenCount={0} // Start with 0 tokens
tokenRate={0} // Start with 0 tokens per second
message={"..."}
elapsedTime={0}
tokenCount={0}
tokenRate={0}
agentSteps={getCurrentAgentSteps()}
/>
)}

Expand All @@ -209,27 +206,41 @@ const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
elapsedTime={messageTokenData[`${selectedConversationId}-${currentMessageIndex}`]?.time}
tokenCount={messageTokenData[`${selectedConversationId}-${currentMessageIndex}`]?.tokens}
tokenRate={messageTokenData[`${selectedConversationId}-${currentMessageIndex}`]?.rate}
agentSteps={getCurrentAgentSteps()}
/>
)}
)}
</div>

<div className={styleClasses.conversatioSliders}>
<Stack style={{ marginLeft: '10px' }}>
<Title size="sm">Inference Settings</Title>
<Text size="sm">Token Limit: {tokenLimit}</Text>
<Slider value={tokenLimit} onChange={setTokenLimit} min={10} max={500} step={1} />
<Text size="sm">Temperature: {temperature.toFixed(2)}</Text>
<Slider value={temperature} onChange={setTemperature} min={0.10} max={1.00} step={0.01} />
<Textarea
label="System Prompt"
placeholder="Set system prompt"
value={systemPrompt}
onChange={(e) => setSystemPrompt(e.target.value)}
size="sm"
mb="sm"
/>
</Stack>
</div>
<Button
variant="light"
size="xs"
radius="xl"
onClick={() => setShowInferenceParams(!showInferenceParams)}
rightSection={showInferenceParams ? <IconChevronDown size={14} /> : <IconChevronUp size={14} />}
mb="xs"
>
{showInferenceParams ? "Hide Inference Settings" : "Show Inference Settings"}
</Button>
<Collapse in={showInferenceParams} mb="md">
<Stack style={{ marginLeft: '10px' }}>
<Title size="sm">Inference Settings</Title>
<Text size="sm">Token Limit: {tokenLimit}</Text>
<Slider value={tokenLimit} onChange={setTokenLimit} min={10} max={500} step={1} />
<Text size="sm">Temperature: {temperature.toFixed(2)}</Text>
<Slider value={temperature} onChange={setTemperature} min={0.10} max={1.00} step={0.01} />
<Textarea
label="System Prompt"
placeholder="Set system prompt"
value={systemPrompt}
onChange={(e) => setSystemPrompt(e.target.value)}
size="sm"
mb="sm"
/>
</Stack>
</Collapse>
</div>


<div className={styleClasses.conversationActions}>
<Tooltip
Expand Down Expand Up @@ -258,7 +269,7 @@ const Conversation = ({ title, enabledUiFeatures }: ConversationProps) => {
</div>
<DataSource opened={fileUploadOpened} onClose={closeFileUpload} />
</div>
)
}
);
};

export default Conversation
export default Conversation;
Loading
Loading