fix(cortex): add @mention detection to ChatInput (not just CommandInput)
The @mention dropdown was only in command-input.tsx but users type in chat-input.tsx (the v3.0 chat textarea). Added @mention support there: - @mention state and detection logic - PatientMentionDropdown rendering - handleMentionSelect with activePatient setting - Escape key closes dropdown before clearing input 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,9 @@ import { useState, useRef, KeyboardEvent, ChangeEvent, forwardRef, useImperative
|
|||||||
import { Send, Mic } from 'lucide-react';
|
import { Send, Mic } from 'lucide-react';
|
||||||
import { useCortexStore } from '@/stores/cortex-store';
|
import { useCortexStore } from '@/stores/cortex-store';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { usePatientSelection } from '@/lib/cortex/hooks/use-patient-selection';
|
||||||
|
import type { PatientSearchResult } from '@/lib/cortex/hooks/use-patient-search';
|
||||||
|
import { PatientMentionDropdown } from '../command-center/patient-mention-dropdown';
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
@@ -35,6 +38,17 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
|
|||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const addChatMessage = useCortexStore((s) => s.addChatMessage);
|
const addChatMessage = useCortexStore((s) => s.addChatMessage);
|
||||||
|
|
||||||
|
// @mention state (E2)
|
||||||
|
const [mentionState, setMentionState] = useState<{
|
||||||
|
query: string;
|
||||||
|
startIndex: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// Patient selection hook
|
||||||
|
const { selectPatient } = usePatientSelection({
|
||||||
|
showSuccessToast: false,
|
||||||
|
});
|
||||||
|
|
||||||
// Expose focus, clear, and setValue methods to parent
|
// Expose focus, clear, and setValue methods to parent
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
focus: () => {
|
focus: () => {
|
||||||
@@ -63,9 +77,45 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// @mention detection
|
||||||
|
const detectMention = (value: string) => {
|
||||||
|
const lastAtIndex = value.lastIndexOf('@');
|
||||||
|
if (lastAtIndex !== -1) {
|
||||||
|
const afterAt = value.slice(lastAtIndex + 1);
|
||||||
|
// Active if: no space after @, and at least 1 char
|
||||||
|
if (!afterAt.includes(' ') && afterAt.length > 0) {
|
||||||
|
setMentionState({ query: afterAt, startIndex: lastAtIndex });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setMentionState(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle @mention selection
|
||||||
|
const handleMentionSelect = (patient: PatientSearchResult) => {
|
||||||
|
if (!mentionState) return;
|
||||||
|
|
||||||
|
// Replace @query with @name in input
|
||||||
|
const before = inputValue.slice(0, mentionState.startIndex);
|
||||||
|
const after = inputValue.slice(mentionState.startIndex + mentionState.query.length + 1);
|
||||||
|
const newValue = `${before}@${patient.name} ${after}`.trim();
|
||||||
|
setInputValue(newValue);
|
||||||
|
|
||||||
|
// Set activePatient
|
||||||
|
selectPatient(patient);
|
||||||
|
|
||||||
|
// Close dropdown
|
||||||
|
setMentionState(null);
|
||||||
|
|
||||||
|
// Focus back on textarea
|
||||||
|
textareaRef.current?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
// Handle input change and auto-resize
|
// Handle input change and auto-resize
|
||||||
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setInputValue(e.target.value);
|
const value = e.target.value;
|
||||||
|
setInputValue(value);
|
||||||
|
detectMention(value);
|
||||||
|
|
||||||
// Auto-resize textarea
|
// Auto-resize textarea
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
@@ -108,9 +158,13 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
|
|||||||
handleSubmit();
|
handleSubmit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape to clear input
|
// Escape: close dropdown first, then clear input
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (mentionState) {
|
||||||
|
setMentionState(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
textareaRef.current.style.height = 'auto';
|
textareaRef.current.style.height = 'auto';
|
||||||
@@ -123,6 +177,15 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(function Ch
|
|||||||
return (
|
return (
|
||||||
<div className="border-t border-slate-200 p-4 bg-white">
|
<div className="border-t border-slate-200 p-4 bg-white">
|
||||||
<div className="relative flex items-end gap-2">
|
<div className="relative flex items-end gap-2">
|
||||||
|
{/* @mention dropdown */}
|
||||||
|
{mentionState && (
|
||||||
|
<PatientMentionDropdown
|
||||||
|
query={mentionState.query}
|
||||||
|
onSelect={handleMentionSelect}
|
||||||
|
onClose={() => setMentionState(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Textarea input */}
|
{/* Textarea input */}
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
|
|||||||
Reference in New Issue
Block a user