Skip to content

Commit af97365

Browse files
author
Eunsoo Lee
committed
Major changes
- Header logo and title now has a linkTo prop - Disabled states for PromptCard - Placeholder user messages - Chat loading state - Open/close Right Panel toggle
1 parent 13195ac commit af97365

File tree

8 files changed

+544
-356
lines changed

8 files changed

+544
-356
lines changed

src/frontend_react/src/components/content/HomeInput.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ const HomeInput: React.FC<HomeInputProps> = ({
131131
icon={task.icon}
132132
description={task.description}
133133
onClick={() => handleQuickTaskClick(task)}
134+
disabled
134135
/>
135136
))}
136137
</div>
Lines changed: 194 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,215 @@
1+
/**
2+
* PlanChat.tsx
3+
* ---------------------------------------
4+
* TEMP FRONTEND INJECTION FOR USER CHAT BUBBLES
5+
*
6+
* This version adds placeholder "user" messages between bot messages
7+
* for UI layout/design purposes only.
8+
*
9+
* All placeholder injection logic is clearly marked, easy to remove.
10+
* Simply delete the `injectUserPlaceholders()` call and use raw `planData.messages`.
11+
*/
12+
113
import HeaderTools from "@/coral/components/Header/HeaderTools";
214
import { Copy, Send } from "@/coral/imports/bundleicons";
315
import ChatInput from "@/coral/modules/ChatInput";
416
import remarkGfm from "remark-gfm";
517
import rehypePrism from "rehype-prism";
618
import { PlanChatProps } from "@/models";
7-
import {
8-
Body1,
9-
Button,
10-
Spinner,
11-
Tag,
12-
ToolbarDivider
13-
} from "@fluentui/react-components";
14-
import {
15-
HeartRegular,
16-
} from "@fluentui/react-icons";
19+
import { Body1, Button, Spinner, Tag } from "@fluentui/react-components";
1720
import { useRef, useState } from "react";
1821
import ReactMarkdown from "react-markdown";
1922
import "../../styles/PlanChat.css";
2023
import "../../styles/Chat.css";
2124
import "../../styles/prism-material-oceanic.css";
2225
import { TaskService } from "@/services/TaskService";
2326

24-
const PlanChat: React.FC<PlanChatProps> = ({
25-
planData,
26-
input,
27-
loading,
28-
OnChatSubmit
27+
/**
28+
* TEMPORARY DEV FUNCTION:
29+
* Injects empty human messages between bot messages
30+
* Used for front-end chat UI preview only
31+
*/
32+
const injectUserPlaceholders = (rawMessages: any[]) => {
33+
if (!rawMessages.length) return [];
34+
35+
const result = [];
36+
for (let i = 0; i < rawMessages.length; i++) {
37+
const current = rawMessages[i];
38+
result.push(current);
39+
40+
const next = rawMessages[i + 1];
41+
const currentIsBot = !current.source.includes("human");
42+
const nextIsBot = next && !next.source.includes("human");
43+
44+
// If two bots in a row, insert user bubble
45+
if (currentIsBot && nextIsBot) {
46+
result.push({
47+
source: `human_placeholder_${i}`,
48+
content: "", // Leave blank so it's easy to style separately
49+
});
50+
}
51+
}
52+
return result;
53+
};
2954

55+
const PlanChat: React.FC<PlanChatProps> = ({
56+
planData,
57+
input,
58+
loading,
59+
OnChatSubmit,
3060
}) => {
31-
const messages = planData?.messages || [];
32-
const [inputValue, setInput] = useState(input);
33-
const [isTyping, setIsTyping] = useState(false);
34-
const [showScrollButton, setShowScrollButton] = useState(false);
35-
const [inputHeight, setInputHeight] = useState(0);
36-
const [currentConversationId, setCurrentConversationId] = useState<string | undefined>(undefined);
37-
38-
const messagesContainerRef = useRef<HTMLDivElement>(null);
39-
const inputContainerRef = useRef<HTMLDivElement>(null);
40-
41-
const sendMessage = async () => { };
42-
43-
const scrollToBottom = () => { };
44-
if (!planData) return <Spinner size="large" />;
45-
return (
46-
<div className="chat-container">
47-
<div className="messages" ref={messagesContainerRef}>
48-
<div className="message-wrapper">
49-
{messages.map((msg, index) => {
50-
const isHuman = msg.source.includes("human");
51-
52-
return (
53-
<div key={index} className={`message ${isHuman ? "user" : "assistant"}`}>
54-
{!isHuman && (
55-
<div className="plan-chat-header">
56-
<div className="plan-chat-speaker">
57-
<span className="speaker-name">{TaskService.cleanTextToSpaces(msg.source)}</span>
58-
<Tag size="extra-small" shape="rounded" appearance="filled" className="bot-tag">BOT</Tag>
59-
</div>
60-
</div>
61-
)}
62-
63-
<Body1>
64-
<div className="plan-chat-message-content">
65-
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypePrism]}>
66-
{msg.content || ""}
67-
</ReactMarkdown>
68-
69-
{!isHuman && (
70-
<div className="assistant-footer">
71-
<div className="assistant-actions">
72-
<Button
73-
onClick={() => msg.content && navigator.clipboard.writeText(msg.content)}
74-
title="Copy Response"
75-
appearance="subtle"
76-
style={{ height: 28, width: 28 }}
77-
icon={<Copy />}
78-
/>
79-
<Tag size="extra-small">Sample data for demonstration purposes only.</Tag>
80-
</div>
81-
</div>
82-
)}
83-
</div>
84-
</Body1>
85-
</div>
86-
);
87-
})}
88-
</div>
89-
90-
{isTyping && (
91-
<div className="typing-indicator">
92-
<span>Thinking...</span>
61+
// 👇 Use modified placeholder-injected message list for layout preview
62+
const rawMessages = planData?.messages || [];
63+
const messages = injectUserPlaceholders(rawMessages); // 👈 Remove this call when backend data is live
64+
65+
const [inputValue, setInput] = useState(input);
66+
const [isTyping, setIsTyping] = useState(false);
67+
const [showScrollButton, setShowScrollButton] = useState(false);
68+
const [inputHeight, setInputHeight] = useState(0);
69+
const [currentConversationId, setCurrentConversationId] = useState<
70+
string | undefined
71+
>(undefined);
72+
73+
const messagesContainerRef = useRef<HTMLDivElement>(null);
74+
const inputContainerRef = useRef<HTMLDivElement>(null);
75+
76+
const sendMessage = async () => {
77+
// Placeholder for message send logic
78+
};
79+
80+
const scrollToBottom = () => {
81+
// Placeholder for scroll behavior
82+
};
83+
84+
if (!planData) return <Spinner size="large" />;
85+
86+
return (
87+
<div className="chat-container">
88+
<div className="messages" ref={messagesContainerRef}>
89+
<div className="message-wrapper">
90+
{messages.map((msg, index) => {
91+
const isHuman = msg.source.includes("human");
92+
93+
return (
94+
<div
95+
key={index}
96+
className={`message ${isHuman ? "user" : "assistant"}`}
97+
>
98+
{/* 🧠 Assistant header + label */}
99+
{!isHuman && (
100+
<div className="plan-chat-header">
101+
<div className="plan-chat-speaker">
102+
<span className="speaker-name">
103+
{TaskService.cleanTextToSpaces(msg.source)}
104+
</span>
105+
<Tag
106+
size="extra-small"
107+
shape="rounded"
108+
appearance="filled"
109+
className="bot-tag"
110+
>
111+
BOT
112+
</Tag>
93113
</div>
114+
</div>
94115
)}
95-
</div>
96-
97-
{showScrollButton && (
98-
<Tag
99-
onClick={scrollToBottom}
100-
className="scroll-to-bottom plan-chat-scroll-button"
101-
shape="circular"
102-
style={{ bottom: inputHeight }}
103-
>
104-
Back to bottom
105-
</Tag>
106-
)}
107-
108-
<div ref={inputContainerRef} className="plan-chat-input-container">
109-
<div className="plan-chat-input-wrapper">
110-
<ChatInput
111-
value={inputValue}
112-
onChange={setInput}
113-
onEnter={() => OnChatSubmit(inputValue)}
114-
disabledChat={!planData.enableChat}
115-
placeholder="Add more info to this task..."
116-
>
117-
<Button
118-
appearance="transparent"
119-
onClick={sendMessage}
120-
icon={<Send />}
121-
disabled={!planData?.enableChat || isTyping || !input.trim()}
122-
/>
123-
</ChatInput>
124-
</div>
125-
</div>
116+
117+
<Body1>
118+
<div className="plan-chat-message-content">
119+
{/* 📄 If message has content, render Markdown */}
120+
{msg.content ? (
121+
<ReactMarkdown
122+
remarkPlugins={[remarkGfm]}
123+
rehypePlugins={[rehypePrism]}
124+
>
125+
{msg.content}
126+
</ReactMarkdown>
127+
) : (
128+
// 🧪 PLACEHOLDER UI for empty user messages
129+
isHuman && (
130+
<ReactMarkdown
131+
remarkPlugins={[remarkGfm]}
132+
rehypePlugins={[rehypePrism]}
133+
>
134+
This is a placeholder user message.
135+
{/*
136+
SWAP THIS
137+
const messages = injectUserPlaceholders(rawMessages);
138+
WITH
139+
const messages = rawMessages;
140+
*/}
141+
</ReactMarkdown>
142+
)
143+
)}
144+
145+
{/* 🛠️ Assistant footer with Copy button */}
146+
{!isHuman && msg.content && (
147+
<div className="assistant-footer">
148+
<div className="assistant-actions">
149+
<Button
150+
onClick={() =>
151+
msg.content &&
152+
navigator.clipboard.writeText(msg.content)
153+
}
154+
title="Copy Response"
155+
appearance="subtle"
156+
style={{ height: 28, width: 28 }}
157+
icon={<Copy />}
158+
/>
159+
<Tag size="extra-small">
160+
Sample data for demonstration purposes only.
161+
</Tag>
162+
</div>
163+
</div>
164+
)}
165+
</div>
166+
</Body1>
167+
</div>
168+
);
169+
})}
170+
</div>
171+
172+
{/* 🤖 Typing indicator while AI is generating */}
173+
{isTyping && (
174+
<div className="typing-indicator">
175+
<span>Thinking...</span>
176+
</div>
177+
)}
178+
</div>
179+
180+
{/* ⬇️ Back-to-bottom bubble when not scrolled */}
181+
{showScrollButton && (
182+
<Tag
183+
onClick={scrollToBottom}
184+
className="scroll-to-bottom plan-chat-scroll-button"
185+
shape="circular"
186+
style={{ bottom: inputHeight }}
187+
>
188+
Back to bottom
189+
</Tag>
190+
)}
191+
192+
{/* 💬 Input field with Send button */}
193+
<div ref={inputContainerRef} className="plan-chat-input-container">
194+
<div className="plan-chat-input-wrapper">
195+
<ChatInput
196+
value={inputValue}
197+
onChange={setInput}
198+
onEnter={() => OnChatSubmit(inputValue)}
199+
disabledChat={!planData.enableChat}
200+
placeholder="Add more info to this task..."
201+
>
202+
<Button
203+
appearance="transparent"
204+
onClick={sendMessage}
205+
icon={<Send />}
206+
disabled={!planData?.enableChat || isTyping || !input.trim()}
207+
/>
208+
</ChatInput>
126209
</div>
127-
);
210+
</div>
211+
</div>
212+
);
128213
};
129214

130215
export default PlanChat;

src/frontend_react/src/components/content/PlanPanelLeft.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({ onNewTaskButton }) => {
100100
return (
101101
<div style={{ flexShrink: 0, display: "flex", overflow: "hidden" }}>
102102
<PanelLeft panelWidth={280} panelResize={true}>
103-
<PanelLeftToolbar panelTitle="Contoso" panelIcon={<ContosoLogo />}>
103+
<PanelLeftToolbar linkTo="/" panelTitle="Contoso" panelIcon={<ContosoLogo />}>
104104
<Tooltip content="New task" relationship={"label"} />
105105
</PanelLeftToolbar>
106106

0 commit comments

Comments
 (0)