From 1338da39c8ba5667d0fda67d31077e8f95bfc7ca Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 21 Apr 2025 04:46:45 -0700 Subject: [PATCH] improvement(ui/ux): chat preview --- .../chat/components/chat-modal/chat-modal.tsx | 196 +++++++++++------- sim/lib/generate-chat-title.ts | 40 ++++ 2 files changed, 160 insertions(+), 76 deletions(-) create mode 100644 sim/lib/generate-chat-title.ts diff --git a/sim/app/w/[id]/components/panel/components/chat/components/chat-modal/chat-modal.tsx b/sim/app/w/[id]/components/panel/components/chat/components/chat-modal/chat-modal.tsx index 5207a650b4..e0a92edd58 100644 --- a/sim/app/w/[id]/components/panel/components/chat/components/chat-modal/chat-modal.tsx +++ b/sim/app/w/[id]/components/panel/components/chat/components/chat-modal/chat-modal.tsx @@ -1,15 +1,69 @@ 'use client' import { KeyboardEvent, useEffect, useMemo, useRef } from 'react' -import { ArrowUp, X } from 'lucide-react' +import { ArrowUp, CornerDownLeft, Loader2, X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { cn } from '@/lib/utils' import { useExecutionStore } from '@/stores/execution/store' import { useChatStore } from '@/stores/panel/chat/store' +import { ChatMessage as ChatMessageType } from '@/stores/panel/chat/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' +import { JSONView } from '@/app/w/[id]/components/panel/components/console/components/json-view/json-view' import { useWorkflowExecution } from '@/app/w/[id]/hooks/use-workflow-execution' +interface ChatMessageProps { + message: ChatMessageType +} + +// ChatGPT-style message component specifically for modal +function ModalChatMessage({ message }: ChatMessageProps) { + // Check if content is a JSON object + const isJsonObject = useMemo(() => { + return typeof message.content === 'object' && message.content !== null + }, [message.content]) + + // For user messages (on the right) + if (message.type === 'user') { + return ( +
+
+
+
+
+ {isJsonObject ? ( + + ) : ( + {message.content} + )} +
+
+
+
+
+ ) + } + + // For assistant messages (on the left) + return ( +
+
+
+
+
+ {isJsonObject ? ( + + ) : ( + {message.content} + )} +
+
+
+
+
+ ) +} + interface ChatModalProps { open: boolean onOpenChange: (open: boolean) => void @@ -20,6 +74,7 @@ interface ChatModalProps { export function ChatModal({ open, onOpenChange, chatMessage, setChatMessage }: ChatModalProps) { const messagesEndRef = useRef(null) const messagesContainerRef = useRef(null) + const inputRef = useRef(null) const { activeWorkflowId } = useWorkflowRegistry() const { messages, addMessage } = useChatStore() @@ -45,6 +100,13 @@ export function ChatModal({ open, onOpenChange, chatMessage, setChatMessage }: C } }, [workflowMessages]) + // Focus input when modal opens + useEffect(() => { + if (open && inputRef.current) { + inputRef.current.focus() + } + }, [open]) + // Handle send message const handleSendMessage = async () => { if (!chatMessage.trim() || !activeWorkflowId || isExecuting) return @@ -78,12 +140,28 @@ export function ChatModal({ open, onOpenChange, chatMessage, setChatMessage }: C return (
- {/* Close button (fixed position) */} -
+ + + {/* Header with title and close button */} +
+

Chat

{/* Messages container */} -
-
- {workflowMessages.map((message) => ( -
-
-
- {message.content} -
+
+
+ {workflowMessages.length === 0 ? ( +
+
+

How can I help you today?

+

+ Ask me anything about your workflow. +

- ))} + ) : ( + workflowMessages.map((message) => ( + + )) + )} {/* Loading indicator (shows only when executing) */} {isExecuting && ( -
+
-
-
- - - +
+
+
+
+
@@ -136,68 +207,41 @@ export function ChatModal({ open, onOpenChange, chatMessage, setChatMessage }: C
{/* Input area (fixed at bottom) */} -
+
-
+
setChatMessage(e.target.value)} onKeyDown={handleKeyPress} placeholder="Message..." - className="flex-1 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-14 pr-12 bg-transparent pl-4 text-base" + className="flex-1 border-0 focus-visible:ring-0 focus-visible:ring-offset-0 py-7 pr-16 bg-transparent pl-6 text-base min-h-[50px] rounded-2xl" disabled={!activeWorkflowId || isExecuting} />
+ +
+

+ {activeWorkflowId + ? 'Your messages will be processed by the active workflow' + : 'Select a workflow to start chatting'} +

+
- - {/* Typing indicator animation styles */} -
) } diff --git a/sim/lib/generate-chat-title.ts b/sim/lib/generate-chat-title.ts new file mode 100644 index 0000000000..46b84d86e2 --- /dev/null +++ b/sim/lib/generate-chat-title.ts @@ -0,0 +1,40 @@ +import OpenAI from 'openai' + +/** + * Generates a short title for a chat based on the first message + * @param message First user message in the chat + * @returns A short title or null if API key is not available + */ +export async function generateChatTitle(message: string): Promise { + const apiKey = process.env.OPENAI_API_KEY + + if (!apiKey) { + return null + } + + try { + const openai = new OpenAI({ apiKey }) + + const response = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: [ + { + role: 'system', + content: 'Generate a very short title (3-5 words max) for a chat that starts with this message. The title should be concise and descriptive.' + }, + { + role: 'user', + content: message + } + ], + max_tokens: 20, + temperature: 0.7, + }) + + const title = response.choices[0]?.message?.content?.trim() || null + return title + } catch (error) { + console.error('Error generating chat title:', error) + return null + } +} \ No newline at end of file