feat(messages): show channel and thread location context in message cards
This commit is contained in:
@@ -12,12 +12,19 @@ export interface MessageMetadata {
|
||||
stickers?: Array<{ name?: string; url?: string }>;
|
||||
attachments?: Array<{ name: string; url: string; contentType?: string }>;
|
||||
embeds?: Array<{ title?: string; image?: string; thumbnail?: string }>;
|
||||
channel?: {
|
||||
channelId: string;
|
||||
channelName?: string;
|
||||
threadId?: string;
|
||||
threadName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMetadata(value: string | null): MessageMetadata {
|
||||
if (!value) return {};
|
||||
try {
|
||||
return JSON.parse(value) as MessageMetadata;
|
||||
const parsed = JSON.parse(value) as MessageMetadata;
|
||||
return parsed;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
Hash,
|
||||
Image as ImageIcon,
|
||||
Pencil,
|
||||
RotateCw,
|
||||
@@ -350,6 +351,20 @@ function MessageRow({
|
||||
export function MessageCard({ messages, onReanalyze }: MessageCardProps) {
|
||||
const firstMsg = messages[0];
|
||||
const hasMultiple = messages.length > 1;
|
||||
const meta = useMemo(
|
||||
() => parseMetadata(firstMsg.metadata),
|
||||
[firstMsg.metadata],
|
||||
);
|
||||
const channelMeta = meta.channel;
|
||||
const locationLabel = useMemo(() => {
|
||||
if (channelMeta?.threadName) {
|
||||
return `# ${channelMeta.channelName || "unknown"} › ${channelMeta.threadName}`;
|
||||
}
|
||||
if (channelMeta?.channelName) {
|
||||
return `# ${channelMeta.channelName}`;
|
||||
}
|
||||
return null;
|
||||
}, [channelMeta]);
|
||||
|
||||
return (
|
||||
<article
|
||||
@@ -369,11 +384,17 @@ export function MessageCard({ messages, onReanalyze }: MessageCardProps) {
|
||||
/>
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Group header: username + timestamp of first message */}
|
||||
{/* Group header: username + location + timestamp */}
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="font-semibold text-sm text-foreground">
|
||||
{firstMsg.username || firstMsg.user_id}
|
||||
</span>
|
||||
{locationLabel && (
|
||||
<span className="flex items-center gap-1 text-[11px] text-muted-foreground/50 bg-muted/50 px-1.5 py-0.5 rounded-full">
|
||||
<Hash className="h-2.5 w-2.5" />
|
||||
{locationLabel}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="text-[11px] text-muted-foreground/60"
|
||||
title={new Date(firstMsg.created_at).toLocaleString()}
|
||||
|
||||
@@ -12,10 +12,6 @@ import {
|
||||
CardTitle,
|
||||
Input,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
@@ -134,20 +130,16 @@ export function MessagesPanel({
|
||||
<Hash className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||
<Select
|
||||
value={selectedChannel || ""}
|
||||
onValueChange={(val) => onChannelChange?.(val)}
|
||||
>
|
||||
<SelectTrigger className="w-full max-w-xs h-9 text-sm">
|
||||
<SelectValue placeholder="All channels" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__all">All channels</SelectItem>
|
||||
{textChannels.map((ch) => (
|
||||
<SelectItem key={ch.id} value={ch.id}>
|
||||
# {ch.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
onChange={(e) => onChannelChange?.(e.target.value === "__all" ? "" : e.target.value)}
|
||||
placeholder="All channels"
|
||||
options={[
|
||||
{ value: "__all", label: "All channels" },
|
||||
...textChannels.map((ch) => ({
|
||||
value: ch.id,
|
||||
label: `# ${ch.name}`,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user