feat(archive): show real channel/thread names in semantic search UI
Gateway archive embedder now parses metadata.channel.{channelName,threadName}
from each message and stores channel_name/thread_name in the Qdrant payload.
Backend exposes them; the semantic results card renders the thread name (or
channel name) instead of a raw #snowflake, with the ID as a last-resort
fallback for legacy points. Matches the message feed's channel-label logic
(getMessageChannelLabel).
This commit is contained in:
@@ -132,6 +132,8 @@ function mapSearchHit(hit: {
|
||||
channel_id?: string;
|
||||
guild_id?: string;
|
||||
thread_id?: string | null;
|
||||
channel_name?: string | null;
|
||||
thread_name?: string | null;
|
||||
created_at?: number;
|
||||
};
|
||||
}) {
|
||||
@@ -146,6 +148,8 @@ function mapSearchHit(hit: {
|
||||
channel_id: hit.payload.channel_id ?? null,
|
||||
guild_id: hit.payload.guild_id ?? null,
|
||||
thread_id: hit.payload.thread_id ?? null,
|
||||
channel_name: hit.payload.channel_name ?? null,
|
||||
thread_name: hit.payload.thread_name ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface ArchiveHit {
|
||||
channel_id?: string;
|
||||
guild_id?: string;
|
||||
thread_id?: string | null;
|
||||
channel_name?: string | null;
|
||||
thread_name?: string | null;
|
||||
created_at?: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface QdrantVerdictPayload {
|
||||
channel_id?: string;
|
||||
guild_id?: string;
|
||||
thread_id?: string | null;
|
||||
channel_name?: string | null;
|
||||
thread_name?: string | null;
|
||||
created_at?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,42 @@ export interface ArchiveMessage {
|
||||
guild_id: string;
|
||||
thread_id: string | null;
|
||||
created_at: number;
|
||||
/** JSON string of RichMessageMetadata (parsed for channel/thread names). */
|
||||
metadata?: string | null;
|
||||
/** True when the message came from an age-restricted (NSFW) channel. NSFW
|
||||
* content is deliberately NOT embedded into the public archive so it can't
|
||||
* be found via public semantic search. Defaults to false. */
|
||||
isAgeRestricted?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a human-readable channel label from the message's metadata JSON.
|
||||
* The gateway captures `metadata.channel.{channelName,threadName}` per message;
|
||||
* prefer the thread name (thread messages read better by their thread title),
|
||||
* then the channel name. Returns null when unavailable (old messages without
|
||||
* the metadata field, or malformed JSON).
|
||||
*/
|
||||
export function extractChannelLabel(metadata: string | null | undefined): {
|
||||
channel_name: string | null;
|
||||
thread_name: string | null;
|
||||
} {
|
||||
if (!metadata) return { channel_name: null, thread_name: null };
|
||||
try {
|
||||
const m = JSON.parse(metadata) as {
|
||||
channel?: {
|
||||
channelName?: string | null;
|
||||
threadName?: string | null;
|
||||
};
|
||||
};
|
||||
return {
|
||||
channel_name: m?.channel?.channelName ?? null,
|
||||
thread_name: m?.channel?.threadName ?? null,
|
||||
};
|
||||
} catch {
|
||||
return { channel_name: null, thread_name: null };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire-and-forget: embed a captured message and upsert it into the persistent
|
||||
* archive collection so the public web can semantic-search the corpus.
|
||||
@@ -52,6 +82,9 @@ export function archiveMessageEmbedded(message: ArchiveMessage): void {
|
||||
if (!normalized) return; // nothing meaningful left after cleanup
|
||||
const vector = await embedText(normalized);
|
||||
if (!vector) return;
|
||||
const { channel_name, thread_name } = extractChannelLabel(
|
||||
message.metadata,
|
||||
);
|
||||
const ok = await upsertQdrantPointV2(
|
||||
ARCHIVE_COLLECTION,
|
||||
qdrantPointId(`archive:${message.id}`),
|
||||
@@ -65,6 +98,8 @@ export function archiveMessageEmbedded(message: ArchiveMessage): void {
|
||||
channel_id: message.channel_id ?? "",
|
||||
guild_id: message.guild_id ?? "",
|
||||
thread_id: message.thread_id ?? null,
|
||||
channel_name: channel_name ?? null,
|
||||
thread_name: thread_name ?? null,
|
||||
created_at: message.created_at ?? Date.now(),
|
||||
analyzed_at: Date.now(),
|
||||
// 5-year persistent window (archive is NOT a TTL cache).
|
||||
|
||||
@@ -366,8 +366,18 @@ export function MessagesView({
|
||||
{r.username}
|
||||
</span>
|
||||
)}
|
||||
{r.channel_id && (
|
||||
{r.thread_name && (
|
||||
<span className="font-mono text-[10px] text-ink-muted">
|
||||
▶ {r.thread_name}
|
||||
</span>
|
||||
)}
|
||||
{!r.thread_name && r.channel_name && (
|
||||
<span className="font-mono text-[10px] text-ink-muted">
|
||||
#{r.channel_name}
|
||||
</span>
|
||||
)}
|
||||
{!r.thread_name && !r.channel_name && r.channel_id && (
|
||||
<span className="font-mono text-[10px] text-ink-faint">
|
||||
#{r.channel_id}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -177,6 +177,8 @@ export interface SemanticSearchResult {
|
||||
channel_id: string | null;
|
||||
guild_id: string | null;
|
||||
thread_id: string | null;
|
||||
channel_name: string | null;
|
||||
thread_name: string | null;
|
||||
}
|
||||
|
||||
export interface MessageActivityBucket {
|
||||
|
||||
Reference in New Issue
Block a user