|
|
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
|
|
|
import { format } from 'date-fns';
|
|
|
import { zhCN } from 'date-fns/locale';
|
|
|
import { toast } from 'sonner';
|
|
|
-import { Search, Trash2, Eye, MessageSquare, Image, Settings } from 'lucide-react';
|
|
|
+import { Search, Trash2, Eye, MessageSquare, Image, Settings, FileImage } from 'lucide-react';
|
|
|
|
|
|
import { chatMessageClient } from '@/client/api';
|
|
|
import type { InferResponseType, InferRequestType } from 'hono/client';
|
|
|
@@ -20,6 +20,32 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
|
|
|
|
|
|
import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
|
|
|
|
|
|
+// 图片预览组件
|
|
|
+const ImagePreview = ({ src, alt, className = '' }: { src: string; alt: string; className?: string }) => {
|
|
|
+ const [error, setError] = useState(false);
|
|
|
+
|
|
|
+ if (error) {
|
|
|
+ return (
|
|
|
+ <div className={`flex items-center justify-center bg-muted rounded-md ${className}`}>
|
|
|
+ <div className="text-center p-4">
|
|
|
+ <FileImage className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
|
|
|
+ <p className="text-xs text-muted-foreground">图片加载失败</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <img
|
|
|
+ src={src}
|
|
|
+ alt={alt}
|
|
|
+ className={`object-cover rounded-md ${className}`}
|
|
|
+ onError={() => setError(true)}
|
|
|
+ loading="lazy"
|
|
|
+ />
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
type ChatMessageListResponse = InferResponseType<typeof chatMessageClient.$get, 200>;
|
|
|
type ChatMessageDetailResponse = InferResponseType<typeof chatMessageClient[':id']['$get'], 200>;
|
|
|
type CreateChatMessageRequest = InferRequestType<typeof chatMessageClient.$post>['json'];
|
|
|
@@ -204,10 +230,23 @@ export const ChatMessagesPage = () => {
|
|
|
{messageTypeMap[message.type]?.label || message.type}
|
|
|
</Badge>
|
|
|
</TableCell>
|
|
|
- <TableCell title={message.content}>
|
|
|
- <div className="max-w-xs truncate">
|
|
|
- {truncateText(message.content)}
|
|
|
- </div>
|
|
|
+ <TableCell>
|
|
|
+ {message.type === 'image' && message.file ? (
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <div className="w-8 h-8 rounded overflow-hidden">
|
|
|
+ <ImagePreview
|
|
|
+ src={message.file.fullUrl}
|
|
|
+ alt="图片消息"
|
|
|
+ className="w-full h-full"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <span className="text-sm text-muted-foreground">图片消息</span>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div title={message.content} className="max-w-xs truncate">
|
|
|
+ {truncateText(message.content)}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</TableCell>
|
|
|
<TableCell>
|
|
|
<div className="text-sm">
|
|
|
@@ -327,9 +366,24 @@ export const ChatMessagesPage = () => {
|
|
|
<label className="text-sm font-medium text-muted-foreground">消息内容</label>
|
|
|
<div className="mt-1 p-3 bg-muted rounded-md">
|
|
|
{selectedMessage.type === 'image' ? (
|
|
|
- <div className="text-sm text-muted-foreground">
|
|
|
- 图片消息(内容可能包含图片URL或文件ID)
|
|
|
- </div>
|
|
|
+ selectedMessage.file ? (
|
|
|
+ <div className="space-y-3">
|
|
|
+ <div className="flex justify-center">
|
|
|
+ <ImagePreview
|
|
|
+ src={selectedMessage.file.fullUrl}
|
|
|
+ alt="图片消息"
|
|
|
+ className="max-w-full max-h-64 rounded-md"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="text-sm text-muted-foreground text-center">
|
|
|
+ 图片消息预览
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
+ 图片消息(文件信息缺失)
|
|
|
+ </div>
|
|
|
+ )
|
|
|
) : (
|
|
|
<p className="text-sm whitespace-pre-wrap break-words">
|
|
|
{selectedMessage.content}
|
|
|
@@ -338,13 +392,44 @@ export const ChatMessagesPage = () => {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- {selectedMessage.type === 'image' && (
|
|
|
+ {selectedMessage.type === 'image' && selectedMessage.file && (
|
|
|
<div>
|
|
|
<label className="text-sm font-medium text-muted-foreground">图片信息</label>
|
|
|
- <div className="mt-1 p-3 bg-muted rounded-md">
|
|
|
- <p className="text-sm text-muted-foreground">
|
|
|
- 这是一个图片消息,可能需要特殊处理来显示图片内容
|
|
|
- </p>
|
|
|
+ <div className="mt-1 p-3 bg-muted rounded-md space-y-2">
|
|
|
+ <div className="grid grid-cols-2 gap-4 text-sm">
|
|
|
+ <div>
|
|
|
+ <span className="text-muted-foreground">文件名:</span>
|
|
|
+ <span>{selectedMessage.file.name}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span className="text-muted-foreground">文件类型:</span>
|
|
|
+ <span>{selectedMessage.file.type || '未知'}</span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span className="text-muted-foreground">文件大小:</span>
|
|
|
+ <span>
|
|
|
+ {selectedMessage.file.size
|
|
|
+ ? `${(selectedMessage.file.size / 1024).toFixed(1)} KB`
|
|
|
+ : '未知'
|
|
|
+ }
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <span className="text-muted-foreground">文件ID:</span>
|
|
|
+ <span>{selectedMessage.file.id}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="text-sm">
|
|
|
+ <span className="text-muted-foreground">文件URL:</span>
|
|
|
+ <a
|
|
|
+ href={selectedMessage.file.fullUrl}
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer"
|
|
|
+ className="text-blue-600 hover:underline break-all"
|
|
|
+ >
|
|
|
+ {selectedMessage.file.fullUrl}
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|