Skip to content

Commit aceb2e9

Browse files
committed
feat(frontend/hooks): add streaming support to use-chat hook
Implemented progressive streaming for chat messages, allowing incremental display of responses as they are generated. This enhances user experience by mimicking real-time interaction. Modified files (5): - use-chat.ts: Added streaming logic and callbacks for UI updates - ChatInterface.tsx: Integrated streaming state into chat interface - ChatMessagesList.tsx: Updated to handle streaming messages - ChatMessage.tsx: Added blinking cursor effect during streaming - chat-service.ts: Created sendMessageWithStreaming function
1 parent 4cbc919 commit aceb2e9

6 files changed

Lines changed: 223 additions & 75 deletions

File tree

src/frontend/task-agent-web/components/chat/ChatInterface.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function ChatInterface() {
3232
messages,
3333
input,
3434
isLoading,
35+
isStreaming, // New: progressive rendering state
3536
error,
3637
threadId,
3738
handleInputChange,
@@ -132,6 +133,7 @@ export function ChatInterface() {
132133
<ChatMessagesList
133134
messages={messages}
134135
isLoading={isLoading}
136+
isStreaming={isStreaming} // Pass streaming state
135137
onSuggestionClick={sendSuggestion}
136138
/>
137139
</div>

src/frontend/task-agent-web/components/chat/ChatMessage.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface ChatMessageProps {
1212
message: ChatMessageType;
1313
onSuggestionClick?: (suggestion: string) => void;
1414
isLoading?: boolean;
15+
isStreaming?: boolean; // New: shows blinking cursor while streaming
1516
}
1617

1718
/**
@@ -44,6 +45,7 @@ export const ChatMessage = memo(function ChatMessage({
4445
message,
4546
onSuggestionClick,
4647
isLoading = false,
48+
isStreaming = false, // Streaming cursor state
4749
}: ChatMessageProps) {
4850
const isUser = message.role === "user";
4951
const [isHovered, setIsHovered] = useState(false);
@@ -68,21 +70,26 @@ export const ChatMessage = memo(function ChatMessage({
6870
onMouseLeave={() => setIsHovered(false)}
6971
>
7072
<div
71-
className={`rounded-2xl px-3 sm:px-5 py-3 sm:py-3.5 max-w-[90%] sm:max-w-[85%] shadow-md hover:shadow-lg transition-shadow ${
73+
className={`relative rounded-2xl px-3 sm:px-5 py-3 sm:py-3.5 max-w-[90%] sm:max-w-[85%] shadow-md hover:shadow-lg transition-shadow ${
7274
isUser
7375
? "bg-gradient-to-br from-blue-600 to-blue-700 text-white ml-auto text-right"
74-
: "bg-white border-2 border-gray-200 mr-auto"
76+
: "bg-white dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 mr-auto"
7577
}`}
7678
>
79+
{/* Copy button - top right corner */}
7780
{!isUser && (
78-
<div className="font-bold text-gray-900 mb-2 flex items-center gap-2">
81+
<MessageActions content={textContent} isVisible={isHovered} />
82+
)}
83+
84+
{!isUser && (
85+
<div className="font-bold text-gray-900 dark:text-white mb-2 flex items-center gap-2">
7986
<span className="text-base sm:text-lg">🤖</span>
8087
<span className="text-sm sm:text-base">Task Assistant</span>
8188
</div>
8289
)}
8390
<div
8491
className={`markdown-content leading-relaxed text-sm sm:text-base ${
85-
isUser ? "text-white" : "text-gray-800"
92+
isUser ? "text-white" : "text-gray-800 dark:text-gray-200"
8693
}`}
8794
>
8895
<ReactMarkdown
@@ -91,6 +98,10 @@ export const ChatMessage = memo(function ChatMessage({
9198
>
9299
{textContent}
93100
</ReactMarkdown>
101+
{/* Blinking cursor while streaming - ChatGPT-like effect */}
102+
{isStreaming && (
103+
<span className="inline-block w-2 h-4 ml-0.5 bg-gray-800 dark:bg-gray-200 animate-pulse" />
104+
)}
94105
</div>
95106

96107
{/* Show suggestions for assistant messages */}
@@ -101,11 +112,6 @@ export const ChatMessage = memo(function ChatMessage({
101112
disabled={isLoading}
102113
/>
103114
)}
104-
105-
{/* Show action buttons for assistant messages on hover */}
106-
{!isUser && (
107-
<MessageActions content={textContent} isVisible={isHovered} />
108-
)}
109115
</div>
110116
</div>
111117
);

src/frontend/task-agent-web/components/chat/ChatMessagesList.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,41 @@ import type { ChatMessage as ChatMessageType } from "@/types/chat";
77
interface ChatMessagesListProps {
88
messages: ChatMessageType[];
99
isLoading: boolean;
10+
isStreaming?: boolean; // New: indicates text is being streamed progressively
1011
onSuggestionClick?: (suggestion: string) => void;
1112
}
1213

1314
/**
1415
* Chat messages list container with empty state and loading indicator
16+
* Supports progressive streaming with blinking cursor
1517
*/
1618
export function ChatMessagesList({
1719
messages,
1820
isLoading,
21+
isStreaming = false,
1922
onSuggestionClick,
2023
}: ChatMessagesListProps) {
24+
// Find the last message (streaming message) for cursor display
25+
const lastMessageIndex = messages.length - 1;
26+
2127
return (
2228
<>
2329
{messages.length === 0 ? (
2430
<div className="flex flex-col items-center justify-center text-center px-4">
25-
<div className="bg-white border border-gray-200 rounded-2xl p-6 sm:p-8 max-w-2xl shadow-sm">
26-
<div className="font-bold text-gray-900 mb-3 text-xl sm:text-2xl flex items-center justify-center gap-2">
31+
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl p-6 sm:p-8 max-w-2xl shadow-sm">
32+
<div className="font-bold text-gray-900 dark:text-white mb-3 text-xl sm:text-2xl flex items-center justify-center gap-2">
2733
<span className="text-2xl sm:text-3xl">🤖</span>
2834
<span>Task Assistant</span>
2935
</div>
30-
<p className="mb-4 text-gray-600 text-sm sm:text-base leading-relaxed">
36+
<p className="mb-4 text-gray-600 dark:text-gray-400 text-sm sm:text-base leading-relaxed">
3137
Hi! I&apos;m your task management assistant. I can help you
3238
create, organize, and track your tasks efficiently.
3339
</p>
34-
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 sm:p-4">
35-
<p className="text-xs sm:text-sm text-gray-700 font-medium mb-2">
40+
<div className="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg p-3 sm:p-4">
41+
<p className="text-xs sm:text-sm text-gray-700 dark:text-gray-300 font-medium mb-2">
3642
Try asking me:
3743
</p>
38-
<p className="text-xs sm:text-sm text-blue-700 italic">
44+
<p className="text-xs sm:text-sm text-blue-700 dark:text-blue-400 italic">
3945
&quot;Create a task to review project proposal&quot;
4046
<br />
4147
&quot;Show me all my high priority tasks&quot;
@@ -45,17 +51,20 @@ export function ChatMessagesList({
4551
</div>
4652
) : (
4753
<div className="flex flex-col gap-3 sm:gap-4 p-3 sm:p-4 py-4 sm:py-6">
48-
{messages.map((message) => (
54+
{messages.map((message, index) => (
4955
<ChatMessage
5056
key={message.id}
5157
message={message}
5258
onSuggestionClick={onSuggestionClick}
5359
isLoading={isLoading}
60+
// Show streaming cursor on last assistant message while streaming
61+
isStreaming={isStreaming && index === lastMessageIndex && message.role === "assistant"}
5462
/>
5563
))}
56-
{isLoading && <LoadingIndicator />}
64+
{/* Show loading indicator only when waiting for stream to start (no content yet) */}
65+
{isLoading && !isStreaming && <LoadingIndicator />}
5766
</div>
5867
)}
5968
</>
6069
);
61-
}
70+
}

src/frontend/task-agent-web/components/chat/MessageActions.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,17 @@ export function MessageActions({ content, isVisible }: MessageActionsProps) {
8585
if (!isVisible) return null;
8686

8787
return (
88-
<div className="flex items-center gap-1 mt-2 pt-2 border-t border-gray-100">
88+
<div className="absolute top-2 right-2 flex items-center gap-1">
8989
<button
9090
onClick={handleCopy}
9191
className="
92-
flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg
92+
flex items-center gap-1.5 px-2 py-1 rounded-md
9393
text-xs font-medium
94-
text-gray-500 hover:text-gray-700
95-
bg-gray-50 hover:bg-gray-100
94+
text-gray-400 hover:text-gray-600
95+
dark:text-gray-500 dark:hover:text-gray-300
96+
bg-gray-100/80 hover:bg-gray-200/90
97+
dark:bg-gray-700/80 dark:hover:bg-gray-600/90
98+
backdrop-blur-sm
9699
transition-all duration-200
97100
cursor-pointer
98101
"

0 commit comments

Comments
 (0)