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:
parent
a9fc6f9937
commit
9b966789fd
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user