feat(room): render AI mentions as 🤖 button with click-to-insert action
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions

AI mentions now render as "🤖 AI: name" buttons instead of plain text.
Clicking a 🤖 button inserts the @ai:mention into the message input at
the cursor position, enabling users to summon that AI model into the conversation.

Implementation:
- MessageMentions renders AI mentions as styled buttons with 🤖 icon
- Click dispatches 'mention-click' CustomEvent on document
- ChatInputArea listens and inserts the mention HTML at cursor
This commit is contained in:
ZhenYi 2026-04-18 00:57:27 +08:00
parent 3a24022972
commit b96ef0342c
2 changed files with 62 additions and 1 deletions

View File

@ -127,8 +127,40 @@ function renderNode(
}
if (node.type === 'mention') {
const displayName = resolveName(node.mentionType, node.id, node.label);
const baseClass = mentionStyles[node.mentionType] ?? mentionStyles.user;
if (node.mentionType === 'ai') {
return (
<span key={index} className={mentionStyles[node.mentionType] ?? mentionStyles.user}>
<button
key={index}
type="button"
className={cn(
baseClass,
'inline-flex items-center gap-1 cursor-pointer border-0 bg-transparent p-0',
'hover:opacity-80 transition-opacity',
)}
onClick={() => {
document.dispatchEvent(
new CustomEvent('mention-click', {
detail: {
type: 'ai',
id: node.id,
label: displayName,
},
bubbles: true,
}),
);
}}
>
<span className="text-sm">🤖</span>
<span>AI:</span>
<span className="font-medium">{displayName}</span>
</button>
);
}
return (
<span key={index} className={baseClass}>
@{displayName}
</span>
);

View File

@ -230,6 +230,35 @@ const ChatInputArea = memo(function ChatInputArea({
}
};
// Listen for mention-click events from message content (e.g. 🤖 AI button)
useEffect(() => {
const onMentionClick = (e: Event) => {
const { type, id, label } = (e as CustomEvent<{ type: string; id: string; label: string }>).detail;
if (!textareaRef.current) return;
const textarea = textareaRef.current;
const cursorPos = textarea.selectionStart;
const textBefore = textarea.value.substring(0, cursorPos);
if (type === 'ai') {
// Insert @ai:mention at cursor position
const html = buildMentionHtml('ai', id, label);
const spacer = ' ';
const newValue = textBefore + html + spacer + textarea.value.substring(cursorPos);
const newCursorPos = cursorPos + html.length + spacer.length;
onDraftChange(newValue);
setTimeout(() => {
if (textareaRef.current) {
textareaRef.current.value = newValue;
textareaRef.current.setSelectionRange(newCursorPos, newCursorPos);
textareaRef.current.focus();
}
}, 0);
}
};
document.addEventListener('mention-click', onMentionClick);
return () => document.removeEventListener('mention-click', onMentionClick);
}, []);
return (
<div className="border-t border-border/70 bg-background p-3">
{replyingTo && (