SarahXia0405 commited on
Commit
d9f042c
·
verified ·
1 Parent(s): 8c39f5c

Update web/src/components/Message.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/Message.tsx +52 -65
web/src/components/Message.tsx CHANGED
@@ -69,32 +69,6 @@ function normalizeMarkdownLists(input: string) {
69
  .replace(/(^|\n)(\s*)([-*+])\s*\n+\s+/g, "$1$2$3 ");
70
  }
71
 
72
- // ✅ References normalization: supports string[] OR {source_file, section}[]
73
- type RefObj = { source_file?: string | null; section?: string | null };
74
- type RefItem = string | RefObj;
75
-
76
- function normalizeRefs(raw: any): string[] {
77
- const arr: RefItem[] = Array.isArray(raw) ? raw : [];
78
- const out: string[] = [];
79
-
80
- for (const r of arr) {
81
- if (typeof r === "string") {
82
- const t = r.trim();
83
- if (t) out.push(t);
84
- continue;
85
- }
86
-
87
- if (r && typeof r === "object") {
88
- const file = String((r as RefObj).source_file ?? "").trim();
89
- const sec = String((r as RefObj).section ?? "").trim();
90
- const text = file && sec ? `${file} — ${sec}` : file || sec;
91
- if (text) out.push(text);
92
- }
93
- }
94
-
95
- return out;
96
- }
97
-
98
  export function Message({
99
  message,
100
  showSenderInfo = false,
@@ -112,7 +86,10 @@ export function Message({
112
  null
113
  );
114
  const [copied, setCopied] = useState(false);
 
 
115
  const [referencesOpen, setReferencesOpen] = useState(false);
 
116
  const [showFeedbackArea, setShowFeedbackArea] = useState(false);
117
  const [feedbackType, setFeedbackType] = useState<
118
  "helpful" | "not-helpful" | null
@@ -174,19 +151,16 @@ export function Message({
174
  // UI uses "not-helpful" (dash), backend expects "not_helpful" (underscore)
175
  const rating = feedbackType === "helpful" ? "helpful" : "not_helpful";
176
 
177
- // ✅ normalize refs to string[] to match backend schema
178
- const normalizedRefs = normalizeRefs((message as any).references);
179
-
180
  try {
181
  await apiFeedback({
182
  user_id: currentUserId,
183
  rating,
184
  assistant_message_id: message.id,
185
  assistant_text: message.content,
186
- user_text: "", // optional; you can wire last user msg later if you want
187
  comment: feedbackText || "",
188
  tags: selectedTags,
189
- refs: normalizedRefs,
190
  learning_mode: chatMode === "ask" ? learningMode : chatMode,
191
  doc_type: docType,
192
  timestamp_ms: Date.now(),
@@ -265,10 +239,7 @@ export function Message({
265
  );
266
  };
267
 
268
- // normalize references for UI display as well
269
- const displayRefs = useMemo(() => {
270
- return normalizeRefs((message as any).references);
271
- }, [message]);
272
 
273
  return (
274
  <div
@@ -322,14 +293,58 @@ export function Message({
322
  </div>
323
  )}
324
 
325
- {/* Bubble */}
326
  <div
327
  className={`
 
328
  rounded-2xl px-4 py-3
329
  ${isUser && !showSenderInfo ? "bg-primary text-primary-foreground" : "bg-muted"}
330
  `}
331
  >
332
  {renderBubbleContent()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  </div>
334
 
335
  {/* Next Question Button for Quiz Mode */}
@@ -351,30 +366,6 @@ export function Message({
351
  </div>
352
  )}
353
 
354
- {/* References (restored) */}
355
- {displayRefs.length > 0 && (
356
- <Collapsible open={referencesOpen} onOpenChange={setReferencesOpen}>
357
- <CollapsibleTrigger asChild>
358
- <Button variant="ghost" size="sm" className="gap-1 h-7 text-xs">
359
- {referencesOpen ? (
360
- <ChevronUp className="h-3 w-3" />
361
- ) : (
362
- <ChevronDown className="h-3 w-3" />
363
- )}
364
- {displayRefs.length}{" "}
365
- {displayRefs.length === 1 ? "reference" : "references"}
366
- </Button>
367
- </CollapsibleTrigger>
368
- <CollapsibleContent className="space-y-1 mt-1">
369
- {displayRefs.map((ref, index) => (
370
- <Badge key={index} variant="outline" className="text-xs">
371
- {ref}
372
- </Badge>
373
- ))}
374
- </CollapsibleContent>
375
- </Collapsible>
376
- )}
377
-
378
  {/* Message Actions */}
379
  {shouldShowActions && (
380
  <div className="flex items-center gap-1">
@@ -389,11 +380,7 @@ export function Message({
389
  onClick={handleCopy}
390
  title="Copy"
391
  >
392
- {copied ? (
393
- <Check className="h-4 w-4" />
394
- ) : (
395
- <Copy className="h-4 w-4" />
396
- )}
397
  </Button>
398
 
399
  {!isUser && (
 
69
  .replace(/(^|\n)(\s*)([-*+])\s*\n+\s+/g, "$1$2$3 ");
70
  }
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  export function Message({
73
  message,
74
  showSenderInfo = false,
 
86
  null
87
  );
88
  const [copied, setCopied] = useState(false);
89
+
90
+ // ✅ References UI state
91
  const [referencesOpen, setReferencesOpen] = useState(false);
92
+
93
  const [showFeedbackArea, setShowFeedbackArea] = useState(false);
94
  const [feedbackType, setFeedbackType] = useState<
95
  "helpful" | "not-helpful" | null
 
151
  // UI uses "not-helpful" (dash), backend expects "not_helpful" (underscore)
152
  const rating = feedbackType === "helpful" ? "helpful" : "not_helpful";
153
 
 
 
 
154
  try {
155
  await apiFeedback({
156
  user_id: currentUserId,
157
  rating,
158
  assistant_message_id: message.id,
159
  assistant_text: message.content,
160
+ user_text: "", // optional
161
  comment: feedbackText || "",
162
  tags: selectedTags,
163
+ refs: message.references ?? [],
164
  learning_mode: chatMode === "ask" ? learningMode : chatMode,
165
  doc_type: docType,
166
  timestamp_ms: Date.now(),
 
239
  );
240
  };
241
 
242
+ const hasRefs = !!(message.references && message.references.length > 0);
 
 
 
243
 
244
  return (
245
  <div
 
293
  </div>
294
  )}
295
 
296
+ {/* Bubble (make it relative so we can anchor the ref box to bottom-left) */}
297
  <div
298
  className={`
299
+ relative
300
  rounded-2xl px-4 py-3
301
  ${isUser && !showSenderInfo ? "bg-primary text-primary-foreground" : "bg-muted"}
302
  `}
303
  >
304
  {renderBubbleContent()}
305
+
306
+ {/* ✅ Restore "left-bottom small reference box" */}
307
+ {!isUser && hasRefs && (
308
+ <div className="mt-3">
309
+ <Collapsible open={referencesOpen} onOpenChange={setReferencesOpen}>
310
+ <CollapsibleTrigger asChild>
311
+ <button
312
+ type="button"
313
+ className="
314
+ inline-flex items-center gap-1
315
+ rounded-md border border-border
316
+ bg-background/80 dark:bg-background/30
317
+ px-2 py-1
318
+ text-xs text-foreground
319
+ shadow-sm
320
+ hover:bg-background
321
+ transition
322
+ "
323
+ title="References"
324
+ >
325
+ {referencesOpen ? (
326
+ <ChevronUp className="h-3 w-3" />
327
+ ) : (
328
+ <ChevronDown className="h-3 w-3" />
329
+ )}
330
+ <span className="font-medium">References</span>
331
+ <span className="opacity-70">({message.references!.length})</span>
332
+ </button>
333
+ </CollapsibleTrigger>
334
+
335
+ <CollapsibleContent className="mt-2 space-y-1">
336
+ {message.references!.map((ref, index) => (
337
+ <div
338
+ key={index}
339
+ className="rounded-md border border-border bg-background/60 dark:bg-background/20 px-2 py-1 text-xs"
340
+ >
341
+ {ref}
342
+ </div>
343
+ ))}
344
+ </CollapsibleContent>
345
+ </Collapsible>
346
+ </div>
347
+ )}
348
  </div>
349
 
350
  {/* Next Question Button for Quiz Mode */}
 
366
  </div>
367
  )}
368
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  {/* Message Actions */}
370
  {shouldShowActions && (
371
  <div className="flex items-center gap-1">
 
380
  onClick={handleCopy}
381
  title="Copy"
382
  >
383
+ {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
 
 
 
 
384
  </Button>
385
 
386
  {!isUser && (