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
109 changes: 71 additions & 38 deletions src/app/LightspeedChatbot/LightspeedChatbot.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { useDocumentTitle } from '@app/utils/useDocumentTitle';
import { Bullseye, DropdownGroup, DropdownItem, DropdownList, Title, TitleSizes } from '@patternfly/react-core';
import { Bullseye, DropdownGroup, DropdownItem, DropdownList, Flex, FlexItem, Title, TitleSizes } from '@patternfly/react-core';

// Chatbot components
import ChatbotToggle from '@patternfly/chatbot/dist/dynamic/ChatbotToggle';
Expand All @@ -20,6 +20,9 @@ import ChatbotHeader, {
ChatbotHeaderSelectorDropdown,
ChatbotHeaderTitle
} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader';
import FileDropZone from '@patternfly/chatbot/dist/dynamic/FileDropZone';
import FileDetailsLabel from '@patternfly/chatbot/dist/dynamic/FileDetailsLabel';
import ChatbotAlert from '@patternfly/chatbot/dist/dynamic/ChatbotAlert';

// Icons
import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
Expand All @@ -29,9 +32,7 @@ import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/ou
// Local imports
import { useChatbot } from './hooks/useChatbot';
import { ToolExecutionCards } from './components/ToolExecutionCards';
import { FOOTNOTE_PROPS, INITIAL_CONVERSATIONS, INITIAL_WELCOME_PROMPTS } from './constants';
import { findMatchingItems } from './utils/helpers';
import { Conversation } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
import { FOOTNOTE_PROPS, INITIAL_WELCOME_PROMPTS } from './constants';

/**
* Main Lightspeed Chatbot Component
Expand All @@ -40,13 +41,13 @@ import { Conversation } from '@patternfly/chatbot/dist/dynamic/ChatbotConversati
* - Model selection
* - Streaming responses
* - Tool execution tracking
* - File attachments
* - Conversation history
* - Multiple display modes (overlay, docked, fullscreen)
*/
const LightspeedChatbot: React.FunctionComponent = () => {
useDocumentTitle('Lightspeed Chatbot');

// Use the custom hook for all chatbot logic
const {
chatbotVisible,
displayMode,
Expand All @@ -56,18 +57,30 @@ const LightspeedChatbot: React.FunctionComponent = () => {
isSendButtonDisabled,
isDrawerOpen,
conversations,
currentConversationId,
announcement,
toolExecutions,
scrollToBottomRef,
attachedFiles,
isLoadingFile,
fileError,
showFileAlert,
onSelectModel,
onSelectDisplayMode,
handleSend,
handleAttach,
handleFileDrop,
onAttachmentClose,
onCloseFileAlert,
handleTextInputChange,
handleConversationSelect,
setChatbotVisible,
setMessages,
setConversations,
setCurrentConversationId,
setIsDrawerOpen
} = useChatbot();
} = useChatbot();



// Enhanced message rendering with tool execution support
const renderMessages = () => {
Expand Down Expand Up @@ -113,29 +126,23 @@ const LightspeedChatbot: React.FunctionComponent = () => {
displayMode={displayMode}
onDrawerToggle={() => {
setIsDrawerOpen(!isDrawerOpen);
setConversations(INITIAL_CONVERSATIONS);
}}
isDrawerOpen={isDrawerOpen}
setIsDrawerOpen={setIsDrawerOpen}
activeItemId="1"
// eslint-disable-next-line no-console
onSelectActiveItem={(e, selectedItem) => console.log(`Selected history item with id ${selectedItem}`)}
activeItemId={currentConversationId}
onSelectActiveItem={(e, selectedItem) => {
console.log(`Selected history item with id ${selectedItem}`);
if (typeof selectedItem === 'string') {
handleConversationSelect(selectedItem);
}
}}
conversations={conversations}
onNewChat={() => {
setIsDrawerOpen(!isDrawerOpen);
setMessages([]);
setConversations(INITIAL_CONVERSATIONS);
setCurrentConversationId('');
}}
handleTextInputChange={(value: string) => {
if (value === '') {
setConversations(INITIAL_CONVERSATIONS);
}
// this is where you would perform search on the items in the drawer
// and update the state
const newConversations: { [key: string]: Conversation[] } = findMatchingItems(value);
setConversations(newConversations);
}}
handleTextInputChange={handleTextInputChange}
drawerContent={
<>
<ChatbotHeader>
Expand All @@ -145,7 +152,7 @@ const LightspeedChatbot: React.FunctionComponent = () => {
displayMode={displayMode}
showOnFullScreen={horizontalLogo}
showOnDefault={iconLogo}
></ChatbotHeaderTitle>
/>
</ChatbotHeaderMain>
<ChatbotHeaderActions>
<ChatbotHeaderSelectorDropdown value={selectedModel} onSelect={onSelectModel}>
Expand Down Expand Up @@ -196,32 +203,58 @@ const LightspeedChatbot: React.FunctionComponent = () => {
</ChatbotHeaderOptionsDropdown>
</ChatbotHeaderActions>
</ChatbotHeader>
<ChatbotContent>
{/* Update the announcement prop on MessageBox whenever a new message is sent
so that users of assistive devices receive sufficient context */}
<MessageBox announcement={announcement}>
<ChatbotWelcomePrompt
title="Hello, Lightspeed User"
description="How may I help you today?"
prompts={INITIAL_WELCOME_PROMPTS}
/>
{/* Display all messages */}
{renderMessages()}
{/* Scroll reference at the bottom of all messages for proper streaming behavior */}
<div ref={scrollToBottomRef}/>
</MessageBox>
</ChatbotContent>
<FileDropZone onFileDrop={handleFileDrop} displayMode={displayMode}>
<ChatbotContent>
<MessageBox announcement={announcement}>
{showFileAlert && (
<ChatbotAlert
variant="danger"
onClose={onCloseFileAlert}
title="File upload failed"
>
{fileError}
</ChatbotAlert>
)}
<ChatbotWelcomePrompt
title="Hello, Lightspeed User"
description="How may I help you today?"
prompts={INITIAL_WELCOME_PROMPTS}
/>
{renderMessages()}
<div ref={scrollToBottomRef}/>
</MessageBox>
</ChatbotContent>
</FileDropZone>
<ChatbotFooter>
<Flex
direction={{ default: 'row' }}
flexWrap={{ default: 'nowrap' }}
spaceItems={{ default: 'spaceItemsSm' }}
style={{ overflowX: 'auto', overflowY: 'hidden', paddingBottom: '8px' }}
>
{attachedFiles.map((file, index) => (
<FlexItem key={index} flex={{ default: 'flexNone' }}>
<FileDetailsLabel
key={index}
fileName={file.name}
isLoading={isLoadingFile && index === attachedFiles.length - 1}
onClose={() => onAttachmentClose(index)}
/>
</FlexItem>
))}
</Flex>
<MessageBar
onSendMessage={handleSend}
handleAttach={handleAttach}
hasAttachButton
hasMicrophoneButton
isSendButtonDisabled={isSendButtonDisabled}
/>
<ChatbotFootnote {...FOOTNOTE_PROPS} />
</ChatbotFooter>
</>
}
></ChatbotConversationHistoryNav>
/>
</Chatbot>
</>
);
Expand Down
25 changes: 16 additions & 9 deletions src/app/LightspeedChatbot/components/ToolExecutionCards.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Card, CardBody, CardTitle } from '@patternfly/react-core';
import { Card, CardBody, CardTitle, Flex, FlexItem } from '@patternfly/react-core';
import { ToolExecutionCardsProps } from '../types';

/**
Expand All @@ -12,15 +12,22 @@ export const ToolExecutionCards: React.FC<ToolExecutionCardsProps> = ({ tools })
}

return (
<React.Fragment>
<Flex
direction={{ default: 'row' }}
flexWrap={{ default: 'nowrap' }}
spaceItems={{ default: 'spaceItemsSm' }}
style={{ overflowX: 'auto', overflowY: 'hidden', paddingBottom: '8px' }}
>
{tools.map((tool, index) => (
<Card key={index} isCompact>
<CardTitle>Tool Execution</CardTitle>
<CardBody>
Using tool: <strong>{tool}</strong>
</CardBody>
</Card>
<FlexItem key={index} flex={{ default: 'flexNone' }}>
<Card isCompact >
<CardTitle>Tool Execution</CardTitle>
<CardBody>
Using tool: <strong>{tool}</strong>
</CardBody>
</Card>
</FlexItem>
))}
</React.Fragment>
</Flex>
);
};
3 changes: 2 additions & 1 deletion src/app/LightspeedChatbot/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
import { WelcomePrompt } from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
import { Conversation } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';

// API Configuration
export const API_BASE_URL = 'http://localhost:8080';
Expand All @@ -13,7 +14,7 @@ export const BOT_AVATAR =
// Initial states
export const INITIAL_MESSAGES: MessageProps[] = [];
export const INITIAL_WELCOME_PROMPTS: WelcomePrompt[] = [];
export const INITIAL_CONVERSATIONS = {};
export const INITIAL_CONVERSATIONS: Conversation[] = [];

// Default system prompt
export const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
Expand Down
Loading