diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b836b5c0..0b1207cf 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -39,6 +39,7 @@ const KnowledgeSourceFormPageWrapper = lazy(() => import('./pages/KnowledgeSourc const PreviewViewPage = lazy(() => import('./pages/PreviewViewPage')); const NotFoundPage = lazy(() => import('./pages/NotFoundPage')); const DataRecordViewWrapper = lazy(() => import('./pages/DataRecordViewWrapper')); +const HubSimplePage = lazy(() => import('./pages/HubSimplePage')); import { useEffect } from 'react'; import { createFrappeSocket } from './utils/socket'; @@ -117,6 +118,16 @@ function App() { + }> + + + + } + /> + }> diff --git a/frontend/src/components/HomeHeaderActions.tsx b/frontend/src/components/HomeHeaderActions.tsx index a49a19a7..7ac8eee2 100644 --- a/frontend/src/components/HomeHeaderActions.tsx +++ b/frontend/src/components/HomeHeaderActions.tsx @@ -5,7 +5,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Plus, Bot, Workflow } from 'lucide-react'; +import { ChevronLeft, Plus, Bot, Workflow } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; export function HomeHeaderActions() { @@ -20,23 +20,29 @@ export function HomeHeaderActions() { }; return ( - - - - - - - - New Flow - - - - New Agent - - - +
+ + + + + + + + + New Flow + + + + New Agent + + + +
); } diff --git a/frontend/src/components/hub/HubConversationView.tsx b/frontend/src/components/hub/HubConversationView.tsx new file mode 100644 index 00000000..91dba027 --- /dev/null +++ b/frontend/src/components/hub/HubConversationView.tsx @@ -0,0 +1,138 @@ +import { useRef, useEffect } from 'react'; +import { motion } from 'motion/react'; +import { Send, Sparkles, Plus } from 'lucide-react'; +import { useUser } from '@/contexts/UserContext'; +import { SlashCommandMenu } from './SlashCommandMenu'; + +interface Message { + role: 'user' | 'assistant'; + content: string; +} + +interface HubConversationViewProps { + messages: Message[]; + inputValue: string; + setInputValue: (v: string) => void; + onSend: () => void; + showSlashMenu: boolean; + slashQuery: string; + onSlashSelect: (cmd: string) => void; + onNewChat: () => void; + isStreaming?: boolean; +} + +export function HubConversationView({ + messages, inputValue, setInputValue, onSend, + showSlashMenu, slashQuery, onSlashSelect, onNewChat, + isStreaming, +}: HubConversationViewProps) { + const { user } = useUser(); + const scrollRef = useRef(null); + + const initials = (user?.full_name || user?.name || 'U') + .split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2); + + useEffect(() => { + if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + }, [messages]); + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey && !showSlashMenu) { + e.preventDefault(); + onSend(); + } + }; + + return ( +
+ {/* Messages */} +
+ {messages.map((msg, i) => ( + +
+ {msg.role === 'user' ? ( +
+ {initials} +
+ ) : ( +
+ +
+ )} +
+
+ {msg.role === 'assistant' && ( +
+ Hub Orchestrator + System +
+ )} + {msg.content === '__NO_PROVIDER__' ? ( +
+

No AI Provider configured

+

Add a provider and model to start using Hub Orchestrator.

+ + Add Provider → + +
+ ) : ( +
+ {msg.content} +
+ )} +
+
+ ))} + + {/* Typing indicator */} + {(messages.length > 0 && messages[messages.length - 1].role === 'user') || isStreaming ? ( + +
+ +
+
+ {[0, 0.15, 0.3].map((delay, i) => ( + + ))} +
+
+ ) : null} +
+ + {/* Input */} +
+
+ +
+