fix(room): resolve mention IDs to display names when rendering messages

- Pass members/repos/aiConfigs lists to MessageContentWithMentions
- Add resolveName() that looks up ID → display name per mention type
- RoomMessageBubble now resolves user/repository/AI mention UIDs to real names
This commit is contained in:
ZhenYi 2026-04-18 00:10:12 +08:00
parent a9fc6f9937
commit 9b966789fd
2 changed files with 47 additions and 6 deletions

View File

@ -1,4 +1,6 @@
import { memo, useMemo } from 'react'; import type { ProjectRepositoryItem, RoomMemberResponse } from '@/client';
import type { RoomAiConfig } from '@/components/room/MentionPopover';
import { memo, useCallback, useMemo } from 'react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { parse, type Node, type MentionMentionType } from '@/lib/mention-ast'; import { parse, type Node, type MentionMentionType } from '@/lib/mention-ast';
@ -100,6 +102,12 @@ function extractFirstMentionName(text: string, type: MentionType): string | null
interface MessageContentWithMentionsProps { interface MessageContentWithMentionsProps {
content: string; content: string;
/** Members list for resolving user mention IDs to display names */
members?: RoomMemberResponse[];
/** Repository list for resolving repository mention IDs to display names */
repos?: ProjectRepositoryItem[];
/** AI configs for resolving AI mention IDs to display names */
aiConfigs?: RoomAiConfig[];
} }
const mentionStyles: Record<string, string> = { const mentionStyles: Record<string, string> = {
@ -109,14 +117,19 @@ const mentionStyles: Record<string, string> = {
notify: 'inline-flex items-center rounded bg-yellow-100/80 px-1.5 py-0.5 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300 font-medium cursor-pointer hover:bg-yellow-200 dark:hover:bg-yellow-900/60 transition-colors text-sm leading-5', notify: 'inline-flex items-center rounded bg-yellow-100/80 px-1.5 py-0.5 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300 font-medium cursor-pointer hover:bg-yellow-200 dark:hover:bg-yellow-900/60 transition-colors text-sm leading-5',
}; };
function renderNode(node: Node, index: number): React.ReactNode { function renderNode(
node: Node,
index: number,
resolveName: (type: string, id: string, label: string) => string,
): React.ReactNode {
if (node.type === 'text') { if (node.type === 'text') {
return <span key={index}>{node.text}</span>; return <span key={index}>{node.text}</span>;
} }
if (node.type === 'mention') { if (node.type === 'mention') {
const displayName = resolveName(node.mentionType, node.id, node.label);
return ( return (
<span key={index} className={mentionStyles[node.mentionType] ?? mentionStyles.user}> <span key={index} className={mentionStyles[node.mentionType] ?? mentionStyles.user}>
@{node.label} @{displayName}
</span> </span>
); );
} }
@ -140,6 +153,9 @@ function renderNode(node: Node, index: number): React.ReactNode {
*/ */
export const MessageContentWithMentions = memo(function MessageContentWithMentions({ export const MessageContentWithMentions = memo(function MessageContentWithMentions({
content, content,
members = [],
repos = [],
aiConfigs = [],
}: MessageContentWithMentionsProps) { }: MessageContentWithMentionsProps) {
const nodes = useMemo(() => { const nodes = useMemo(() => {
// Try the new AST parser first (handles <mention> and <ai> tags) // Try the new AST parser first (handles <mention> and <ai> tags)
@ -172,6 +188,26 @@ export const MessageContentWithMentions = memo(function MessageContentWithMentio
return parts; return parts;
}, [content]); }, [content]);
// Resolve ID → display name for each mention type
const resolveName = useCallback(
(type: string, id: string, label: string): string => {
if (type === 'user') {
const member = members.find((m) => m.user === id);
return member?.user_info?.username ?? member?.user ?? label;
}
if (type === 'repository') {
const repo = repos.find((r) => r.uid === id);
return repo?.repo_name ?? label;
}
if (type === 'ai') {
const cfg = aiConfigs.find((c) => c.model === id);
return cfg?.modelName ?? cfg?.model ?? label;
}
return label;
},
[members, repos, aiConfigs],
);
return ( return (
<div <div
className={cn( className={cn(
@ -181,7 +217,7 @@ export const MessageContentWithMentions = memo(function MessageContentWithMentio
'[&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:overflow-x-auto', '[&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:overflow-x-auto',
)} )}
> >
{nodes.map((node, i) => renderNode(node, i))} {nodes.map((node, i) => renderNode(node, i, resolveName))}
</div> </div>
); );
}); });

View File

@ -92,7 +92,7 @@ export const RoomMessageBubble = memo(function RoomMessageBubble({
const isStreaming = !!message.is_streaming; const isStreaming = !!message.is_streaming;
const isEdited = !!message.edited_at; const isEdited = !!message.edited_at;
const { user } = useUser(); const { user } = useUser();
const { wsClient, streamingMessages } = useRoom(); const { wsClient, streamingMessages, members, projectRepos, roomAiConfigs } = useRoom();
const isOwner = user?.uid === getSenderUserUid(message); const isOwner = user?.uid === getSenderUserUid(message);
const isRevoked = !!message.revoked; const isRevoked = !!message.revoked;
const isFailed = message.isOptimisticError === true; const isFailed = message.isOptimisticError === true;
@ -315,7 +315,12 @@ export const RoomMessageBubble = memo(function RoomMessageBubble({
)) ))
) : ( ) : (
<div className="max-w-full min-w-0 overflow-hidden whitespace-pre-wrap break-words"> <div className="max-w-full min-w-0 overflow-hidden whitespace-pre-wrap break-words">
<MessageContentWithMentions content={displayContent} /> <MessageContentWithMentions
content={displayContent}
members={members}
repos={projectRepos}
aiConfigs={roomAiConfigs}
/>
</div> </div>
)} )}