Skip to content

Commit 8710868

Browse files
author
Eunsoo Lee
committed
Major changes
- Scroll to bottom on load and new message - Task / Subtask visual haul - Carats in PanelLeft fixed - Shortened task titles - Removed Content panel title icons - Sample data... visual haul - Progress ring is now a Coral component
1 parent 6d444c7 commit 8710868

File tree

13 files changed

+824
-613
lines changed

13 files changed

+824
-613
lines changed

src/frontend_react/src/components/content/PlanChat.tsx

Lines changed: 165 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ import remarkGfm from "remark-gfm";
55
import rehypePrism from "rehype-prism";
66
import { AgentType, PlanChatProps, role } from "@/models";
77
import {
8-
Body1,
9-
Button,
10-
Spinner,
11-
Tag,
12-
ToolbarDivider
8+
Body1,
9+
Button,
10+
Spinner,
11+
Tag,
12+
ToolbarDivider,
1313
} from "@fluentui/react-components";
14-
import {
15-
HeartRegular,
16-
} from "@fluentui/react-icons";
17-
import { useRef, useState } from "react";
14+
import { DiamondRegular, HeartRegular } from "@fluentui/react-icons";
15+
import { useEffect, useRef, useState } from "react";
1816
import ReactMarkdown from "react-markdown";
1917
import "../../styles/PlanChat.css";
2018
import "../../styles/Chat.css";
@@ -23,105 +21,168 @@ import { TaskService } from "@/services/TaskService";
2321
import InlineToaster from "../toast/InlineToaster";
2422

2523
const PlanChat: React.FC<PlanChatProps> = ({
26-
planData,
27-
input,
28-
loading,
29-
submittingChatDisableInput,
30-
OnChatSubmit
31-
24+
planData,
25+
input,
26+
loading,
27+
submittingChatDisableInput,
28+
OnChatSubmit,
3229
}) => {
33-
const messages = planData?.messages || [];
34-
const [inputValue, setInput] = useState(input);
35-
const [isTyping, setIsTyping] = useState(false);
36-
const [showScrollButton, setShowScrollButton] = useState(false);
37-
const [inputHeight, setInputHeight] = useState(0);
38-
39-
const messagesContainerRef = useRef<HTMLDivElement>(null);
40-
const inputContainerRef = useRef<HTMLDivElement>(null);
41-
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 === AgentType.HUMAN;
51-
52-
return (
53-
<div key={index} className={`message ${isHuman ? role.user : role.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-
91-
</div>
92-
93-
{showScrollButton && (
94-
<Tag
95-
onClick={scrollToBottom}
96-
className="scroll-to-bottom plan-chat-scroll-button"
97-
shape="circular"
98-
style={{ bottom: inputHeight }}
99-
>
100-
Back to bottom
101-
</Tag>
102-
)}
103-
<InlineToaster />
104-
<div ref={inputContainerRef} className="plan-chat-input-container">
105-
<div className="plan-chat-input-wrapper">
106-
<ChatInput
107-
value={inputValue}
108-
onChange={setInput}
109-
onEnter={() => OnChatSubmit(inputValue)}
110-
disabledChat={planData.enableChat ? submittingChatDisableInput : true}
111-
placeholder="Add more info to this task..."
30+
const messages = planData?.messages || [];
31+
const [inputValue, setInput] = useState(input);
32+
const [isTyping, setIsTyping] = useState(false);
33+
const [showScrollButton, setShowScrollButton] = useState(false);
34+
const [inputHeight, setInputHeight] = useState(0);
35+
36+
const messagesContainerRef = useRef<HTMLDivElement>(null);
37+
const inputContainerRef = useRef<HTMLDivElement>(null);
38+
39+
// Scroll to Bottom useEffect
40+
41+
useEffect(() => {
42+
scrollToBottom();
43+
}, [messages]);
44+
45+
//Scroll to Bottom Buttom
46+
47+
useEffect(() => {
48+
const container = messagesContainerRef.current;
49+
if (!container) return;
50+
51+
const handleScroll = () => {
52+
const { scrollTop, scrollHeight, clientHeight } = container;
53+
setShowScrollButton(scrollTop + clientHeight < scrollHeight - 100);
54+
};
55+
56+
container.addEventListener("scroll", handleScroll);
57+
return () => container.removeEventListener("scroll", handleScroll);
58+
}, []);
59+
60+
useEffect(() => {
61+
if (inputContainerRef.current) {
62+
setInputHeight(inputContainerRef.current.offsetHeight);
63+
}
64+
}, [inputValue]); // or [inputValue, submittingChatDisableInput]
65+
66+
67+
68+
const scrollToBottom = () => {
69+
messagesContainerRef.current?.scrollTo({
70+
top: messagesContainerRef.current.scrollHeight,
71+
behavior: "smooth",
72+
});
73+
setShowScrollButton(false);
74+
};
75+
76+
if (!planData) return <Spinner size="large" />;
77+
return (
78+
<div className="chat-container">
79+
<div className="messages" ref={messagesContainerRef}>
80+
<div className="message-wrapper">
81+
{messages.map((msg, index) => {
82+
const isHuman = msg.source === AgentType.HUMAN;
83+
84+
return (
85+
<div
86+
key={index}
87+
className={`message ${isHuman ? role.user : role.assistant}`}
88+
>
89+
{!isHuman && (
90+
<div className="plan-chat-header">
91+
<div className="plan-chat-speaker">
92+
<Body1 className="speaker-name">
93+
{TaskService.cleanTextToSpaces(msg.source)}
94+
</Body1>
95+
<Tag
96+
size="extra-small"
97+
shape="rounded"
98+
appearance="brand"
99+
className="bot-tag"
100+
>
101+
BOT
102+
</Tag>
103+
</div>
104+
</div>
105+
)}
106+
107+
<Body1>
108+
<div className="plan-chat-message-content">
109+
<ReactMarkdown
110+
remarkPlugins={[remarkGfm]}
111+
rehypePlugins={[rehypePrism]}
112112
>
113-
<Button
114-
appearance="transparent"
115-
onClick={() => OnChatSubmit(inputValue)}
116-
icon={<Send />}
117-
disabled={planData.enableChat ? submittingChatDisableInput : true}
118-
/>
119-
</ChatInput>
120-
</div>
121-
</div>
113+
{msg.content || ""}
114+
</ReactMarkdown>
115+
116+
{!isHuman && (
117+
<div className="assistant-footer">
118+
<div className="assistant-actions">
119+
<div>
120+
<Button
121+
onClick={() =>
122+
msg.content &&
123+
navigator.clipboard.writeText(msg.content)
124+
}
125+
title="Copy Response"
126+
appearance="subtle"
127+
style={{ height: 28, width: 28 }}
128+
icon={<Copy />}
129+
/>
130+
131+
</div>
132+
133+
<Tag icon={<DiamondRegular/>} appearance="filled" size="extra-small">
134+
Sample data for demonstration purposes only.
135+
</Tag>
136+
</div>
137+
</div>
138+
)}
139+
</div>
140+
</Body1>
141+
</div>
142+
);
143+
})}
122144
</div>
145+
</div>
123146

124-
);
147+
{showScrollButton && (
148+
<Tag
149+
onClick={scrollToBottom}
150+
className="scroll-to-bottom plan-chat-scroll-button"
151+
shape="circular"
152+
style={{
153+
bottom: inputHeight,
154+
position: "absolute", // ensure this or your class handles it
155+
right: 16, // optional, for right alignment
156+
zIndex: 5,
157+
}}
158+
>
159+
Back to bottom
160+
</Tag>
161+
162+
)}
163+
<InlineToaster />
164+
<div ref={inputContainerRef} className="plan-chat-input-container">
165+
<div className="plan-chat-input-wrapper">
166+
<ChatInput
167+
value={inputValue}
168+
onChange={setInput}
169+
onEnter={() => OnChatSubmit(inputValue)}
170+
disabledChat={
171+
planData.enableChat ? submittingChatDisableInput : true
172+
}
173+
placeholder="Add more info to this task..."
174+
>
175+
<Button
176+
appearance="transparent"
177+
onClick={() => OnChatSubmit(inputValue)}
178+
icon={<Send />}
179+
disabled={planData.enableChat ? submittingChatDisableInput : true}
180+
/>
181+
</ChatInput>
182+
</div>
183+
</div>
184+
</div>
185+
);
125186
};
126187

127188
export default PlanChat;

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import PanelLeft from "@/coral/components/Panels/PanelLeft";
22
import PanelLeftToolbar from "@/coral/components/Panels/PanelLeftToolbar";
33
import {
4+
Body1Strong,
45
Button,
6+
Subtitle1,
7+
Subtitle2,
58
Toast,
69
ToastBody,
710
ToastTitle,
@@ -26,7 +29,10 @@ import PanelFooter from "@/coral/components/Panels/PanelFooter";
2629
import PanelUserCard from "../../coral/components/Panels/UserCard";
2730
import { getUserInfoGlobal } from "@/api/config";
2831

29-
const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({ reloadTasks, onNewTaskButton }) => {
32+
const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({
33+
reloadTasks,
34+
onNewTaskButton,
35+
}) => {
3036
const { dispatchToast } = useToastController("toast");
3137
const navigate = useNavigate();
3238
const { planId } = useParams<{ planId: string }>();
@@ -36,7 +42,9 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({ reloadTasks, onNewTaskButt
3642
const [plans, setPlans] = useState<PlanWithSteps[] | null>(null);
3743
const [plansLoading, setPlansLoading] = useState<boolean>(false);
3844
const [plansError, setPlansError] = useState<Error | null>(null);
39-
const [userInfo, setUserInfo] = useState<UserInfo | null>(getUserInfoGlobal());
45+
const [userInfo, setUserInfo] = useState<UserInfo | null>(
46+
getUserInfoGlobal()
47+
);
4048
// Fetch plans
4149
const loadPlansData = useCallback(async (forceRefresh = false) => {
4250
try {
@@ -101,17 +109,23 @@ const PlanPanelLeft: React.FC<PlanPanelLefProps> = ({ reloadTasks, onNewTaskButt
101109
return (
102110
<div style={{ flexShrink: 0, display: "flex", overflow: "hidden" }}>
103111
<PanelLeft panelWidth={280} panelResize={true}>
104-
<PanelLeftToolbar linkTo="/" panelTitle="Contoso" panelIcon={<ContosoLogo />}>
112+
<PanelLeftToolbar
113+
linkTo="/"
114+
panelTitle="Contoso"
115+
panelIcon={<ContosoLogo />}
116+
>
105117
<Tooltip content="New task" relationship={"label"} />
106118
</PanelLeftToolbar>
107119

108120
<br />
109-
<div
110-
className="tab tab-new-task"
111-
onClick={onNewTaskButton}
112-
>
113-
<ChatAdd20Regular />
114-
New task
121+
<div className="tab tab-new-task" onClick={onNewTaskButton}>
122+
<div className="tab tab-new-task-icon"
123+
>
124+
<ChatAdd20Regular />
125+
126+
</div>
127+
<Body1Strong>New task</Body1Strong>
128+
115129
</div>
116130

117131
<br />

0 commit comments

Comments
 (0)