diff --git a/apps/web/src/pages/playground-prompt-mention.tsx b/apps/web/src/pages/playground-prompt-mention.tsx index 9e022d5..52fefb1 100644 --- a/apps/web/src/pages/playground-prompt-mention.tsx +++ b/apps/web/src/pages/playground-prompt-mention.tsx @@ -49,15 +49,16 @@ export function PlaygroundPromptMentionInput(props: { const editableRef = useRef(null); const dropdownRef = useRef(null); const blurTimerRef = useRef(undefined); + const isComposingRef = useRef(false); const [text, setText] = useState(props.value); const [focused, setFocused] = useState(false); - const [isComposing, setIsComposing] = useState(false); + const [hasEditableContent, setHasEditableContent] = useState(() => promptTextHasContent(props.value)); const [mentionOpen, setMentionOpen] = useState(false); const [mentionAtIndex, setMentionAtIndex] = useState(-1); const [mentionSearch, setMentionSearch] = useState(''); const [highlightIndex, setHighlightIndex] = useState(0); const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, placement: 'bottom' as 'bottom' | 'top' }); - const showPlaceholder = text.trim().length === 0; + const showPlaceholder = !hasEditableContent; const uploadSignature = useMemo( () => props.uploads.map((item) => `${item.id}:${item.kind}:${item.name}:${item.url}`).join('|'), @@ -78,8 +79,12 @@ export function PlaygroundPromptMentionInput(props: { }, [mentionItems, mentionSearch]); useEffect(() => { - if (props.value === text) return; + if (props.value === text) { + setHasEditableContent(promptTextHasContent(props.value)); + return; + } setText(props.value); + setHasEditableContent(promptTextHasContent(props.value)); if (!focused) { requestAnimationFrame(() => renderToEditable(props.value)); } @@ -89,6 +94,7 @@ export function PlaygroundPromptMentionInput(props: { const cleaned = removeInvalidPlaygroundResourceTokens(text, props.uploads); if (cleaned !== text) { setText(cleaned); + setHasEditableContent(promptTextHasContent(cleaned)); props.onChange(cleaned); requestAnimationFrame(() => renderToEditable(cleaned)); return; @@ -149,6 +155,7 @@ export function PlaygroundPromptMentionInput(props: { const editable = editableRef.current; if (!editable) return; editable.innerHTML = textToHtml(nextText, props.uploads); + setHasEditableContent(promptTextHasContent(nextText)); if (typeof caret === 'number') { editable.focus(); setCaretOffset(editable, caret); @@ -157,8 +164,10 @@ export function PlaygroundPromptMentionInput(props: { function commitFromEditable(shouldInspectMention: boolean, event?: InputEvent) { const editable = editableRef.current; - if (!editable || props.disabled || isComposing) return; + if (!editable || props.disabled) return; const nextText = serializeEditableToPlainText(editable); + setHasEditableContent(promptTextHasContent(nextText)); + if (isComposingRef.current || event?.isComposing) return; setText(nextText); props.onChange(nextText); if (!shouldInspectMention) return; @@ -206,6 +215,7 @@ export function PlaygroundPromptMentionInput(props: { const nextText = `${before}${token} ${after}`; const nextCaret = before.length + token.length + 1; setText(nextText); + setHasEditableContent(promptTextHasContent(nextText)); props.onChange(nextText); closeMention(); requestAnimationFrame(() => renderToEditable(nextText, nextCaret)); @@ -257,10 +267,13 @@ export function PlaygroundPromptMentionInput(props: { }, 120); }} onCompositionEnd={() => { - setIsComposing(false); + isComposingRef.current = false; + setHasEditableContent(editablePromptTextHasContent(editableRef.current)); requestAnimationFrame(() => commitFromEditable(true)); }} - onCompositionStart={() => setIsComposing(true)} + onCompositionStart={() => { + isComposingRef.current = true; + }} onFocus={() => { setFocused(true); if (blurTimerRef.current) window.clearTimeout(blurTimerRef.current); @@ -313,6 +326,14 @@ export function PlaygroundPromptMentionInput(props: { ); } +function promptTextHasContent(raw: string) { + return raw.trim().length > 0; +} + +function editablePromptTextHasContent(editable: HTMLElement | null) { + return editable ? promptTextHasContent(serializeEditableToPlainText(editable)) : false; +} + function MentionThumb(props: { item: PlaygroundMentionUpload }) { if (props.item.kind === 'image') { return ;