Spaces:
Running
Running
Girish Jeswani commited on
Commit ·
637ec04
1
Parent(s): 69fa630
add action buttons to advisor messages
Browse files
phd-advisor-frontend/src/components/MessageBubble.js
CHANGED
|
@@ -1,10 +1,45 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
-
import { Reply } from 'lucide-react';
|
| 3 |
import { advisors, getAdvisorColors } from '../data/advisors';
|
| 4 |
import { useTheme } from '../contexts/ThemeContext';
|
| 5 |
|
| 6 |
-
const MessageBubble = ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
const { isDark } = useTheme();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
if (message.type === 'user') {
|
| 10 |
return (
|
|
@@ -26,6 +61,7 @@ const MessageBubble = ({ message, onClick, showReplyButton = false }) => {
|
|
| 26 |
const advisor = advisors[message.advisorId];
|
| 27 |
const Icon = advisor.icon;
|
| 28 |
const colors = getAdvisorColors(message.advisorId, isDark);
|
|
|
|
| 29 |
|
| 30 |
return (
|
| 31 |
<div className="advisor-message-container">
|
|
@@ -36,12 +72,11 @@ const MessageBubble = ({ message, onClick, showReplyButton = false }) => {
|
|
| 36 |
<Icon style={{ color: colors.color }} />
|
| 37 |
</div>
|
| 38 |
<div
|
| 39 |
-
className=
|
| 40 |
style={{
|
| 41 |
backgroundColor: colors.bgColor,
|
| 42 |
borderColor: colors.color + '40'
|
| 43 |
}}
|
| 44 |
-
onClick={onClick}
|
| 45 |
>
|
| 46 |
<div className="advisor-message-header">
|
| 47 |
<h4
|
|
@@ -50,6 +85,7 @@ const MessageBubble = ({ message, onClick, showReplyButton = false }) => {
|
|
| 50 |
>
|
| 51 |
{advisor.name}
|
| 52 |
{message.isReply && <span className="reply-badge">↳ Reply</span>}
|
|
|
|
| 53 |
</h4>
|
| 54 |
<span
|
| 55 |
className="message-time"
|
|
@@ -74,20 +110,66 @@ const MessageBubble = ({ message, onClick, showReplyButton = false }) => {
|
|
| 74 |
</p>
|
| 75 |
{showReplyButton && (
|
| 76 |
<div className="message-actions">
|
| 77 |
-
<
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
</div>
|
| 92 |
)}
|
| 93 |
</div>
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { Reply, Copy, Check, Maximize2 } from 'lucide-react';
|
| 3 |
import { advisors, getAdvisorColors } from '../data/advisors';
|
| 4 |
import { useTheme } from '../contexts/ThemeContext';
|
| 5 |
|
| 6 |
+
const MessageBubble = ({
|
| 7 |
+
message,
|
| 8 |
+
onReply,
|
| 9 |
+
onCopy,
|
| 10 |
+
onExpand,
|
| 11 |
+
showReplyButton = false
|
| 12 |
+
}) => {
|
| 13 |
const { isDark } = useTheme();
|
| 14 |
+
const [showTooltip, setShowTooltip] = useState(null);
|
| 15 |
+
const [copiedStates, setCopiedStates] = useState({});
|
| 16 |
+
|
| 17 |
+
const handleCopy = async (messageId, content) => {
|
| 18 |
+
try {
|
| 19 |
+
await navigator.clipboard.writeText(content);
|
| 20 |
+
setCopiedStates(prev => ({ ...prev, [messageId]: true }));
|
| 21 |
+
if (onCopy) onCopy(messageId, content);
|
| 22 |
+
|
| 23 |
+
// Reset the copied state after 2 seconds
|
| 24 |
+
setTimeout(() => {
|
| 25 |
+
setCopiedStates(prev => ({ ...prev, [messageId]: false }));
|
| 26 |
+
}, 2000);
|
| 27 |
+
} catch (err) {
|
| 28 |
+
console.error('Failed to copy text: ', err);
|
| 29 |
+
}
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const handleExpand = (messageId, advisorId) => {
|
| 33 |
+
if (onExpand) onExpand(messageId, advisorId);
|
| 34 |
+
};
|
| 35 |
+
|
| 36 |
+
const showTooltipWithDelay = (tooltipType) => {
|
| 37 |
+
setTimeout(() => setShowTooltip(tooltipType), 500);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
const hideTooltip = () => {
|
| 41 |
+
setShowTooltip(null);
|
| 42 |
+
};
|
| 43 |
|
| 44 |
if (message.type === 'user') {
|
| 45 |
return (
|
|
|
|
| 61 |
const advisor = advisors[message.advisorId];
|
| 62 |
const Icon = advisor.icon;
|
| 63 |
const colors = getAdvisorColors(message.advisorId, isDark);
|
| 64 |
+
const isCopied = copiedStates[message.id];
|
| 65 |
|
| 66 |
return (
|
| 67 |
<div className="advisor-message-container">
|
|
|
|
| 72 |
<Icon style={{ color: colors.color }} />
|
| 73 |
</div>
|
| 74 |
<div
|
| 75 |
+
className="advisor-message-bubble"
|
| 76 |
style={{
|
| 77 |
backgroundColor: colors.bgColor,
|
| 78 |
borderColor: colors.color + '40'
|
| 79 |
}}
|
|
|
|
| 80 |
>
|
| 81 |
<div className="advisor-message-header">
|
| 82 |
<h4
|
|
|
|
| 85 |
>
|
| 86 |
{advisor.name}
|
| 87 |
{message.isReply && <span className="reply-badge">↳ Reply</span>}
|
| 88 |
+
{message.isExpansion && <span className="expansion-badge">⤴ Expanded</span>}
|
| 89 |
</h4>
|
| 90 |
<span
|
| 91 |
className="message-time"
|
|
|
|
| 110 |
</p>
|
| 111 |
{showReplyButton && (
|
| 112 |
<div className="message-actions">
|
| 113 |
+
<div className="action-buttons">
|
| 114 |
+
{/* Reply Button */}
|
| 115 |
+
<div className="tooltip-container">
|
| 116 |
+
<button
|
| 117 |
+
className="action-button"
|
| 118 |
+
onClick={() => onReply && onReply(message)}
|
| 119 |
+
onMouseEnter={() => showTooltipWithDelay('reply')}
|
| 120 |
+
onMouseLeave={hideTooltip}
|
| 121 |
+
style={{
|
| 122 |
+
color: colors.color,
|
| 123 |
+
borderColor: colors.color + '40'
|
| 124 |
+
}}
|
| 125 |
+
>
|
| 126 |
+
<Reply size={14} />
|
| 127 |
+
</button>
|
| 128 |
+
{showTooltip === 'reply' && (
|
| 129 |
+
<div className="tooltip">Reply to this message</div>
|
| 130 |
+
)}
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
+
{/* Copy Button */}
|
| 134 |
+
<div className="tooltip-container">
|
| 135 |
+
<button
|
| 136 |
+
className="action-button"
|
| 137 |
+
onClick={() => handleCopy(message.id, message.content)}
|
| 138 |
+
onMouseEnter={() => showTooltipWithDelay('copy')}
|
| 139 |
+
onMouseLeave={hideTooltip}
|
| 140 |
+
style={{
|
| 141 |
+
color: isCopied ? '#10B981' : colors.color,
|
| 142 |
+
borderColor: isCopied ? '#10B98140' : colors.color + '40'
|
| 143 |
+
}}
|
| 144 |
+
>
|
| 145 |
+
{isCopied ? <Check size={14} /> : <Copy size={14} />}
|
| 146 |
+
</button>
|
| 147 |
+
{showTooltip === 'copy' && (
|
| 148 |
+
<div className="tooltip">
|
| 149 |
+
{isCopied ? 'Copied!' : 'Copy response'}
|
| 150 |
+
</div>
|
| 151 |
+
)}
|
| 152 |
+
</div>
|
| 153 |
+
|
| 154 |
+
{/* Expand/Elaborate Button */}
|
| 155 |
+
<div className="tooltip-container">
|
| 156 |
+
<button
|
| 157 |
+
className="action-button"
|
| 158 |
+
onClick={() => handleExpand(message.id, message.advisorId)}
|
| 159 |
+
onMouseEnter={() => showTooltipWithDelay('expand')}
|
| 160 |
+
onMouseLeave={hideTooltip}
|
| 161 |
+
style={{
|
| 162 |
+
color: colors.color,
|
| 163 |
+
borderColor: colors.color + '40'
|
| 164 |
+
}}
|
| 165 |
+
>
|
| 166 |
+
<Maximize2 size={14} />
|
| 167 |
+
</button>
|
| 168 |
+
{showTooltip === 'expand' && (
|
| 169 |
+
<div className="tooltip">Expand on this response</div>
|
| 170 |
+
)}
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
</div>
|
| 174 |
)}
|
| 175 |
</div>
|
phd-advisor-frontend/src/styles/components.css
CHANGED
|
@@ -554,12 +554,119 @@
|
|
| 554 |
margin: 0;
|
| 555 |
}
|
| 556 |
|
|
|
|
| 557 |
.message-actions {
|
| 558 |
margin-top: 12px;
|
| 559 |
padding-top: 12px;
|
| 560 |
border-top: 1px solid var(--border-tertiary);
|
| 561 |
}
|
| 562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
.reply-button {
|
| 564 |
display: flex;
|
| 565 |
align-items: center;
|
|
@@ -789,4 +896,19 @@
|
|
| 789 |
.features-grid {
|
| 790 |
grid-template-columns: 1fr;
|
| 791 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
}
|
|
|
|
| 554 |
margin: 0;
|
| 555 |
}
|
| 556 |
|
| 557 |
+
/* Updated Message Actions Styles */
|
| 558 |
.message-actions {
|
| 559 |
margin-top: 12px;
|
| 560 |
padding-top: 12px;
|
| 561 |
border-top: 1px solid var(--border-tertiary);
|
| 562 |
}
|
| 563 |
|
| 564 |
+
.action-buttons {
|
| 565 |
+
display: flex;
|
| 566 |
+
align-items: center;
|
| 567 |
+
gap: 8px;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.action-button {
|
| 571 |
+
display: flex;
|
| 572 |
+
align-items: center;
|
| 573 |
+
justify-content: center;
|
| 574 |
+
width: 32px;
|
| 575 |
+
height: 32px;
|
| 576 |
+
background: none;
|
| 577 |
+
border: 1px solid;
|
| 578 |
+
border-radius: 6px;
|
| 579 |
+
cursor: pointer;
|
| 580 |
+
transition: all 0.2s ease;
|
| 581 |
+
position: relative;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.action-button:hover {
|
| 585 |
+
background: var(--bg-secondary);
|
| 586 |
+
transform: translateY(-1px);
|
| 587 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
.action-button:active {
|
| 591 |
+
transform: translateY(0);
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
/* Tooltip Styles */
|
| 595 |
+
.tooltip-container {
|
| 596 |
+
position: relative;
|
| 597 |
+
display: inline-block;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
.tooltip {
|
| 601 |
+
position: absolute;
|
| 602 |
+
bottom: 100%;
|
| 603 |
+
left: 50%;
|
| 604 |
+
transform: translateX(-50%);
|
| 605 |
+
margin-bottom: 8px;
|
| 606 |
+
padding: 6px 10px;
|
| 607 |
+
background: var(--bg-primary);
|
| 608 |
+
color: var(--text-primary);
|
| 609 |
+
border: 1px solid var(--border-secondary);
|
| 610 |
+
border-radius: 6px;
|
| 611 |
+
font-size: 12px;
|
| 612 |
+
font-weight: 500;
|
| 613 |
+
white-space: nowrap;
|
| 614 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| 615 |
+
z-index: 1000;
|
| 616 |
+
opacity: 0;
|
| 617 |
+
animation: tooltipFadeIn 0.2s ease-out forwards;
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
.tooltip::after {
|
| 621 |
+
content: '';
|
| 622 |
+
position: absolute;
|
| 623 |
+
top: 100%;
|
| 624 |
+
left: 50%;
|
| 625 |
+
transform: translateX(-50%);
|
| 626 |
+
border: 4px solid transparent;
|
| 627 |
+
border-top-color: var(--border-secondary);
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
.tooltip::before {
|
| 631 |
+
content: '';
|
| 632 |
+
position: absolute;
|
| 633 |
+
top: 100%;
|
| 634 |
+
left: 50%;
|
| 635 |
+
transform: translateX(-50%);
|
| 636 |
+
margin-top: -1px;
|
| 637 |
+
border: 4px solid transparent;
|
| 638 |
+
border-top-color: var(--bg-primary);
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
/* Tooltip Animation */
|
| 642 |
+
@keyframes tooltipFadeIn {
|
| 643 |
+
from {
|
| 644 |
+
opacity: 0;
|
| 645 |
+
transform: translateX(-50%) translateY(4px);
|
| 646 |
+
}
|
| 647 |
+
to {
|
| 648 |
+
opacity: 1;
|
| 649 |
+
transform: translateX(-50%) translateY(0);
|
| 650 |
+
}
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
/* Dark theme tooltip adjustments */
|
| 654 |
+
[data-theme="dark"] .tooltip {
|
| 655 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
/* Focus states for accessibility */
|
| 659 |
+
.action-button:focus-visible {
|
| 660 |
+
outline: 2px solid var(--accent-primary);
|
| 661 |
+
outline-offset: 2px;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
/* Copy button success state */
|
| 665 |
+
.action-button.copied {
|
| 666 |
+
color: #10B981 !important;
|
| 667 |
+
border-color: #10B98140 !important;
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
.reply-button {
|
| 671 |
display: flex;
|
| 672 |
align-items: center;
|
|
|
|
| 896 |
.features-grid {
|
| 897 |
grid-template-columns: 1fr;
|
| 898 |
}
|
| 899 |
+
|
| 900 |
+
.action-buttons {
|
| 901 |
+
gap: 6px;
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
.action-button {
|
| 905 |
+
width: 28px;
|
| 906 |
+
height: 28px;
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
.tooltip {
|
| 910 |
+
font-size: 11px;
|
| 911 |
+
padding: 4px 8px;
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
}
|