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 }>;
|
stickers?: Array<{ name?: string; url?: string }>;
|
||||||
attachments?: Array<{ name: string; url: string; contentType?: string }>;
|
attachments?: Array<{ name: string; url: string; contentType?: string }>;
|
||||||
embeds?: Array<{ title?: string; image?: string; thumbnail?: 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 {
|
export function parseMetadata(value: string | null): MessageMetadata {
|
||||||
if (!value) return {};
|
if (!value) return {};
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value) as MessageMetadata;
|
const parsed = JSON.parse(value) as MessageMetadata;
|
||||||
|
return parsed;
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
|
Hash,
|
||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
Pencil,
|
Pencil,
|
||||||
RotateCw,
|
RotateCw,
|
||||||
@@ -350,6 +351,20 @@ function MessageRow({
|
|||||||
export function MessageCard({ messages, onReanalyze }: MessageCardProps) {
|
export function MessageCard({ messages, onReanalyze }: MessageCardProps) {
|
||||||
const firstMsg = messages[0];
|
const firstMsg = messages[0];
|
||||||
const hasMultiple = messages.length > 1;
|
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 (
|
return (
|
||||||
<article
|
<article
|
||||||
@@ -369,11 +384,17 @@ export function MessageCard({ messages, onReanalyze }: MessageCardProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1">
|
<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">
|
<div className="flex items-baseline gap-2 mb-2">
|
||||||
<span className="font-semibold text-sm text-foreground">
|
<span className="font-semibold text-sm text-foreground">
|
||||||
{firstMsg.username || firstMsg.user_id}
|
{firstMsg.username || firstMsg.user_id}
|
||||||
</span>
|
</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
|
<span
|
||||||
className="text-[11px] text-muted-foreground/60"
|
className="text-[11px] text-muted-foreground/60"
|
||||||
title={new Date(firstMsg.created_at).toLocaleString()}
|
title={new Date(firstMsg.created_at).toLocaleString()}
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsContent,
|
TabsContent,
|
||||||
TabsList,
|
TabsList,
|
||||||
@@ -134,20 +130,16 @@ export function MessagesPanel({
|
|||||||
<Hash className="h-4 w-4 text-muted-foreground shrink-0" />
|
<Hash className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||||
<Select
|
<Select
|
||||||
value={selectedChannel || ""}
|
value={selectedChannel || ""}
|
||||||
onValueChange={(val) => onChannelChange?.(val)}
|
onChange={(e) => onChannelChange?.(e.target.value === "__all" ? "" : e.target.value)}
|
||||||
>
|
placeholder="All channels"
|
||||||
<SelectTrigger className="w-full max-w-xs h-9 text-sm">
|
options={[
|
||||||
<SelectValue placeholder="All channels" />
|
{ value: "__all", label: "All channels" },
|
||||||
</SelectTrigger>
|
...textChannels.map((ch) => ({
|
||||||
<SelectContent>
|
value: ch.id,
|
||||||
<SelectItem value="__all">All channels</SelectItem>
|
label: `# ${ch.name}`,
|
||||||
{textChannels.map((ch) => (
|
})),
|
||||||
<SelectItem key={ch.id} value={ch.id}>
|
]}
|
||||||
# {ch.name}
|
/>
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user