diff --git a/frontend/src/__tests__/ai/ChatWindow.test.tsx b/frontend/src/__tests__/ai/ChatWindow.test.tsx
deleted file mode 100644
index 37b274b..0000000
--- a/frontend/src/__tests__/ai/ChatWindow.test.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import React from 'react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { ChatWindow } from '@/components/ai/ChatWindow';
-
-const mockMessages = [
- { id: 'm1', session_id: 's1', role: 'user', content: 'Hello AI', tokens: 10, model_used: '' },
- { id: 'm2', session_id: 's1', role: 'assistant', content: 'Hello User', tokens: 10, model_used: 'gpt-4' },
-];
-
-const fetchMessagesMock = vi.fn();
-const fetchAttachmentsMock = vi.fn();
-const streamChatMock = vi.fn();
-const uploadAttachmentMock = vi.fn();
-
-vi.mock('@/api/ai', () => ({
- fetchMessages: (...args: any[]) => fetchMessagesMock(...args),
- fetchAttachments: (...args: any[]) => fetchAttachmentsMock(...args),
- streamChat: (...args: any[]) => streamChatMock(...args),
- uploadAttachment: (...args: any[]) => uploadAttachmentMock(...args),
- getAttachmentDownloadUrl: (id: string) => `/api/v1/ai/attachments/${id}/download`,
-}));
-
-vi.mock('react-markdown', () => ({
- default: ({ children }: { children: string }) =>
{children}
,
-}));
-
-vi.mock('remark-gfm', () => ({
- default: () => ({}),
-}));
-
-beforeEach(() => {
- vi.clearAllMocks();
- fetchMessagesMock.mockResolvedValue(mockMessages);
- fetchAttachmentsMock.mockResolvedValue([]);
- streamChatMock.mockReturnValue({
- async *[Symbol.asyncIterator]() {
- yield { type: 'token', content: 'Hi' };
- yield { type: 'done' };
- },
- });
-});
-
-describe('ChatWindow', () => {
- it('renders chat window with messages', async () => {
- render();
- await waitFor(() => {
- expect(screen.getByText('Hello AI')).toBeInTheDocument();
- expect(screen.getByText('Hello User')).toBeInTheDocument();
- });
- });
-
- it('renders textarea input for message', async () => {
- render();
- await waitFor(() => {
- expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
- });
- });
-
- it('renders send button', async () => {
- render();
- await waitFor(() => {
- expect(screen.getByText('Senden')).toBeInTheDocument();
- });
- });
-
- it('disables send button when input is empty', async () => {
- render();
- await waitFor(() => {
- const sendBtn = screen.getByText('Senden');
- expect(sendBtn).toBeDisabled();
- });
- });
-
- it('enables send button when input has text', async () => {
- render();
- await waitFor(() => {
- expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
- });
- const input = screen.getByPlaceholderText('Nachricht eingeben...');
- fireEvent.change(input, { target: { value: 'Test message' } });
- expect(screen.getByText('Senden')).not.toBeDisabled();
- });
-
- it('shows empty state when no messages', async () => {
- fetchMessagesMock.mockResolvedValueOnce([]);
- render();
- await waitFor(() => {
- expect(screen.getByText('Starte eine Konversation...')).toBeInTheDocument();
- });
- });
-
- it('displays error when fetchMessages fails', async () => {
- fetchMessagesMock.mockRejectedValueOnce(new Error('Load failed'));
- render();
- await waitFor(() => {
- expect(screen.getByText('Load failed')).toBeInTheDocument();
- });
- });
-
- it('sends message on button click', async () => {
- render();
- await waitFor(() => {
- expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
- });
- const input = screen.getByPlaceholderText('Nachricht eingeben...');
- fireEvent.change(input, { target: { value: 'Test message' } });
- fireEvent.click(screen.getByText('Senden'));
- await waitFor(() => {
- expect(streamChatMock).toHaveBeenCalledWith('s1', 'Test message', undefined);
- });
- });
-});
diff --git a/frontend/src/__tests__/ai/SessionList.test.tsx b/frontend/src/__tests__/ai/SessionList.test.tsx
deleted file mode 100644
index 523cec1..0000000
--- a/frontend/src/__tests__/ai/SessionList.test.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { SessionList } from '@/components/ai/SessionList';
-
-const queryClient = new QueryClient({
- defaultOptions: { queries: { retry: false } },
-});
-
-function renderWithProvider(ui: React.ReactElement) {
- return render(
- {ui}
- );
-}
-
-const mockSessions = [
- { id: 's1', title: 'First Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
- { id: 's2', title: 'Second Chat', folder_id: 'f1', agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
-];
-
-const mockFolders = [
- { id: 'f1', name: 'My Folder', parent_id: null, created_at: '2024-01-01T00:00:00Z' },
-];
-
-const fetchSessionsMock = vi.fn();
-const fetchFoldersMock = vi.fn();
-const createSessionMock = vi.fn();
-const deleteSessionMock = vi.fn();
-const updateSessionMock = vi.fn();
-const createFolderMock = vi.fn();
-const deleteFolderMock = vi.fn();
-const updateFolderMock = vi.fn();
-
-vi.mock('@/api/ai', () => ({
- fetchSessions: (...args: any[]) => fetchSessionsMock(...args),
- fetchFolders: (...args: any[]) => fetchFoldersMock(...args),
- createSession: (...args: any[]) => createSessionMock(...args),
- deleteSession: (...args: any[]) => deleteSessionMock(...args),
- updateSession: (...args: any[]) => updateSessionMock(...args),
- createFolder: (...args: any[]) => createFolderMock(...args),
- deleteFolder: (...args: any[]) => deleteFolderMock(...args),
- updateFolder: (...args: any[]) => updateFolderMock(...args),
-}));
-
-beforeEach(() => {
- vi.clearAllMocks();
- fetchSessionsMock.mockResolvedValue(mockSessions);
- fetchFoldersMock.mockResolvedValue(mockFolders);
- createSessionMock.mockResolvedValue({ id: 's3', title: 'New Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '', updated_at: '' });
- deleteSessionMock.mockResolvedValue({});
- updateSessionMock.mockResolvedValue({});
- createFolderMock.mockResolvedValue({ id: 'f2', name: 'New Folder', parent_id: null, created_at: '' });
- deleteFolderMock.mockResolvedValue({});
- updateFolderMock.mockResolvedValue({});
-});
-
-describe('SessionList', () => {
- it('renders session list with sessions', async () => {
- renderWithProvider();
- await waitFor(() => {
- expect(screen.getByText('First Chat')).toBeInTheDocument();
- });
- });
-
- it('renders folder names', async () => {
- renderWithProvider();
- await waitFor(() => {
- expect(screen.getByText('My Folder')).toBeInTheDocument();
- });
- });
-
- it('renders sessions inside folders', async () => {
- renderWithProvider();
- await waitFor(() => {
- expect(screen.getByText('Second Chat')).toBeInTheDocument();
- });
- });
-
- it('calls onSelectSession when session clicked', async () => {
- const onSelect = vi.fn();
- renderWithProvider();
- await waitFor(() => {
- expect(screen.getByText('First Chat')).toBeInTheDocument();
- });
- fireEvent.click(screen.getByText('First Chat'));
- expect(onSelect).toHaveBeenCalledWith('s1');
- });
-
- it('shows loading state initially', () => {
- fetchSessionsMock.mockReturnValue(new Promise(() => {}));
- fetchFoldersMock.mockReturnValue(new Promise(() => {}));
- renderWithProvider();
- expect(screen.getByText('Laden...')).toBeInTheDocument();
- });
-
- it('displays error when fetch fails', async () => {
- fetchSessionsMock.mockRejectedValueOnce(new Error('Failed to load'));
- renderWithProvider();
- await waitFor(() => {
- expect(screen.getByText('Failed to load')).toBeInTheDocument();
- });
- });
-});
diff --git a/frontend/src/api/ai.ts b/frontend/src/api/ai.ts
index b62571a..8e8f03e 100644
--- a/frontend/src/api/ai.ts
+++ b/frontend/src/api/ai.ts
@@ -61,47 +61,6 @@ export interface AIAgent {
updated_at?: string;
}
-export interface ChatFolder {
- id: string;
- name: string;
- parent_id: string | null;
- user_id: string;
- created_at?: string;
-}
-
-export interface ChatSession {
- id: string;
- user_id: string;
- agent_id: string | null;
- title: string;
- is_pinned: boolean;
- is_sidebar: boolean;
- folder_id: string | null;
- created_at?: string;
- updated_at?: string;
-}
-
-export interface ChatMessage {
- id: string;
- session_id: string;
- role: string;
- content: string;
- tool_calls?: Record[];
- tool_results?: Record[];
- tokens: number;
- model_used: string;
- created_at?: string;
-}
-
-export interface ChatAttachment {
- id: string;
- message_id: string | null;
- session_id: string;
- filename: string;
- mime_type: string;
- size_bytes: number;
-}
-
export interface AITool {
name: string;
description: string;
@@ -147,53 +106,11 @@ export const fetchTools = async () => {
return Array.isArray(res) ? res : res.items ?? [];
};
-// ─── Folders ───
-
-export const fetchFolders = () => apiGet('/ai/folders');
-export const createFolder = (data: { name: string; parent_id?: string }) => apiPost('/ai/folders', data);
-export const updateFolder = (id: string, data: Partial) => apiPut(`/ai/folders/${id}`, data);
-export const deleteFolder = (id: string) => apiDelete(`/ai/folders/${id}`);
-
-// ─── Sessions ───
-
-export const fetchSessions = (isSidebar?: boolean) =>
- apiGet('/ai/sessions', { params: isSidebar !== undefined ? { is_sidebar: isSidebar } : {} });
-export const createSession = (data: { title?: string; agent_id?: string; is_sidebar?: boolean; folder_id?: string }) =>
- apiPost('/ai/sessions', data);
-export const updateSession = (id: string, data: Partial) =>
- apiPut(`/ai/sessions/${id}`, data);
-export const deleteSession = (id: string) => apiDelete(`/ai/sessions/${id}`);
-
// ─── Messages ───
export const fetchMessages = (conversationId: string) =>
apiGet<{ role: string; content: string }[]>(`/ai/conversations/${conversationId}/messages`);
-// ─── Attachments ───
-
-export const fetchAttachments = (sessionId: string) =>
- apiGet(`/ai/sessions/${sessionId}/attachments`);
-
-export async function uploadAttachment(sessionId: string, file: File): Promise {
- const formData = new FormData();
- formData.append('file', file);
- const csrfToken = sessionStorage.getItem('leocrm_csrf_token');
- const response = await fetch(`/api/v1/ai/sessions/${sessionId}/attachments`, {
- method: 'POST',
- headers: {
- ...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}),
- },
- credentials: 'include',
- body: formData,
- });
- if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
- return response.json();
-}
-
-export function getAttachmentDownloadUrl(attachmentId: string): string {
- return `/api/v1/ai/attachments/${attachmentId}/download`;
-}
-
// ─── Streaming Chat ───
export interface StreamEvent {
diff --git a/frontend/src/components/layout/AISidebar.tsx b/frontend/src/components/layout/AISidebar.tsx
index a3ef5c8..821e7a6 100644
--- a/frontend/src/components/layout/AISidebar.tsx
+++ b/frontend/src/components/layout/AISidebar.tsx
@@ -1,9 +1,9 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
+import { Link } from 'react-router-dom';
import { ResizablePanel } from '@/components/ui/ResizablePanel';
import { SuggestionList } from '@/components/ai/SuggestionSidebar';
import { ImprovementPanel } from '@/components/ai/ImprovementPanel';
-import { createSession, fetchSessions } from '@/api/ai';
import { useUIStore } from '@/store/uiStore';
import { getContributedTabs } from './sidebarTabs';
import { useTranslation } from 'react-i18next';
@@ -56,26 +56,6 @@ function ChatPanel() {
export function AISidebar() {
const { t } = useTranslation();
const { aiSidebarCollapsed, toggleAISidebar, aiSidebarTab, setAISidebarTab, notifications, removeNotification } = useUIStore();
- const [sessionId, setSessionId] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- (async () => {
- try {
- const sessions = await fetchSessions(true);
- if (sessions.length > 0) {
- setSessionId(sessions[0].id);
- } else {
- const session = await createSession({ is_sidebar: true, title: 'Sidebar Chat' });
- setSessionId(session.id);
- }
- } catch (e) {
- console.error('AISidebar init error:', e);
- } finally {
- setLoading(false);
- }
- })();
- }, []);
const tabs: TabDef[] = [
{ key: 'chat', label: 'KI Chat', icon: robotIcon, testId: 'ai-sidebar-tab-chat' },
@@ -166,18 +146,18 @@ export function AISidebar() {
const ContributedComponent = contributed.component;
return ;
}
- // chat tab
- if (loading) {
- return (
- Laden...
- );
- }
- if (sessionId) {
- return KI Chat im Kommunikations-Plugin verfügbar
;
- }
+ // chat tab — voller KI-Chat lebt auf der /ai-assistant-Seite
return (
-
- Session konnte nicht erstellt werden
+
+
+ Der volle KI-Chat ist auf der AI-Assistant-Seite verfügbar.
+
+
+ AI Assistant öffnen
+
);
};