diff --git a/src/api/index.ts b/src/api/index.ts deleted file mode 100644 index ee71972..0000000 --- a/src/api/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const fetchChatHistories = async (token: string) => { - const response = await fetch(`${import.meta.env.VITE_MCP_BACKEND_API_ENDPOINT}/chats`, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - if (!response.ok) { - throw new Error('Failed to fetch chat histories'); - } - return await response.json(); -}; - -export const fetchLLMProviders = async () => { - const response = await fetch(`${import.meta.env.VITE_MCP_BACKEND_API_ENDPOINT}/llm-providers`); - if (!response.ok) { - throw new Error('Failed to fetch LLM providers'); - } - return await response.json(); -}; - -export const fetchTools = async () => { - const response = await fetch('http://localhost:8081/api/tools'); - if (!response.ok) { - throw new Error('Failed to fetch tools'); - } - return await response.json(); -}; \ No newline at end of file diff --git a/src/components/ChatContainer/ChatContainer.test.tsx b/src/components/ChatContainer/ChatContainer.test.tsx index 1d15fe7..d2067d5 100644 --- a/src/components/ChatContainer/ChatContainer.test.tsx +++ b/src/components/ChatContainer/ChatContainer.test.tsx @@ -26,13 +26,6 @@ jest.mock('../../services/ToolService', () => ({ })) })); -// Mock the api module -jest.mock('../../api', () => ({ - fetchChatHistories: jest.fn(), - fetchLLMProviders: jest.fn(), - fetchTools: jest.fn() -})); - // Mock the auth0 hook jest.mock('@auth0/auth0-react'); @@ -95,12 +88,6 @@ beforeAll(() => { Element.prototype.scrollIntoView = jest.fn(); }); -afterAll(() => { - // Clean up - // @ts-ignore - delete window.import; -}); - describe('ChatContainer', () => { const mockModelSettings = { temperature: 0.7, diff --git a/src/components/ChatContainer/ChatContainer.tsx b/src/components/ChatContainer/ChatContainer.tsx index 0991695..816c0e3 100644 --- a/src/components/ChatContainer/ChatContainer.tsx +++ b/src/components/ChatContainer/ChatContainer.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {useAuth0} from '@auth0/auth0-react'; import {ChatContainerProps, ChatPayload, MessageHandlerConfig} from "../../types/chat.ts"; import {ApiChatMessage, ChatService, ClientChatMessage} from "../../services/ChatService.ts"; @@ -24,6 +24,16 @@ export const ChatContainer: React.FC = ({ const [useStreaming, setUseStreaming] = useState(true); const [availableTools, setAvailableTools] = useState([]); + const handleError = useCallback((error: unknown) => { + addNotification( + 'error', + error instanceof Error || (typeof error === 'object' && error && 'message' in error) + ? (error as { message: string }).message + : 'Failed to send message' + ); + }, [addNotification]); + + const handleStreamingChange = (value: boolean) => { setUseStreaming(value); }; @@ -50,16 +60,7 @@ export const ChatContainer: React.FC = ({ if (response.error) { console.error('Error loading tools:', response.error); - // Creating a local function that uses addNotification - const notifyError = () => { - addNotification( - 'error', - typeof response.error === 'object' && true && 'message' in response.error - ? response.error - : 'Failed to send message' - ); - }; - notifyError(); + handleError(response.error); return; } @@ -68,14 +69,8 @@ export const ChatContainer: React.FC = ({ } } catch (error) { console.error('Error fetching tools:', error); - // Creating another local function - const notifyError = () => { - addNotification( - 'error', - error instanceof Error ? error.message : 'Failed to send message' - ); - }; - notifyError(); + handleError(error); + return; } }; diff --git a/src/components/ChatInputButton/ToolsModal.test.tsx b/src/components/ChatInputButton/ToolsModal.test.tsx index c99b129..b9ae5ff 100644 --- a/src/components/ChatInputButton/ToolsModal.test.tsx +++ b/src/components/ChatInputButton/ToolsModal.test.tsx @@ -1,53 +1,72 @@ -import { render, screen, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import {fetchTools} from "../../api"; -import {act} from "react"; -import {ToolsModal} from "./ToolsModal.tsx"; -import {Tool} from "../../types/tools.ts"; - -// Mock the entire api module -jest.mock('../../api', () => ({ - fetchTools: jest.fn() -})); +import { render, screen, act, fireEvent } from '@testing-library/react'; +import { ToolsModal } from './ToolsModal'; +import { ToolService } from '../../services/ToolService'; +import { Tool } from '../../types/tools'; + +// Create mock data +const mockTools = [ + { name: 'Tool1', description: 'Description 1' }, + { name: 'Tool2', description: 'Description 2' }, + { name: 'Tool3', description: 'Description 3' }, +]; -// Mock the XMarkIcon component -jest.mock('@heroicons/react/24/outline', () => ({ - XMarkIcon: () =>
X
+// Mock the ToolItem component +jest.mock('../ToolItem/ToolItem', () => ({ + ToolItem: ({ tool, onToggle }: { + tool: Tool; + isSelected: boolean; + onToggle: (name: string) => void; + }) => ( +
onToggle(tool.name)} + > + {tool.name} +
+ ) })); // Mock SearchBar component -jest.mock('../SearchBar.tsx', () => ({ - SearchBar: ({ value, onChange }: { value: string; onChange: (value: string) => void }) => ( +jest.mock('../SearchBar', () => ({ + SearchBar: ({ value, onChange }: { + value: string; + onChange: (value: string) => void; + }) => ( onChange(e.target.value)} - placeholder="Search tools" /> ) })); -// Mock ToolItem component +// Mock the ToolService +jest.mock('../../services/ToolService', () => ({ + ToolService: jest.fn().mockImplementation(() => ({ + getTools: jest.fn().mockResolvedValue({ + data: mockTools + }) + })) +})); + +// Mock the ToolItem component jest.mock('../ToolItem/ToolItem', () => ({ ToolItem: ({ tool, isSelected, onToggle }: { tool: Tool; isSelected: boolean; - onToggle: (name: string) => void + onToggle: (name: string) => void; }) => (
onToggle(tool.name)} > - {tool.name} {isSelected ? '(Selected)' : ''} + {tool.name} + {isSelected && ' (Selected)'}
) })); -const mockTools = [ - { name: 'Tool1', description: 'Description 1' }, - { name: 'Tool2', description: 'Description 2' }, - { name: 'Tool3', description: 'Description 3' }, -]; describe('ToolsModal', () => { const mockOnClose = jest.fn(); @@ -55,9 +74,9 @@ describe('ToolsModal', () => { beforeEach(() => { jest.clearAllMocks(); - (fetchTools as jest.Mock).mockResolvedValue(mockTools); }); + it('should render and fetch tools when opened', async () => { await act(async () => { render( @@ -71,7 +90,7 @@ describe('ToolsModal', () => { }); expect(screen.getByText('Available Tools')).toBeInTheDocument(); - expect(fetchTools).toHaveBeenCalledTimes(1); + expect(ToolService).toHaveBeenCalledTimes(1); expect(screen.getByTestId('tool-item-Tool1')).toBeInTheDocument(); expect(screen.getByTestId('tool-item-Tool2')).toBeInTheDocument(); diff --git a/src/components/ChatInputButton/ToolsModal.tsx b/src/components/ChatInputButton/ToolsModal.tsx index d745ae5..c8e89e3 100644 --- a/src/components/ChatInputButton/ToolsModal.tsx +++ b/src/components/ChatInputButton/ToolsModal.tsx @@ -2,8 +2,8 @@ import React, {useEffect, useMemo, useState} from 'react'; import {XMarkIcon} from "@heroicons/react/24/outline"; import {Tool, ToolsModalProps} from "../../types/tools.ts"; import {SearchBar} from "../SearchBar.tsx"; -import {fetchTools} from "../../api"; import {ToolItem} from "../ToolItem/ToolItem.tsx"; +import {ToolService} from "../../services/ToolService.ts"; export const ToolsModal: React.FC = ({ isOpen, @@ -18,8 +18,9 @@ export const ToolsModal: React.FC = ({ useEffect(() => { const loadTools = async () => { try { - const data = await fetchTools(); - setTools(data); + const toolService = new ToolService(); + const response = await toolService.getTools(); + setTools(response.data || []); } catch (error) { console.error('Error fetching tools:', error); } @@ -30,7 +31,6 @@ export const ToolsModal: React.FC = ({ } }, [isOpen]); - const handleToolToggle = (toolName: string) => { setSelectedTools(prev => prev.includes(toolName) diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 2424328..ad4570d 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,12 +1,12 @@ // components/Sidebar.tsx import React, {useCallback, useEffect, useState} from 'react'; import {ChatHistory} from "../types"; -import {fetchChatHistories} from "../api"; import {SidebarHeader} from "./sidebar/SidebarHeader"; import {NewChatSection} from "./sidebar/NewChatSection"; import {ChatHistoryList} from "./sidebar/ChatHistoryList"; import {SidebarFooter} from "./sidebar/SidebarFooter"; import {useAuth0} from '@auth0/auth0-react'; +import {ChatService} from "../services/ChatService.ts"; interface SidebarProps { @@ -33,8 +33,9 @@ export const Sidebar: React.FC = ({ setIsLoading(true); try { const token = await getAccessTokenSilently(); - const response = await fetchChatHistories(token); - setChatHistories(response.chats || []); + const chatService = new ChatService(token); + const response = await chatService.getChatHistories(); + setChatHistories(response.data.chats || []); setError(null); } catch (err) { setError('Failed to load chat histories'); diff --git a/src/services/ChatService.ts b/src/services/ChatService.ts index eabbb05..88039b1 100644 --- a/src/services/ChatService.ts +++ b/src/services/ChatService.ts @@ -25,6 +25,9 @@ interface StreamChunk { done?: boolean; } +interface ChatHistoriesResponse { + chats: ChatHistory[]; +} // Updated ChatService export class ChatService extends APIClient { @@ -32,8 +35,8 @@ export class ChatService extends APIClient { super(import.meta.env.VITE_MCP_BACKEND_API_ENDPOINT, token); } - async getChatHistories(): Promise> { - return this.fetchWithError('/chats'); + async getChatHistories(): Promise> { + return this.fetchWithError('/api/v1/chats'); } async sendMessage(payload: ChatPayload): Promise> { @@ -44,7 +47,7 @@ export class ChatService extends APIClient { } async loadChatHistory(chatId: string): Promise> { - return this.fetchWithError<{ messages: ApiChatMessage[] }>(`/chats/${chatId}`); + return this.fetchWithError<{ messages: ApiChatMessage[] }>(`/api/v1/chats/${chatId}`); } async sendStreamMessage( @@ -52,7 +55,7 @@ export class ChatService extends APIClient { onChunk: (chunk: StreamChunk) => void ): Promise { try { - const response = await this.fetchStream('/ask-stream', { + const response = await this.fetchStream('/api/v1/chats/stream', { method: 'POST', body: JSON.stringify(payload), }); diff --git a/src/services/LLMService.ts b/src/services/LLMService.ts index 29c25ef..d940830 100644 --- a/src/services/LLMService.ts +++ b/src/services/LLMService.ts @@ -7,6 +7,6 @@ export class LLMService extends APIClient { } async getLLMProviders(): Promise> { - return this.fetchWithError('/llm-providers'); + return this.fetchWithError('/api/v1/llm-providers'); } } \ No newline at end of file diff --git a/src/services/ToolService.ts b/src/services/ToolService.ts index 3678857..0f2adee 100644 --- a/src/services/ToolService.ts +++ b/src/services/ToolService.ts @@ -3,10 +3,10 @@ import {APIClient, APIResponse} from "./APIClient.ts"; export class ToolService extends APIClient { constructor(token: string = '') { - super('http://localhost:8081/api', token); + super(import.meta.env.VITE_MCP_BACKEND_API_ENDPOINT, token); } async getTools(): Promise> { - return this.fetchWithError('/tools'); + return this.fetchWithError('/api/v1/tools'); } -} \ No newline at end of file +}