feat(archive): rich metadata in semantic search — username/channel/guild context + guild filter
Archive payload now stores username, channel_id, guild_id, thread_id and the real message created_at (not embed time). Backend searchArray accepts an optional guildId and applies a Qdrant payload filter so results can be scoped to the guild being viewed. API/frontend expose the new fields and the semantic results card shows who said it, in which channel, and when — turning bare text blobs into contextual results. Old points fall back to analyzed_at and omit the new fields gracefully.
This commit is contained in:
@@ -129,6 +129,7 @@ export function MessagesView({
|
||||
const semantic = useSemanticSearch(
|
||||
query,
|
||||
query.trim().length >= 2 && semanticMode,
|
||||
guildId,
|
||||
);
|
||||
const activity = useMessageActivity(30);
|
||||
const edits = useRecentEdits(50, undefined, initialEdits);
|
||||
@@ -360,6 +361,16 @@ export function MessagesView({
|
||||
<span className="font-mono text-[10px] font-semibold text-signal">
|
||||
{(r.score * 100).toFixed(0)}% RELEVANCE
|
||||
</span>
|
||||
{r.username && (
|
||||
<span className="font-mono text-[10px] font-medium text-ink">
|
||||
{r.username}
|
||||
</span>
|
||||
)}
|
||||
{r.channel_id && (
|
||||
<span className="font-mono text-[10px] text-ink-muted">
|
||||
#{r.channel_id}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="ml-auto font-mono text-[10px] text-ink-muted"
|
||||
suppressHydrationWarning
|
||||
|
||||
@@ -212,13 +212,21 @@ export function useMessageSearch(query: string, enabled: boolean) {
|
||||
|
||||
// ── Semantic Search (public archive, Qdrant) ──────
|
||||
|
||||
export function useSemanticSearch(query: string, enabled: boolean) {
|
||||
export function useSemanticSearch(
|
||||
query: string,
|
||||
enabled: boolean,
|
||||
guildId?: string | null,
|
||||
) {
|
||||
return useSWR<SemanticSearchResult[]>(
|
||||
enabled && query.trim().length >= 2
|
||||
? ["semantic-search", query.trim()]
|
||||
? ["semantic-search", query.trim(), guildId ?? ""]
|
||||
: null,
|
||||
async () => {
|
||||
const res = await messagesApi.semanticSearch(query.trim(), 10);
|
||||
const res = await messagesApi.semanticSearch(
|
||||
query.trim(),
|
||||
10,
|
||||
guildId ?? undefined,
|
||||
);
|
||||
return res.results;
|
||||
},
|
||||
{ keepPreviousData: true },
|
||||
|
||||
@@ -70,8 +70,12 @@ export const messagesApi = {
|
||||
}>,
|
||||
|
||||
// Public semantic search over the persistent message archive (Qdrant).
|
||||
semanticSearch: (query: string, limit?: number) =>
|
||||
orpc.messages.semanticSearch({ query, limit }) as unknown as Promise<{
|
||||
semanticSearch: (query: string, limit?: number, guildId?: string) =>
|
||||
orpc.messages.semanticSearch({
|
||||
query,
|
||||
limit,
|
||||
...(guildId ? { guildId } : {}),
|
||||
}) as unknown as Promise<{
|
||||
results: SemanticSearchResult[];
|
||||
nextCursor: null;
|
||||
}>,
|
||||
|
||||
@@ -173,6 +173,10 @@ export interface SemanticSearchResult {
|
||||
content: string;
|
||||
score: number;
|
||||
created_at: number;
|
||||
username: string | null;
|
||||
channel_id: string | null;
|
||||
guild_id: string | null;
|
||||
thread_id: string | null;
|
||||
}
|
||||
|
||||
export interface MessageActivityBucket {
|
||||
|
||||
Reference in New Issue
Block a user