From 7e53b747c74118fad7d96c575db575d0076875c0 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Sat, 18 Jul 2026 17:48:09 +0200 Subject: [PATCH] feat: add Proaktiv and KI Chat tabs to AI sidebar --- .../src/components/ai/SuggestionSidebar.tsx | 134 ++++++++++-------- frontend/src/components/layout/AISidebar.tsx | 84 ++++++++--- frontend/src/components/layout/AppShell.tsx | 8 -- frontend/src/components/layout/TopBar.tsx | 4 +- frontend/src/store/uiStore.ts | 7 + 5 files changed, 149 insertions(+), 88 deletions(-) diff --git a/frontend/src/components/ai/SuggestionSidebar.tsx b/frontend/src/components/ai/SuggestionSidebar.tsx index 49779b6..5e2579d 100644 --- a/frontend/src/components/ai/SuggestionSidebar.tsx +++ b/frontend/src/components/ai/SuggestionSidebar.tsx @@ -7,7 +7,19 @@ interface SuggestionSidebarProps { onClose: () => void; } -export function SuggestionSidebar({ isOpen, onClose }: SuggestionSidebarProps) { +const filterOptions = [ + { value: 'all', label: 'Alle' }, + { value: 'info', label: 'Info' }, + { value: 'warning', label: 'Warnung' }, + { value: 'action', label: 'Aktion' }, + { value: 'insight', label: 'Erkenntnis' }, +]; + +/** + * Inline suggestion list — no overlay/aside wrapper. + * Used inside the AISidebar proactive tab. + */ +export function SuggestionList() { const { suggestions, connected, dismiss, act } = useSuggestions(); const [filter, setFilter] = useState('all'); @@ -15,14 +27,68 @@ export function SuggestionSidebar({ isOpen, onClose }: SuggestionSidebarProps) { ? suggestions : suggestions.filter(s => s.suggestion_type === filter); - const filterOptions = [ - { value: 'all', label: 'Alle' }, - { value: 'info', label: 'Info' }, - { value: 'warning', label: 'Warnung' }, - { value: 'action', label: 'Aktion' }, - { value: 'insight', label: 'Erkenntnis' }, - ]; + return ( +
+ {/* Live indicator */} +
+ {connected && ( + + + Live + + )} + + {suggestions.length} Vorschläge + +
+ {/* Filter */} +
+ {filterOptions.map(opt => ( + + ))} +
+ + {/* Content */} +
+ {filteredSuggestions.length === 0 ? ( +
+
🤖
+

Keine Vorschläge vorhanden

+

Die KI analysiert deinen Kontext...

+
+ ) : ( +
+ {filteredSuggestions.map(suggestion => ( + + ))} +
+ )} +
+
+ ); +} + +/** + * Legacy overlay-based SuggestionSidebar — kept for backwards compat. + * Uses SuggestionList internally. + */ +export function SuggestionSidebar({ isOpen, onClose }: SuggestionSidebarProps) { return ( <> {/* Overlay */} @@ -43,12 +109,6 @@ export function SuggestionSidebar({ isOpen, onClose }: SuggestionSidebarProps) {

KI Vorschläge

- {connected && ( - - - Live - - )}
- {/* Filter */} -
- {filterOptions.map(opt => ( - - ))} -
- - {/* Content */} -
- {filteredSuggestions.length === 0 ? ( -
-
🤖
-

Keine Vorschläge vorhanden

-

Die KI analysiert deinen Kontext...

-
- ) : ( -
- {filteredSuggestions.map(suggestion => ( - - ))} -
- )} -
- - {/* Footer */} -
- - {suggestions.length} Vorschläge insgesamt - -
+ ); diff --git a/frontend/src/components/layout/AISidebar.tsx b/frontend/src/components/layout/AISidebar.tsx index 7ea449b..977552b 100644 --- a/frontend/src/components/layout/AISidebar.tsx +++ b/frontend/src/components/layout/AISidebar.tsx @@ -1,11 +1,12 @@ import React, { useState, useEffect } from 'react'; import { ChatWindow } from '@/components/ai/ChatWindow'; import { ResizablePanel } from '@/components/ui/ResizablePanel'; +import { SuggestionList } from '@/components/ai/SuggestionSidebar'; import { createSession, fetchSessions } from '@/api/ai'; import { useUIStore } from '@/store/uiStore'; export function AISidebar() { - const { aiSidebarCollapsed, toggleAISidebar } = useUIStore(); + const { aiSidebarCollapsed, toggleAISidebar, aiSidebarTab, setAISidebarTab } = useUIStore(); const [sessionId, setSessionId] = useState(null); const [loading, setLoading] = useState(true); @@ -48,6 +49,65 @@ export function AISidebar() { ); } + const renderTabContent = () => { + if (aiSidebarTab === 'proactive') { + return ; + } + // chat tab + if (loading) { + return ( +
Laden...
+ ); + } + if (sessionId) { + return ; + } + return ( +
+ Session konnte nicht erstellt werden +
+ ); + }; + + const tabBar = ( +
+ + +
+ ); + // Expanded: Mobile = full width overlay, Desktop = resizable panel return ( <> @@ -70,16 +130,9 @@ export function AISidebar() { KI Assistent + {tabBar}
- {loading ? ( -
Laden...
- ) : sessionId ? ( - - ) : ( -
- Session konnte nicht erstellt werden -
- )} + {renderTabContent()}
@@ -105,16 +158,9 @@ export function AISidebar() { + {tabBar}
- {loading ? ( -
Laden...
- ) : sessionId ? ( - - ) : ( -
- Session konnte nicht erstellt werden -
- )} + {renderTabContent()}
diff --git a/frontend/src/components/layout/AppShell.tsx b/frontend/src/components/layout/AppShell.tsx index 3521c1e..fd87951 100644 --- a/frontend/src/components/layout/AppShell.tsx +++ b/frontend/src/components/layout/AppShell.tsx @@ -4,14 +4,11 @@ import { Sidebar } from './Sidebar'; import { TopBar } from './TopBar'; import { PluginToolbar } from './PluginToolbar'; import { AISidebar } from './AISidebar'; -import { SuggestionSidebar } from '@/components/ai/SuggestionSidebar'; import { ToastContainer } from '@/components/ui/Toast'; -import { useUIStore } from '@/store/uiStore'; import { useAIContext } from '@/hooks/useAIContext'; export function AppShell() { const location = useLocation(); - const { suggestionSidebarOpen, toggleSuggestionSidebar } = useUIStore(); // Track context for AI Proactive suggestions useAIContext(); @@ -39,11 +36,6 @@ export function AppShell() { {/* AI Sidebar — full height, right of TopBar and Toolbar */} {showAISidebar && } - {/* Suggestion Sidebar — overlays from right */} - toggleSuggestionSidebar()} - /> ); diff --git a/frontend/src/components/layout/TopBar.tsx b/frontend/src/components/layout/TopBar.tsx index fa9fb13..3fd8b66 100644 --- a/frontend/src/components/layout/TopBar.tsx +++ b/frontend/src/components/layout/TopBar.tsx @@ -15,7 +15,7 @@ export function TopBar() { const { t } = useTranslation(); const navigate = useNavigate(); const { user } = useAuthStore(); - const { toggleSidebar, toggleAISidebar, toggleSuggestionSidebar, aiSidebarCollapsed } = useUIStore(); + const { toggleSidebar, toggleAISidebar, openAISidebarProactive, aiSidebarCollapsed } = useUIStore(); const { currentTenant, availableTenants, switchTenant, isSwitching } = useTenant(); const logoutMutation = useLogout(); @@ -143,7 +143,7 @@ export function TopBar() {
{/* AI Suggestions Badge */} - + {/* Notifications */}
diff --git a/frontend/src/store/uiStore.ts b/frontend/src/store/uiStore.ts index b364e79..9af0696 100644 --- a/frontend/src/store/uiStore.ts +++ b/frontend/src/store/uiStore.ts @@ -2,6 +2,7 @@ import { create } from 'zustand'; export type Theme = 'light' | 'dark' | 'system'; export type Locale = 'de' | 'en'; +export type AISidebarTab = 'proactive' | 'chat'; export interface Toast { id: string; @@ -16,6 +17,7 @@ export interface UIState { sidebarOpen: boolean; suggestionSidebarOpen: boolean; aiSidebarCollapsed: boolean; + aiSidebarTab: AISidebarTab; toasts: Toast[]; setTheme: (theme: Theme) => void; setLocale: (locale: Locale) => void; @@ -24,6 +26,8 @@ export interface UIState { toggleSuggestionSidebar: () => void; toggleAISidebar: () => void; setAISidebarCollapsed: (collapsed: boolean) => void; + setAISidebarTab: (tab: AISidebarTab) => void; + openAISidebarProactive: () => void; addToast: (toast: Omit) => void; removeToast: (id: string) => void; clearToasts: () => void; @@ -37,6 +41,7 @@ export const useUIStore = create((set) => ({ sidebarOpen: true, suggestionSidebarOpen: false, aiSidebarCollapsed: true, + aiSidebarTab: 'chat', toasts: [], setTheme: (theme) => { localStorage.setItem('leocrm_theme', theme); @@ -51,6 +56,8 @@ export const useUIStore = create((set) => ({ toggleSuggestionSidebar: () => set((s) => ({ suggestionSidebarOpen: !s.suggestionSidebarOpen })), toggleAISidebar: () => set((s) => ({ aiSidebarCollapsed: !s.aiSidebarCollapsed })), setAISidebarCollapsed: (collapsed) => set({ aiSidebarCollapsed: collapsed }), + setAISidebarTab: (tab) => set({ aiSidebarTab: tab }), + openAISidebarProactive: () => set({ aiSidebarCollapsed: false, aiSidebarTab: 'proactive' }), addToast: (toast) => { const id = `toast-${++toastIdCounter}`; set((s) => ({ toasts: [...s.toasts, { ...toast, id }] }));