@@ -550,7 +217,7 @@ const LightspeedChatbot: React.FunctionComponent = () => {
hasMicrophoneButton
isSendButtonDisabled={isSendButtonDisabled}
/>
-
+
>
}
diff --git a/src/app/LightspeedChatbot/README.md b/src/app/LightspeedChatbot/README.md
new file mode 100644
index 0000000..c05aa98
--- /dev/null
+++ b/src/app/LightspeedChatbot/README.md
@@ -0,0 +1,139 @@
+# Lightspeed Chatbot
+
+A well-organized, modular chatbot implementation built with React and PatternFly.
+
+## ๐ Project Structure
+
+```
+LightspeedChatbot/
+โโโ README.md # This file
+โโโ index.ts # Main exports
+โโโ LightspeedChatbot.tsx # Main component
+โโโ types.ts # TypeScript type definitions
+โโโ constants.ts # Configuration and constants
+โโโ components/
+โ โโโ ToolExecutionCards.tsx # Tool execution display component
+โโโ hooks/
+โ โโโ useChatbot.ts # Custom hook for chatbot logic
+โโโ services/
+โ โโโ api.ts # API service functions
+โโโ utils/
+ โโโ helpers.ts # Utility functions
+```
+
+## ๐งฉ Components
+
+### `LightspeedChatbot.tsx`
+The main chatbot component that orchestrates all functionality:
+- Uses the `useChatbot` hook for state management
+- Renders the complete chatbot interface
+- Handles tool execution display
+- Supports multiple display modes (overlay, docked, fullscreen)
+
+### `ToolExecutionCards.tsx`
+A reusable component for displaying tool execution information:
+- Shows which tools are being used during message processing
+- Renders as compact cards with tool names
+- Automatically hides when no tools are active
+
+## ๐ฃ Hooks
+
+### `useChatbot.ts`
+A comprehensive custom hook that manages:
+- **State**: Messages, models, conversations, UI states
+- **Effects**: Model loading, auto-scrolling
+- **Handlers**: Send messages, select models, toggle UI elements
+- **API Integration**: Streaming query processing
+
+## ๐ง Services
+
+### `api.ts`
+Centralized API service functions:
+- `fetchModels()`: Retrieves available AI models
+- `sendQuery()`: Sends non-streaming queries
+- `sendStreamingQuery()`: Handles streaming responses with real-time updates
+
+## ๐ Types
+
+### `types.ts`
+Complete TypeScript definitions:
+- **API Types**: Model, QueryRequest, QueryResponse
+- **Streaming Types**: StreamEvent, StreamTokenData, StreamEndData
+- **Component Types**: Props and state interfaces
+
+## ๐ Utils
+
+### `helpers.ts`
+Utility functions:
+- `generateId()`: Creates unique message IDs
+- `findMatchingItems()`: Searches conversation history
+- `copyToClipboard()`: Handles text copying
+
+## ๐จ Constants
+
+### `constants.ts`
+Configuration values:
+- API endpoints and avatars
+- Initial state values
+- UI configuration (footnotes, prompts)
+
+## ๐ Usage
+
+```typescript
+import { LightspeedChatbot } from './LightspeedChatbot';
+
+function App() {
+ return (
+
+
+
+ );
+}
+```
+
+## ๐ง Configuration
+
+### API Configuration
+Update `constants.ts` to configure:
+- `API_BASE_URL`: Your API endpoint
+- `USER_AVATAR`, `BOT_AVATAR`: Avatar URLs
+- `DEFAULT_SYSTEM_PROMPT`: AI behavior instructions
+
+### Styling
+The component uses PatternFly components and can be styled using:
+- PatternFly CSS variables
+- Custom CSS classes
+- Inline styles for specific elements
+
+## ๐ Features
+
+- **Real-time Streaming**: Live response updates
+- **Tool Execution Tracking**: Visual feedback for AI tool usage
+- **Multiple Display Modes**: Overlay, docked, and fullscreen
+- **Conversation History**: Persistent chat sessions
+- **Model Selection**: Choose from available AI models
+- **Accessibility**: Full screen reader support
+- **Error Handling**: Graceful error recovery
+
+## ๐ฏ Benefits of This Organization
+
+1. **Separation of Concerns**: Each file has a single responsibility
+2. **Reusability**: Components and hooks can be used independently
+3. **Maintainability**: Easy to find and modify specific functionality
+4. **Testability**: Each module can be tested in isolation
+5. **Scalability**: Easy to add new features without cluttering
+6. **Type Safety**: Comprehensive TypeScript definitions
+7. **Documentation**: Clear structure and inline comments
+
+## ๐ Key Improvements Made
+
+- โ
**Modular Architecture**: Split large file into focused modules
+- โ
**Custom Hooks**: Extracted complex logic into reusable hooks
+- โ
**Type Safety**: Comprehensive TypeScript definitions
+- โ
**Service Layer**: Centralized API management
+- โ
**Utility Functions**: Shared helper functions
+- โ
**Constants Management**: Centralized configuration
+- โ
**Component Composition**: Smaller, focused components
+- โ
**Clear Documentation**: Comprehensive README and comments
+
+This organization makes the codebase much easier to understand, maintain, and extend!
\ No newline at end of file
diff --git a/src/app/LightspeedChatbot/components/ToolExecutionCards.tsx b/src/app/LightspeedChatbot/components/ToolExecutionCards.tsx
new file mode 100644
index 0000000..97ae463
--- /dev/null
+++ b/src/app/LightspeedChatbot/components/ToolExecutionCards.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Card, CardBody, CardTitle } from '@patternfly/react-core';
+import { ToolExecutionCardsProps } from '../types';
+
+/**
+ * Component for displaying tool execution cards
+ * Shows which tools are being used during message processing
+ */
+export const ToolExecutionCards: React.FC
= ({ tools }) => {
+ if (tools.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {tools.map((tool, index) => (
+
+ Tool Execution
+
+ Using tool: {tool}
+
+
+ ))}
+
+ );
+};
\ No newline at end of file
diff --git a/src/app/LightspeedChatbot/constants.ts b/src/app/LightspeedChatbot/constants.ts
new file mode 100644
index 0000000..c3fdff5
--- /dev/null
+++ b/src/app/LightspeedChatbot/constants.ts
@@ -0,0 +1,36 @@
+import { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
+import { WelcomePrompt } from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt';
+
+// API Configuration
+export const API_BASE_URL = 'http://localhost:8080';
+
+// Avatar URLs
+export const USER_AVATAR =
+ 'https://raw.githubusercontent.com/patternfly/chatbot/912cd12c09af5d8309ec2ac380076a4421368731/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/user_avatar.svg';
+export const BOT_AVATAR =
+ 'https://raw.githubusercontent.com/patternfly/chatbot/912cd12c09af5d8309ec2ac380076a4421368731/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/patternfly_avatar.jpg';
+
+// Initial states
+export const INITIAL_MESSAGES: MessageProps[] = [];
+export const INITIAL_WELCOME_PROMPTS: WelcomePrompt[] = [];
+export const INITIAL_CONVERSATIONS = {};
+
+// Default system prompt
+export const DEFAULT_SYSTEM_PROMPT = 'You are a helpful assistant.';
+
+// Footnote configuration
+export const FOOTNOTE_PROPS = {
+ label: 'Lightspeed uses AI. Check for mistakes.',
+ popover: {
+ title: 'Verify accuracy',
+ description: `While Lightspeed strives for accuracy, there's always a possibility of errors. It's a good practice to verify critical information from reliable sources, especially if it's crucial for decision-making or actions.`,
+ cta: {
+ label: 'Got it',
+ onClick: () => {},
+ },
+ link: {
+ label: 'Learn more',
+ url: 'https://www.redhat.com/',
+ },
+ },
+};
diff --git a/src/app/LightspeedChatbot/hooks/useChatbot.ts b/src/app/LightspeedChatbot/hooks/useChatbot.ts
new file mode 100644
index 0000000..56f1af4
--- /dev/null
+++ b/src/app/LightspeedChatbot/hooks/useChatbot.ts
@@ -0,0 +1,253 @@
+import React from 'react';
+import { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
+import { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message';
+import { Conversation } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
+
+import { Model, QueryRequest, StreamTokenData, StreamEndData } from '../types';
+import { INITIAL_MESSAGES, INITIAL_CONVERSATIONS, USER_AVATAR, BOT_AVATAR, DEFAULT_SYSTEM_PROMPT } from '../constants';
+import { fetchModels, sendStreamingQuery } from '../services/api';
+import { generateId, findMatchingItems, copyToClipboard } from '../utils/helpers';
+
+export const useChatbot = () => {
+ // State management
+ const [chatbotVisible, setChatbotVisible] = React.useState(false);
+ const [displayMode, setDisplayMode] = React.useState(ChatbotDisplayMode.default);
+ const [messages, setMessages] = React.useState(INITIAL_MESSAGES);
+ const [selectedModel, setSelectedModel] = React.useState('');
+ const [selectedProvider, setSelectedProvider] = React.useState('');
+ const [availableModels, setAvailableModels] = React.useState([]);
+ const [isSendButtonDisabled, setIsSendButtonDisabled] = React.useState(false);
+ const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
+ const [conversations, setConversations] = React.useState(
+ INITIAL_CONVERSATIONS,
+ );
+ const [announcement, setAnnouncement] = React.useState();
+ const [currentConversationId, setCurrentConversationId] = React.useState('');
+ const [toolExecutions, setToolExecutions] = React.useState<{ [messageId: string]: string[] }>({});
+
+ const scrollToBottomRef = React.useRef(null);
+
+ // Load available models on component mount
+ React.useEffect(() => {
+ const loadModels = async () => {
+ const models = await fetchModels();
+ setAvailableModels(models);
+ // Set first LLM model as default
+ const defaultModel = models.find((model) => model.api_model_type === 'llm');
+ if (defaultModel) {
+ setSelectedModel(defaultModel.identifier);
+ setSelectedProvider(defaultModel.provider_id);
+ }
+ };
+ loadModels();
+ }, []);
+
+ // Auto-scroll to latest message
+ React.useEffect(() => {
+ if (messages.length > 0) {
+ scrollToBottomRef.current?.scrollIntoView({ behavior: 'instant' });
+ }
+ }, [messages]);
+
+ // Event handlers
+ const onSelectModel = (
+ _event: React.MouseEvent | undefined,
+ value: string | number | undefined,
+ ) => {
+ setSelectedModel(value as string);
+ };
+
+ const onSelectDisplayMode = (
+ _event: React.MouseEvent | undefined,
+ value: string | number | undefined,
+ ) => {
+ setDisplayMode(value as ChatbotDisplayMode);
+ };
+
+ const onToggleChatbot = () => {
+ setChatbotVisible(!chatbotVisible);
+ };
+
+ const onDrawerToggle = () => {
+ setIsDrawerOpen(!isDrawerOpen);
+ setConversations(INITIAL_CONVERSATIONS);
+ };
+
+ const onNewChat = () => {
+ setIsDrawerOpen(!isDrawerOpen);
+ setMessages([]);
+ setConversations(INITIAL_CONVERSATIONS);
+ setCurrentConversationId('');
+ };
+
+ const handleTextInputChange = (value: string) => {
+ if (value === '') {
+ setConversations(INITIAL_CONVERSATIONS);
+ return;
+ }
+ // Search conversations based on input
+ const newConversations = findMatchingItems(value);
+ setConversations(newConversations);
+ };
+
+ const handleSend = async (message: string | number) => {
+ setIsSendButtonDisabled(true);
+ const messageContent = String(message);
+
+ // Create new messages array with user message
+ const newMessages: MessageProps[] = [...messages];
+ newMessages.push({
+ id: generateId(),
+ role: 'user',
+ content: messageContent,
+ name: 'User',
+ avatar: USER_AVATAR,
+ isLoading: false,
+ });
+
+ // Add bot message placeholder
+ const botMessageId = generateId();
+ newMessages.push({
+ id: botMessageId,
+ role: 'bot',
+ content: '',
+ name: 'Lightspeed AI',
+ isLoading: true,
+ avatar: BOT_AVATAR,
+ });
+
+ setMessages(newMessages);
+ setAnnouncement(`Message from User: ${messageContent}. Message from Lightspeed AI is loading.`);
+
+ try {
+ const queryRequest: QueryRequest = {
+ query: messageContent,
+ conversation_id: currentConversationId || undefined,
+ model: selectedModel || undefined,
+ provider: selectedProvider || undefined,
+ system_prompt: DEFAULT_SYSTEM_PROMPT,
+ };
+
+ let streamingContent = '';
+ let finalConversationId = currentConversationId;
+ let currentToolExecutions: string[] = [];
+
+ await sendStreamingQuery(
+ queryRequest,
+ // onToken callback
+ (token: string, tokenData?: StreamTokenData) => {
+ if (tokenData && tokenData.role === 'tool_execution') {
+ currentToolExecutions.push(token);
+ setToolExecutions((prev) => ({
+ ...prev,
+ [botMessageId]: [...currentToolExecutions],
+ }));
+ } else {
+ streamingContent += token;
+ }
+
+ setMessages((prevMessages) => {
+ const updatedMessages = [...prevMessages];
+ const botMessageIndex = updatedMessages.findIndex((msg) => msg.id === botMessageId);
+ if (botMessageIndex !== -1) {
+ updatedMessages[botMessageIndex] = {
+ ...updatedMessages[botMessageIndex],
+ content: streamingContent,
+ isLoading: false,
+ };
+ }
+ return updatedMessages;
+ });
+ },
+ // onStart callback
+ (conversationId: string) => {
+ finalConversationId = conversationId;
+ setCurrentConversationId(conversationId);
+ },
+ // onEnd callback
+ (endData: StreamEndData) => {
+ setMessages((prevMessages) => {
+ const updatedMessages = [...prevMessages];
+ const botMessageIndex = updatedMessages.findIndex((msg) => msg.id === botMessageId);
+ if (botMessageIndex !== -1) {
+ updatedMessages[botMessageIndex] = {
+ ...updatedMessages[botMessageIndex],
+ content: streamingContent,
+ isLoading: false,
+ actions: {
+ copy: { onClick: () => copyToClipboard(streamingContent) },
+ share: { onClick: () => {} },
+ listen: { onClick: () => {} },
+ },
+ };
+ }
+ return updatedMessages;
+ });
+ setAnnouncement(`Message from Lightspeed AI: ${streamingContent}`);
+ },
+ );
+ } catch (error) {
+ console.error('Error sending streaming query:', error);
+ setMessages((prevMessages) => {
+ const updatedMessages = [...prevMessages];
+ const botMessageIndex = updatedMessages.findIndex((msg) => msg.id === botMessageId);
+ if (botMessageIndex !== -1) {
+ updatedMessages[botMessageIndex] = {
+ ...updatedMessages[botMessageIndex],
+ content: 'Sorry, I encountered an error processing your request. Please try again.',
+ isLoading: false,
+ actions: {
+ copy: { onClick: () => {} },
+ share: { onClick: () => {} },
+ listen: { onClick: () => {} },
+ },
+ };
+ }
+ return updatedMessages;
+ });
+ setAnnouncement(`Message from Lightspeed AI: Sorry, I encountered an error processing your request.`);
+ } finally {
+ setIsSendButtonDisabled(false);
+ }
+ };
+
+ return {
+ // State
+ chatbotVisible,
+ displayMode,
+ messages,
+ selectedModel,
+ selectedProvider,
+ availableModels,
+ isSendButtonDisabled,
+ isDrawerOpen,
+ conversations,
+ announcement,
+ currentConversationId,
+ toolExecutions,
+ scrollToBottomRef,
+
+ // Actions
+ onSelectModel,
+ onSelectDisplayMode,
+ onToggleChatbot,
+ onDrawerToggle,
+ onNewChat,
+ handleTextInputChange,
+ handleSend,
+
+ // Setters (needed for direct state updates)
+ setChatbotVisible,
+ setDisplayMode,
+ setMessages,
+ setSelectedModel,
+ setSelectedProvider,
+ setAvailableModels,
+ setIsSendButtonDisabled,
+ setIsDrawerOpen,
+ setConversations,
+ setAnnouncement,
+ setCurrentConversationId,
+ setToolExecutions,
+ };
+};
diff --git a/src/app/LightspeedChatbot/index.ts b/src/app/LightspeedChatbot/index.ts
new file mode 100644
index 0000000..62b7a04
--- /dev/null
+++ b/src/app/LightspeedChatbot/index.ts
@@ -0,0 +1,39 @@
+// Main component export
+export { LightspeedChatbot } from './LightspeedChatbot';
+
+// Type exports
+export type {
+ Model,
+ QueryRequest,
+ QueryResponse,
+ StreamEvent,
+ StreamStartData,
+ StreamTokenData,
+ StreamEndData,
+ ToolExecutionCardsProps,
+ ChatbotState,
+} from './types';
+
+// API service exports
+export { fetchModels, sendQuery, sendStreamingQuery } from './services/api';
+
+// Utility exports
+export { generateId, findMatchingItems, copyToClipboard } from './utils/helpers';
+
+// Component exports
+export { ToolExecutionCards } from './components/ToolExecutionCards';
+
+// Hook exports
+export { useChatbot } from './hooks/useChatbot';
+
+// Constants exports
+export {
+ API_BASE_URL,
+ USER_AVATAR,
+ BOT_AVATAR,
+ INITIAL_MESSAGES,
+ INITIAL_WELCOME_PROMPTS,
+ INITIAL_CONVERSATIONS,
+ DEFAULT_SYSTEM_PROMPT,
+ FOOTNOTE_PROPS,
+} from './constants';
diff --git a/src/app/LightspeedChatbot/services/api.ts b/src/app/LightspeedChatbot/services/api.ts
new file mode 100644
index 0000000..2bdb70c
--- /dev/null
+++ b/src/app/LightspeedChatbot/services/api.ts
@@ -0,0 +1,149 @@
+import { API_BASE_URL } from '../constants';
+import {
+ Model,
+ QueryRequest,
+ QueryResponse,
+ StreamEvent,
+ StreamStartData,
+ StreamTokenData,
+ StreamEndData,
+} from '../types';
+
+/**
+ * Fetches available models from the API
+ * @returns Promise Array of available models
+ */
+export const fetchModels = async (): Promise => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/v1/models`, {
+ method: 'GET',
+ });
+
+ console.log('Models response status:', response.status);
+
+ if (!response.ok) {
+ console.error('Models API error:', response.status, response.statusText);
+ throw new Error(`Failed to fetch models: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log('Models response data:', data);
+
+ const models = data.models || [];
+ console.log('Extracted models:', models);
+
+ return models;
+ } catch (error) {
+ console.error('Error fetching models:', error);
+ // Return fallback models for testing
+ return [
+ {
+ identifier: 'test-model',
+ metadata: {},
+ api_model_type: 'llm',
+ provider_id: 'test',
+ provider_resource_id: 'test-model',
+ type: 'model',
+ model_type: 'llm',
+ },
+ ];
+ }
+};
+
+/**
+ * Sends a query to the API (non-streaming)
+ * @param request QueryRequest object
+ * @returns Promise
+ */
+export const sendQuery = async (request: QueryRequest): Promise => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/v1/query`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(request),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to send query');
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error sending query:', error);
+ throw error;
+ }
+};
+
+/**
+ * Sends a streaming query to the API
+ * @param request QueryRequest object
+ * @param onToken Callback for each token received
+ * @param onStart Callback when streaming starts
+ * @param onEnd Callback when streaming ends
+ */
+export const sendStreamingQuery = async (
+ request: QueryRequest,
+ onToken: (token: string, tokenData?: StreamTokenData) => void,
+ onStart: (conversationId: string) => void,
+ onEnd: (endData: StreamEndData) => void,
+): Promise => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/v1/streaming_query`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(request),
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to send streaming query');
+ }
+
+ const reader = response.body?.getReader();
+ const decoder = new TextDecoder();
+
+ if (!reader) {
+ throw new Error('No reader available');
+ }
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ const chunk = decoder.decode(value, { stream: true });
+ const lines = chunk.split('\n');
+
+ for (const line of lines) {
+ if (line.startsWith('data: ')) {
+ try {
+ const eventData: StreamEvent = JSON.parse(line.slice(6));
+
+ switch (eventData.event) {
+ case 'start':
+ const startData = eventData.data as StreamStartData;
+ onStart(startData.conversation_id);
+ break;
+ case 'token':
+ const tokenData = eventData.data as StreamTokenData;
+ onToken(tokenData.token, tokenData);
+ break;
+ case 'end':
+ const endData = eventData.data as StreamEndData;
+ onEnd(endData);
+ break;
+ }
+ } catch (parseError) {
+ console.error('Error parsing streaming data:', parseError);
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error('Error sending streaming query:', error);
+ throw error;
+ }
+};
diff --git a/src/app/LightspeedChatbot/types.ts b/src/app/LightspeedChatbot/types.ts
new file mode 100644
index 0000000..1d384b8
--- /dev/null
+++ b/src/app/LightspeedChatbot/types.ts
@@ -0,0 +1,71 @@
+// Types for the Lightspeed Chatbot
+export interface Model {
+ identifier: string;
+ metadata: Record;
+ api_model_type: string;
+ provider_id: string;
+ provider_resource_id: string;
+ type: string;
+ model_type: string;
+}
+
+export interface QueryRequest {
+ query: string;
+ conversation_id?: string;
+ provider?: string;
+ model?: string;
+ system_prompt?: string;
+ attachments?: Array<{
+ attachment_type: string;
+ content_type: string;
+ content: string;
+ }>;
+}
+
+export interface QueryResponse {
+ conversation_id?: string;
+ response: string;
+}
+
+// Streaming types
+export interface StreamEvent {
+ event: 'start' | 'token' | 'end';
+ data: any;
+}
+
+export interface StreamStartData {
+ conversation_id: string;
+}
+
+export interface StreamTokenData {
+ id: number;
+ role: string;
+ token: string;
+}
+
+export interface StreamEndData {
+ referenced_documents: any[];
+ truncated: any;
+ input_tokens: number;
+ output_tokens: number;
+}
+
+// Component types
+export interface ToolExecutionCardsProps {
+ tools: string[];
+}
+
+export interface ChatbotState {
+ chatbotVisible: boolean;
+ displayMode: any;
+ messages: any[];
+ selectedModel: string;
+ selectedProvider: string;
+ availableModels: Model[];
+ isSendButtonDisabled: boolean;
+ isDrawerOpen: boolean;
+ conversations: any;
+ announcement?: string;
+ currentConversationId: string;
+ toolExecutions: { [messageId: string]: string[] };
+}
diff --git a/src/app/LightspeedChatbot/utils/helpers.ts b/src/app/LightspeedChatbot/utils/helpers.ts
new file mode 100644
index 0000000..2da531e
--- /dev/null
+++ b/src/app/LightspeedChatbot/utils/helpers.ts
@@ -0,0 +1,34 @@
+import { Conversation } from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav';
+
+/**
+ * Generates a unique ID for messages
+ * Note: This is a simple implementation for demo purposes
+ * In production, consider using a more robust ID generation method
+ */
+export const generateId = (): string => {
+ const id = Date.now() + Math.random();
+ return id.toString();
+};
+
+/**
+ * Finds matching conversation items based on search value
+ * @param targetValue The search string
+ * @returns Matching conversations object
+ */
+export const findMatchingItems = (targetValue: string): { [key: string]: Conversation[] } => {
+ // Since we start with empty conversations, return empty object
+ // In a real implementation, you would filter conversations based on targetValue
+ return {};
+};
+
+/**
+ * Copies text to clipboard
+ * @param text Text to copy
+ */
+export const copyToClipboard = async (text: string): Promise => {
+ try {
+ await navigator.clipboard.writeText(text);
+ } catch (error) {
+ console.error('Failed to copy text to clipboard:', error);
+ }
+};
diff --git a/src/app/NotFound/NotFound.tsx b/src/app/NotFound/NotFound.tsx
deleted file mode 100644
index d6b3fce..0000000
--- a/src/app/NotFound/NotFound.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import * as React from 'react';
-import { ExclamationTriangleIcon } from '@patternfly/react-icons';
-import {
- Button,
- EmptyState,
- EmptyStateBody,
- EmptyStateFooter,
- PageSection,
-} from '@patternfly/react-core';
-import { useNavigate } from 'react-router-dom';
-
-const NotFound: React.FunctionComponent = () => {
- function GoHomeBtn() {
- const navigate = useNavigate();
- function handleClick() {
- navigate('/');
- }
- return (
-
- );
- }
-
- return (
-
-
-
- We didn't find a page that matches the address you navigated to.
-
-
-
-
- )
-};
-
-export { NotFound };
diff --git a/src/app/index.tsx b/src/app/index.tsx
index 4892ea8..683f54f 100644
--- a/src/app/index.tsx
+++ b/src/app/index.tsx
@@ -1,8 +1,6 @@
import * as React from 'react';
import '@patternfly/react-core/dist/styles/base.css';
import { BrowserRouter as Router } from 'react-router-dom';
-import { AppLayout } from '@app/AppLayout/AppLayout';
-import { AppRoutes } from '@app/routes';
import '@patternfly/chatbot/dist/css/main.css';
import { LightspeedChatbot } from './LightspeedChatbot/LightspeedChatbot';