SarahXia0405 commited on
Commit
60f8c0d
·
verified ·
1 Parent(s): 3474589

Update web/src/components/Message.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/Message.tsx +90 -28
web/src/components/Message.tsx CHANGED
@@ -1,10 +1,10 @@
1
  import React, { useState } from 'react';
2
  import { Button } from './ui/button';
3
- import {
4
- Copy,
5
- ThumbsUp,
6
- ThumbsDown,
7
- ChevronDown,
8
  ChevronUp,
9
  Check,
10
  Bot
@@ -13,23 +13,51 @@ import { Badge } from './ui/badge';
13
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
14
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog';
15
  import { Textarea } from './ui/textarea';
16
- import { Label } from './ui/label';
17
- import type { Message as MessageType } from '../App';
18
  import { toast } from 'sonner@2.0.3';
19
 
20
  interface MessageProps {
21
  message: MessageType;
22
  showSenderInfo?: boolean; // For group chat mode
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
 
25
- export function Message({ message, showSenderInfo = false }: MessageProps) {
26
- const [feedback, setFeedback] = useState<'helpful' | 'not-helpful' | null>(null);
 
 
 
 
 
 
 
27
  const [copied, setCopied] = useState(false);
28
  const [referencesOpen, setReferencesOpen] = useState(false);
29
  const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
30
- const [feedbackType, setFeedbackType] = useState<'helpful' | 'not-helpful' | null>(null);
31
  const [feedbackText, setFeedbackText] = useState('');
32
-
 
33
  const isUser = message.role === 'user';
34
 
35
  const handleCopy = async () => {
@@ -39,12 +67,12 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
39
  setTimeout(() => setCopied(false), 2000);
40
  };
41
 
42
- const handleFeedback = (type: 'helpful' | 'not-helpful') => {
43
- setFeedback(type);
44
- toast.success(`Thanks for your feedback!`);
45
- };
46
-
47
- const handleFeedbackDialogOpen = (type: 'helpful' | 'not-helpful') => {
48
  setFeedbackType(type);
49
  setShowFeedbackDialog(true);
50
  };
@@ -55,12 +83,40 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
55
  setFeedbackText('');
56
  };
57
 
58
- const handleFeedbackDialogSubmit = () => {
59
- if (feedbackType) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  setFeedback(feedbackType);
61
- toast.success(`Thanks for your feedback!`);
 
 
 
 
 
 
62
  }
63
- handleFeedbackDialogClose();
64
  };
65
 
66
  return (
@@ -68,8 +124,8 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
68
  {/* Avatar */}
69
  {showSenderInfo && message.sender ? (
70
  <div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
71
- message.sender.isAI
72
- ? 'bg-gradient-to-br from-purple-500 to-blue-500'
73
  : 'bg-muted'
74
  }`}>
75
  {message.sender.isAI ? (
@@ -101,7 +157,7 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
101
  className={`
102
  rounded-2xl px-4 py-3
103
  ${isUser && !showSenderInfo
104
- ? 'bg-primary text-primary-foreground'
105
  : 'bg-muted'
106
  }
107
  `}
@@ -160,18 +216,20 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
160
  size="sm"
161
  className={`h-7 gap-1 ${feedback === 'helpful' ? 'text-green-600' : ''}`}
162
  onClick={() => handleFeedbackDialogOpen('helpful')}
 
163
  >
164
  <ThumbsUp className="h-3 w-3" />
165
- <span className="text-xs">Helpful</span>
166
  </Button>
167
  <Button
168
  variant="ghost"
169
  size="sm"
170
  className={`h-7 gap-1 ${feedback === 'not-helpful' ? 'text-red-600' : ''}`}
171
  onClick={() => handleFeedbackDialogOpen('not-helpful')}
 
172
  >
173
  <ThumbsDown className="h-3 w-3" />
174
- <span className="text-xs">Not helpful</span>
175
  </Button>
176
  </>
177
  )}
@@ -190,7 +248,9 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
190
  <DialogHeader>
191
  <DialogTitle>Provide Feedback</DialogTitle>
192
  <DialogDescription>
193
- {feedbackType === 'helpful' ? 'Tell us why you found this message helpful.' : 'Tell us why you found this message not helpful.'}
 
 
194
  </DialogDescription>
195
  </DialogHeader>
196
  <Textarea
@@ -204,12 +264,14 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
204
  type="button"
205
  variant="outline"
206
  onClick={handleFeedbackDialogClose}
 
207
  >
208
  Cancel
209
  </Button>
210
  <Button
211
  type="button"
212
  onClick={handleFeedbackDialogSubmit}
 
213
  >
214
  Submit
215
  </Button>
@@ -218,4 +280,4 @@ export function Message({ message, showSenderInfo = false }: MessageProps) {
218
  </Dialog>
219
  </div>
220
  );
221
- }
 
1
  import React, { useState } from 'react';
2
  import { Button } from './ui/button';
3
+ import {
4
+ Copy,
5
+ ThumbsUp,
6
+ ThumbsDown,
7
+ ChevronDown,
8
  ChevronUp,
9
  Check,
10
  Bot
 
13
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/collapsible';
14
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from './ui/dialog';
15
  import { Textarea } from './ui/textarea';
16
+ import type { Message as MessageType, LearningMode } from '../App';
 
17
  import { toast } from 'sonner@2.0.3';
18
 
19
  interface MessageProps {
20
  message: MessageType;
21
  showSenderInfo?: boolean; // For group chat mode
22
+
23
+ // ✅ 新增:写入 /api/feedback 需要的信息
24
+ userId: string;
25
+ learningMode: LearningMode;
26
+ docType?: string;
27
+ lastUserText?: string; // 可选:如果你想把本轮 user 的问题也一起上报
28
+ }
29
+
30
+ type UIFeedback = 'helpful' | 'not-helpful';
31
+
32
+ async function postJson<T>(url: string, payload: any): Promise<T> {
33
+ const res = await fetch(url, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify(payload),
37
+ });
38
+ if (!res.ok) {
39
+ const text = await res.text().catch(() => '');
40
+ throw new Error(`HTTP ${res.status}: ${text || res.statusText}`);
41
+ }
42
+ return (await res.json()) as T;
43
  }
44
 
45
+ export function Message({
46
+ message,
47
+ showSenderInfo = false,
48
+ userId,
49
+ learningMode,
50
+ docType = '',
51
+ lastUserText = '',
52
+ }: MessageProps) {
53
+ const [feedback, setFeedback] = useState<UIFeedback | null>(null);
54
  const [copied, setCopied] = useState(false);
55
  const [referencesOpen, setReferencesOpen] = useState(false);
56
  const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
57
+ const [feedbackType, setFeedbackType] = useState<UIFeedback | null>(null);
58
  const [feedbackText, setFeedbackText] = useState('');
59
+ const [submitting, setSubmitting] = useState(false);
60
+
61
  const isUser = message.role === 'user';
62
 
63
  const handleCopy = async () => {
 
67
  setTimeout(() => setCopied(false), 2000);
68
  };
69
 
70
+ const handleFeedbackDialogOpen = (type: UIFeedback) => {
71
+ // ✅ 已经提交过就不再允许重复写入
72
+ if (feedback) {
73
+ toast.message('Feedback already recorded for this message.');
74
+ return;
75
+ }
76
  setFeedbackType(type);
77
  setShowFeedbackDialog(true);
78
  };
 
83
  setFeedbackText('');
84
  };
85
 
86
+ const handleFeedbackDialogSubmit = async () => {
87
+ if (!feedbackType) return;
88
+
89
+ // UI: not-helpful -> API: not_helpful
90
+ const apiRating = feedbackType === 'helpful' ? 'helpful' : 'not_helpful';
91
+
92
+ try {
93
+ setSubmitting(true);
94
+
95
+ const payload = {
96
+ user_id: (userId || '').trim(),
97
+ rating: apiRating,
98
+ assistant_message_id: message.id,
99
+ assistant_text: message.content || '',
100
+ user_text: lastUserText || '',
101
+ comment: feedbackText.trim() || '',
102
+ refs: message.references || [],
103
+ learning_mode: learningMode,
104
+ doc_type: docType,
105
+ timestamp_ms: Date.now(),
106
+ };
107
+
108
+ const resp = await postJson<{ ok: boolean }>('/api/feedback', payload);
109
+ if (!resp?.ok) throw new Error('ok=false');
110
+
111
  setFeedback(feedbackType);
112
+ toast.success('Thanks for your feedback!');
113
+ handleFeedbackDialogClose();
114
+ } catch (e: any) {
115
+ console.error(e);
116
+ toast.error(`Feedback failed: ${e?.message || 'unknown error'}`);
117
+ } finally {
118
+ setSubmitting(false);
119
  }
 
120
  };
121
 
122
  return (
 
124
  {/* Avatar */}
125
  {showSenderInfo && message.sender ? (
126
  <div className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
127
+ message.sender.isAI
128
+ ? 'bg-gradient-to-br from-purple-500 to-blue-500'
129
  : 'bg-muted'
130
  }`}>
131
  {message.sender.isAI ? (
 
157
  className={`
158
  rounded-2xl px-4 py-3
159
  ${isUser && !showSenderInfo
160
+ ? 'bg-primary text-primary-foreground'
161
  : 'bg-muted'
162
  }
163
  `}
 
216
  size="sm"
217
  className={`h-7 gap-1 ${feedback === 'helpful' ? 'text-green-600' : ''}`}
218
  onClick={() => handleFeedbackDialogOpen('helpful')}
219
+ disabled={!!feedback}
220
  >
221
  <ThumbsUp className="h-3 w-3" />
222
+ <span className="text-xs">{feedback === 'helpful' ? 'Helpful (sent)' : 'Helpful'}</span>
223
  </Button>
224
  <Button
225
  variant="ghost"
226
  size="sm"
227
  className={`h-7 gap-1 ${feedback === 'not-helpful' ? 'text-red-600' : ''}`}
228
  onClick={() => handleFeedbackDialogOpen('not-helpful')}
229
+ disabled={!!feedback}
230
  >
231
  <ThumbsDown className="h-3 w-3" />
232
+ <span className="text-xs">{feedback === 'not-helpful' ? 'Not helpful (sent)' : 'Not helpful'}</span>
233
  </Button>
234
  </>
235
  )}
 
248
  <DialogHeader>
249
  <DialogTitle>Provide Feedback</DialogTitle>
250
  <DialogDescription>
251
+ {feedbackType === 'helpful'
252
+ ? 'Tell us why you found this message helpful.'
253
+ : 'Tell us why you found this message not helpful.'}
254
  </DialogDescription>
255
  </DialogHeader>
256
  <Textarea
 
264
  type="button"
265
  variant="outline"
266
  onClick={handleFeedbackDialogClose}
267
+ disabled={submitting}
268
  >
269
  Cancel
270
  </Button>
271
  <Button
272
  type="button"
273
  onClick={handleFeedbackDialogSubmit}
274
+ disabled={submitting}
275
  >
276
  Submit
277
  </Button>
 
280
  </Dialog>
281
  </div>
282
  );
283
+ }