feat(frontend): add repo type to mention autocomplete system

Add 'repo' to MentionType across all editor types, include repos in the
@ trigger pool, add repo badge (green chip), Repos section in the
mention dropdown, and MentionBadge styles. Wire projectRepos from
room context into IMEditor mentionItems.
This commit is contained in:
ZhenYi 2026-04-26 23:58:59 +08:00
parent adbc0705db
commit c8eba28e7a
5 changed files with 28 additions and 6 deletions

View File

@ -76,7 +76,7 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(fu
{roomName, onSend, replyingTo, onCancelReply}, {roomName, onSend, replyingTo, onCancelReply},
ref, ref,
) { ) {
const {members, activeRoomId, roomAiConfigs, wsClient} = useRoom(); const {members, activeRoomId, roomAiConfigs, projectRepos, wsClient} = useRoom();
// Ref passed to the inner IMEditor // Ref passed to the inner IMEditor
const innerEditorRef = useRef<IMEditorHandle | null>(null); const innerEditorRef = useRef<IMEditorHandle | null>(null);
@ -147,12 +147,18 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(fu
channels: [] as { id: string; label: string; type: 'channel'; avatar?: string }[], channels: [] as { id: string; label: string; type: 'channel'; avatar?: string }[],
ai: roomAiConfigs.map((cfg) => ({ ai: roomAiConfigs.map((cfg) => ({
id: cfg.model, id: cfg.model,
label: cfg.modelName ?? cfg.model, label: cfg.modelName || 'Unknown AI',
type: 'ai' as const, type: 'ai' as const,
})), })),
repos: projectRepos.map((r) => ({
id: r.repo_name,
label: r.repo_name,
type: 'repo' as const,
description: r.description ?? undefined,
})),
commands: SLASH_COMMANDS, commands: SLASH_COMMANDS,
specialMentions: SPECIAL_MENTIONS, specialMentions: SPECIAL_MENTIONS,
}), [members, roomAiConfigs]); }), [members, roomAiConfigs, projectRepos]);
// File upload handler — POST to /rooms/{room_id}/upload // File upload handler — POST to /rooms/{room_id}/upload
const handleUploadFile = async (file: File): Promise<{ id: string; url: string }> => { const handleUploadFile = async (file: File): Promise<{ id: string; url: string }> => {

View File

@ -26,6 +26,7 @@ export interface IMEditorProps {
users: MentionItem[]; users: MentionItem[];
channels: MentionItem[]; channels: MentionItem[];
ai: MentionItem[]; ai: MentionItem[];
repos: MentionItem[];
commands: MentionItem[]; commands: MentionItem[];
specialMentions?: MentionItem[]; specialMentions?: MentionItem[];
}; };
@ -151,6 +152,7 @@ function getBadge(type: MentionType): { label: string; cls: string } | null {
if (type === 'ai') return {label: 'AI', cls: 'bg-blue-50 text-blue-600'}; if (type === 'ai') return {label: 'AI', cls: 'bg-blue-50 text-blue-600'};
if (type === 'channel') return {label: '#', cls: 'bg-gray-100 text-gray-500'}; if (type === 'channel') return {label: '#', cls: 'bg-gray-100 text-gray-500'};
if (type === 'command') return {label: 'cmd', cls: 'bg-amber-50 text-amber-600'}; if (type === 'command') return {label: 'cmd', cls: 'bg-amber-50 text-amber-600'};
if (type === 'repo') return {label: 'repo', cls: 'bg-green-50 text-green-600'};
return null; return null;
} }
@ -173,12 +175,13 @@ function serializeAstNode(node: EditorNode): string {
// ─── Mention Dropdown (sectioned by type) ──────────────────────────────────── // ─── Mention Dropdown (sectioned by type) ────────────────────────────────────
const SECTION_ORDER = ['special_here', 'special_channel', 'ai', 'user', 'channel', 'command'] as const; const SECTION_ORDER = ['special_here', 'special_channel', 'ai', 'user', 'repo', 'channel', 'command'] as const;
const SECTION_LABELS: Record<string, string> = { const SECTION_LABELS: Record<string, string> = {
special_here: 'Notify', special_here: 'Notify',
special_channel: 'Notify', special_channel: 'Notify',
ai: 'AI', ai: 'AI',
user: 'Members', user: 'Members',
repo: 'Repositories',
channel: 'Channels', channel: 'Channels',
command: 'Commands', command: 'Commands',
}; };
@ -356,7 +359,8 @@ export const IMEditor = forwardRef<IMEditorHandle, IMEditorProps>(function IMEdi
...(mentionItems.specialMentions ?? []), ...(mentionItems.specialMentions ?? []),
...mentionItems.ai, ...mentionItems.ai,
...mentionItems.users, ...mentionItems.users,
], [mentionItems.specialMentions, mentionItems.ai, mentionItems.users]); ...mentionItems.repos,
], [mentionItems.specialMentions, mentionItems.ai, mentionItems.users, mentionItems.repos]);
const hashPool = useMemo(() => [...mentionItems.channels], [mentionItems.channels]); const hashPool = useMemo(() => [...mentionItems.channels], [mentionItems.channels]);

View File

@ -2,7 +2,7 @@
* Core types for the IM editor (mentions, files, emojis). * Core types for the IM editor (mentions, files, emojis).
*/ */
export type MentionType = 'user' | 'channel' | 'ai' | 'command' | 'special_here' | 'special_channel'; export type MentionType = 'user' | 'channel' | 'ai' | 'repo' | 'command' | 'special_here' | 'special_channel';
export interface MentionItem { export interface MentionItem {
id: string; id: string;

View File

@ -42,6 +42,11 @@ const TYPE_STYLE: Record<MentionType, { light: string; dark?: string; prefix: st
dark: 'dark:bg-orange-900/30 dark:text-orange-300', dark: 'dark:bg-orange-900/30 dark:text-orange-300',
prefix: '@', prefix: '@',
}, },
repo: {
light: 'bg-teal-50 text-teal-600',
dark: 'dark:bg-teal-900/30 dark:text-teal-300',
prefix: '@',
},
}; };
export function MentionBadge({ type, label, onClick, id, className }: MentionBadgeProps) { export function MentionBadge({ type, label, onClick, id, className }: MentionBadgeProps) {

View File

@ -7,6 +7,7 @@ export type MentionType =
| 'user' | 'user'
| 'channel' | 'channel'
| 'ai' | 'ai'
| 'repo'
| 'command' | 'command'
| 'special_here' | 'special_here'
| 'special_channel'; | 'special_channel';
@ -15,6 +16,7 @@ export const MENTION_TYPES: MentionType[] = [
'user', 'user',
'channel', 'channel',
'ai', 'ai',
'repo',
'command', 'command',
'special_here', 'special_here',
'special_channel', 'special_channel',
@ -84,6 +86,11 @@ export function serializeAiMention(aiId: string, aiName: string): string {
return serializeMention('ai', aiId, aiName); return serializeMention('ai', aiId, aiName);
} }
/** Build a mention token from a repo mention. */
export function serializeRepoMention(repoName: string, label?: string): string {
return serializeMention('repo', repoName, label ?? repoName);
}
/** Build a mention token from a command mention. */ /** Build a mention token from a command mention. */
export function serializeCommandMention(commandId: string, commandName: string): string { export function serializeCommandMention(commandId: string, commandName: string): string {
return serializeMention('command', commandId, commandName); return serializeMention('command', commandId, commandName);