Spaces:
Running
Running
Update web/src/components/Message.tsx
Browse files- web/src/components/Message.tsx +20 -43
web/src/components/Message.tsx
CHANGED
|
@@ -33,19 +33,17 @@ import { apiFeedback } from "../lib/api";
|
|
| 33 |
interface MessageProps {
|
| 34 |
key?: React.Key;
|
| 35 |
message: MessageType;
|
| 36 |
-
showSenderInfo?: boolean;
|
| 37 |
-
isFirstGreeting?: boolean;
|
| 38 |
-
showNextButton?: boolean;
|
| 39 |
-
onNextQuestion?: () => void;
|
| 40 |
-
chatMode?: "ask" | "review" | "quiz";
|
| 41 |
|
| 42 |
-
// ✅ NEW: for feedback submission
|
| 43 |
currentUserId?: string;
|
| 44 |
docType?: string;
|
| 45 |
learningMode?: string;
|
| 46 |
}
|
| 47 |
|
| 48 |
-
// 反馈标签选项
|
| 49 |
const FEEDBACK_TAGS = {
|
| 50 |
"not-helpful": [
|
| 51 |
"Code was incorrect",
|
|
@@ -63,7 +61,6 @@ const FEEDBACK_TAGS = {
|
|
| 63 |
],
|
| 64 |
};
|
| 65 |
|
| 66 |
-
// ---- Markdown list normalization ----
|
| 67 |
function normalizeMarkdownLists(input: string) {
|
| 68 |
if (!input) return input;
|
| 69 |
|
|
@@ -79,10 +76,8 @@ export function Message({
|
|
| 79 |
showNextButton = false,
|
| 80 |
onNextQuestion,
|
| 81 |
chatMode = "ask",
|
| 82 |
-
|
| 83 |
-
// ✅ NEW
|
| 84 |
currentUserId,
|
| 85 |
-
docType = "
|
| 86 |
learningMode = "general",
|
| 87 |
}: MessageProps) {
|
| 88 |
const [feedback, setFeedback] = useState<"helpful" | "not-helpful" | null>(
|
|
@@ -90,7 +85,6 @@ export function Message({
|
|
| 90 |
);
|
| 91 |
const [copied, setCopied] = useState(false);
|
| 92 |
|
| 93 |
-
// ✅ References UI state
|
| 94 |
const [referencesOpen, setReferencesOpen] = useState(false);
|
| 95 |
|
| 96 |
const [showFeedbackArea, setShowFeedbackArea] = useState(false);
|
|
@@ -106,6 +100,9 @@ export function Message({
|
|
| 106 |
isFirstGreeting || message.id === "review-1" || message.id === "quiz-1";
|
| 107 |
const shouldShowActions = isUser ? true : !isWelcomeMessage;
|
| 108 |
|
|
|
|
|
|
|
|
|
|
| 109 |
const handleCopy = async () => {
|
| 110 |
await navigator.clipboard.writeText(message.content);
|
| 111 |
setCopied(true);
|
|
@@ -140,7 +137,6 @@ export function Message({
|
|
| 140 |
);
|
| 141 |
};
|
| 142 |
|
| 143 |
-
// ✅ REAL submit to backend -> LangSmith dataset
|
| 144 |
const handleFeedbackSubmit = async () => {
|
| 145 |
if (!currentUserId || !currentUserId.trim()) {
|
| 146 |
toast.error("Missing user_id; cannot submit feedback.");
|
|
@@ -151,7 +147,6 @@ export function Message({
|
|
| 151 |
return;
|
| 152 |
}
|
| 153 |
|
| 154 |
-
// UI uses "not-helpful" (dash), backend expects "not_helpful" (underscore)
|
| 155 |
const rating = feedbackType === "helpful" ? "helpful" : "not_helpful";
|
| 156 |
|
| 157 |
try {
|
|
@@ -160,7 +155,7 @@ export function Message({
|
|
| 160 |
rating,
|
| 161 |
assistant_message_id: message.id,
|
| 162 |
assistant_text: message.content,
|
| 163 |
-
user_text: "",
|
| 164 |
comment: feedbackText || "",
|
| 165 |
tags: selectedTags,
|
| 166 |
refs: message.references ?? [],
|
|
@@ -242,18 +237,12 @@ export function Message({
|
|
| 242 |
);
|
| 243 |
};
|
| 244 |
|
| 245 |
-
const refsArray = Array.isArray(message.references) ? message.references : [];
|
| 246 |
-
const refsCount = refsArray.length;
|
| 247 |
-
|
| 248 |
const attachments = (message as any).attachments as
|
| 249 |
| Array<{ name: string; kind: string; size: number; fileType?: string }>
|
| 250 |
| undefined;
|
| 251 |
|
| 252 |
const hasAttachments = !!(attachments && attachments.length);
|
| 253 |
|
| 254 |
-
// ✅ show references row for any assistant message (even 0), but not for user.
|
| 255 |
-
const shouldShowReferencesRow = !isUser;
|
| 256 |
-
|
| 257 |
return (
|
| 258 |
<div
|
| 259 |
className={`flex gap-2 ${
|
|
@@ -284,11 +273,7 @@ export function Message({
|
|
| 284 |
</div>
|
| 285 |
) : !isUser ? (
|
| 286 |
<div className="w-10 h-10 rounded-full overflow-hidden bg-white flex items-center justify-center flex-shrink-0">
|
| 287 |
-
<img
|
| 288 |
-
src={clareAvatar}
|
| 289 |
-
alt="Clare"
|
| 290 |
-
className="w-full h-full object-cover"
|
| 291 |
-
/>
|
| 292 |
</div>
|
| 293 |
) : null}
|
| 294 |
|
|
@@ -298,7 +283,6 @@ export function Message({
|
|
| 298 |
}`}
|
| 299 |
style={{ maxWidth: "min(770px, calc(100% - 2rem))" }}
|
| 300 |
>
|
| 301 |
-
{/* Sender name in group chat */}
|
| 302 |
{showSenderInfo && message.sender && (
|
| 303 |
<div className="flex items-center gap-2 px-1">
|
| 304 |
<span className="text-xs">{message.sender.name}</span>
|
|
@@ -313,20 +297,17 @@ export function Message({
|
|
| 313 |
{/* Bubble */}
|
| 314 |
<div
|
| 315 |
className={`
|
|
|
|
| 316 |
rounded-2xl px-4 py-3
|
| 317 |
${isUser && !showSenderInfo ? "bg-primary text-primary-foreground" : "bg-muted"}
|
| 318 |
`}
|
| 319 |
>
|
| 320 |
-
{/*
|
| 321 |
{hasAttachments && (
|
| 322 |
<div className="mb-3 flex flex-wrap gap-2">
|
| 323 |
{attachments!.map((a, idx) => {
|
| 324 |
const icon =
|
| 325 |
-
a.kind === "pdf"
|
| 326 |
-
? pdfIcon
|
| 327 |
-
: a.kind === "ppt"
|
| 328 |
-
? pptIcon
|
| 329 |
-
: otherIcon;
|
| 330 |
|
| 331 |
const label =
|
| 332 |
a.kind === "pdf"
|
|
@@ -376,9 +357,9 @@ export function Message({
|
|
| 376 |
{renderBubbleContent()}
|
| 377 |
</div>
|
| 378 |
|
| 379 |
-
{/* ✅ References
|
| 380 |
-
{
|
| 381 |
-
<div className="mt-
|
| 382 |
<Collapsible open={referencesOpen} onOpenChange={setReferencesOpen}>
|
| 383 |
<CollapsibleTrigger asChild>
|
| 384 |
<button
|
|
@@ -393,7 +374,7 @@ export function Message({
|
|
| 393 |
hover:bg-background
|
| 394 |
transition
|
| 395 |
"
|
| 396 |
-
title="References"
|
| 397 |
>
|
| 398 |
{referencesOpen ? (
|
| 399 |
<ChevronUp className="h-3 w-3" />
|
|
@@ -411,7 +392,7 @@ export function Message({
|
|
| 411 |
No references returned.
|
| 412 |
</div>
|
| 413 |
) : (
|
| 414 |
-
|
| 415 |
<div
|
| 416 |
key={index}
|
| 417 |
className="rounded-md border border-border bg-background/60 dark:bg-background/20 px-2 py-1 text-xs"
|
|
@@ -458,11 +439,7 @@ export function Message({
|
|
| 458 |
onClick={handleCopy}
|
| 459 |
title="Copy"
|
| 460 |
>
|
| 461 |
-
{copied ?
|
| 462 |
-
<Check className="h-4 w-4" />
|
| 463 |
-
) : (
|
| 464 |
-
<Copy className="h-4 w-4" />
|
| 465 |
-
)}
|
| 466 |
</Button>
|
| 467 |
|
| 468 |
{!isUser && (
|
|
|
|
| 33 |
interface MessageProps {
|
| 34 |
key?: React.Key;
|
| 35 |
message: MessageType;
|
| 36 |
+
showSenderInfo?: boolean;
|
| 37 |
+
isFirstGreeting?: boolean;
|
| 38 |
+
showNextButton?: boolean;
|
| 39 |
+
onNextQuestion?: () => void;
|
| 40 |
+
chatMode?: "ask" | "review" | "quiz";
|
| 41 |
|
|
|
|
| 42 |
currentUserId?: string;
|
| 43 |
docType?: string;
|
| 44 |
learningMode?: string;
|
| 45 |
}
|
| 46 |
|
|
|
|
| 47 |
const FEEDBACK_TAGS = {
|
| 48 |
"not-helpful": [
|
| 49 |
"Code was incorrect",
|
|
|
|
| 61 |
],
|
| 62 |
};
|
| 63 |
|
|
|
|
| 64 |
function normalizeMarkdownLists(input: string) {
|
| 65 |
if (!input) return input;
|
| 66 |
|
|
|
|
| 76 |
showNextButton = false,
|
| 77 |
onNextQuestion,
|
| 78 |
chatMode = "ask",
|
|
|
|
|
|
|
| 79 |
currentUserId,
|
| 80 |
+
docType = "All",
|
| 81 |
learningMode = "general",
|
| 82 |
}: MessageProps) {
|
| 83 |
const [feedback, setFeedback] = useState<"helpful" | "not-helpful" | null>(
|
|
|
|
| 85 |
);
|
| 86 |
const [copied, setCopied] = useState(false);
|
| 87 |
|
|
|
|
| 88 |
const [referencesOpen, setReferencesOpen] = useState(false);
|
| 89 |
|
| 90 |
const [showFeedbackArea, setShowFeedbackArea] = useState(false);
|
|
|
|
| 100 |
isFirstGreeting || message.id === "review-1" || message.id === "quiz-1";
|
| 101 |
const shouldShowActions = isUser ? true : !isWelcomeMessage;
|
| 102 |
|
| 103 |
+
const refsList = (message.references ?? []) as string[];
|
| 104 |
+
const refsCount = refsList.length;
|
| 105 |
+
|
| 106 |
const handleCopy = async () => {
|
| 107 |
await navigator.clipboard.writeText(message.content);
|
| 108 |
setCopied(true);
|
|
|
|
| 137 |
);
|
| 138 |
};
|
| 139 |
|
|
|
|
| 140 |
const handleFeedbackSubmit = async () => {
|
| 141 |
if (!currentUserId || !currentUserId.trim()) {
|
| 142 |
toast.error("Missing user_id; cannot submit feedback.");
|
|
|
|
| 147 |
return;
|
| 148 |
}
|
| 149 |
|
|
|
|
| 150 |
const rating = feedbackType === "helpful" ? "helpful" : "not_helpful";
|
| 151 |
|
| 152 |
try {
|
|
|
|
| 155 |
rating,
|
| 156 |
assistant_message_id: message.id,
|
| 157 |
assistant_text: message.content,
|
| 158 |
+
user_text: "",
|
| 159 |
comment: feedbackText || "",
|
| 160 |
tags: selectedTags,
|
| 161 |
refs: message.references ?? [],
|
|
|
|
| 237 |
);
|
| 238 |
};
|
| 239 |
|
|
|
|
|
|
|
|
|
|
| 240 |
const attachments = (message as any).attachments as
|
| 241 |
| Array<{ name: string; kind: string; size: number; fileType?: string }>
|
| 242 |
| undefined;
|
| 243 |
|
| 244 |
const hasAttachments = !!(attachments && attachments.length);
|
| 245 |
|
|
|
|
|
|
|
|
|
|
| 246 |
return (
|
| 247 |
<div
|
| 248 |
className={`flex gap-2 ${
|
|
|
|
| 273 |
</div>
|
| 274 |
) : !isUser ? (
|
| 275 |
<div className="w-10 h-10 rounded-full overflow-hidden bg-white flex items-center justify-center flex-shrink-0">
|
| 276 |
+
<img src={clareAvatar} alt="Clare" className="w-full h-full object-cover" />
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
</div>
|
| 278 |
) : null}
|
| 279 |
|
|
|
|
| 283 |
}`}
|
| 284 |
style={{ maxWidth: "min(770px, calc(100% - 2rem))" }}
|
| 285 |
>
|
|
|
|
| 286 |
{showSenderInfo && message.sender && (
|
| 287 |
<div className="flex items-center gap-2 px-1">
|
| 288 |
<span className="text-xs">{message.sender.name}</span>
|
|
|
|
| 297 |
{/* Bubble */}
|
| 298 |
<div
|
| 299 |
className={`
|
| 300 |
+
relative
|
| 301 |
rounded-2xl px-4 py-3
|
| 302 |
${isUser && !showSenderInfo ? "bg-primary text-primary-foreground" : "bg-muted"}
|
| 303 |
`}
|
| 304 |
>
|
| 305 |
+
{/* Attachments */}
|
| 306 |
{hasAttachments && (
|
| 307 |
<div className="mb-3 flex flex-wrap gap-2">
|
| 308 |
{attachments!.map((a, idx) => {
|
| 309 |
const icon =
|
| 310 |
+
a.kind === "pdf" ? pdfIcon : a.kind === "ppt" ? pptIcon : otherIcon;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
const label =
|
| 313 |
a.kind === "pdf"
|
|
|
|
| 357 |
{renderBubbleContent()}
|
| 358 |
</div>
|
| 359 |
|
| 360 |
+
{/* ✅ References OUTSIDE bubble, BETWEEN bubble and actions */}
|
| 361 |
+
{!isUser && (
|
| 362 |
+
<div className="mt-0.5">
|
| 363 |
<Collapsible open={referencesOpen} onOpenChange={setReferencesOpen}>
|
| 364 |
<CollapsibleTrigger asChild>
|
| 365 |
<button
|
|
|
|
| 374 |
hover:bg-background
|
| 375 |
transition
|
| 376 |
"
|
| 377 |
+
title={refsCount > 0 ? "References" : "No references returned."}
|
| 378 |
>
|
| 379 |
{referencesOpen ? (
|
| 380 |
<ChevronUp className="h-3 w-3" />
|
|
|
|
| 392 |
No references returned.
|
| 393 |
</div>
|
| 394 |
) : (
|
| 395 |
+
refsList.map((ref, index) => (
|
| 396 |
<div
|
| 397 |
key={index}
|
| 398 |
className="rounded-md border border-border bg-background/60 dark:bg-background/20 px-2 py-1 text-xs"
|
|
|
|
| 439 |
onClick={handleCopy}
|
| 440 |
title="Copy"
|
| 441 |
>
|
| 442 |
+
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
</Button>
|
| 444 |
|
| 445 |
{!isUser && (
|