import React, { useEffect, useRef, useState } from 'react'; import MarkdownRenderer from '../shared/MarkdownRenderer'; import { findLastUserMessageIndex } from './studioRunUtils'; import './StudioRunChat.css'; const RENDER_MODES = ['none', 'markdown', 'html']; const RENDER_MODE_LABELS = { none: '📄 纯文本', html: '🌐 HTML', markdown: '📝 Markdown', }; function renderBody(text, renderMode, isUser) { if (!text) return null; if (renderMode === 'html' && !isUser) { const hasHtmlTags = /<[a-z][\s\S]*>/i.test(text); if (hasHtmlTags) { return
; } return
{text}
; } if (renderMode === 'markdown') { return ; } return
{text}
; } function StudioRunChat({ stepMessages = [], thinking, evaluation, pendingUserText, inputValue, onInputChange, onSend, disabled = false, sending = false, streamOutput = false, onStreamOutputChange, placeholder = '输入消息…', }) { const containerRef = useRef(null); const focusAnchorRef = useRef(null); const optionsRef = useRef(null); const [showOptions, setShowOptions] = useState(false); const [renderMode, setRenderMode] = useState('markdown'); const lastUserIdx = findLastUserMessageIndex(stepMessages); const historyMessages = lastUserIdx > 0 ? stepMessages.slice(0, lastUserIdx).filter((msg) => msg.role === 'user') : []; const lastUserMessage = lastUserIdx >= 0 ? stepMessages[lastUserIdx] : pendingUserText ? { role: 'user', content: pendingUserText } : null; const summaryText = evaluation || null; const hasFocusContent = thinking || summaryText || pendingUserText || sending || lastUserMessage; useEffect(() => { const container = containerRef.current; const anchor = focusAnchorRef.current; if (!container || !anchor) return; container.scrollTop = Math.max(0, anchor.offsetTop - container.offsetTop); }, [stepMessages, thinking, evaluation, pendingUserText, sending]); useEffect(() => { const handleClickOutside = (event) => { if ( optionsRef.current && !optionsRef.current.contains(event.target) && !event.target.closest('.studio-run-chat-options-toggle') ) { setShowOptions(false); } }; if (showOptions) { document.addEventListener('mousedown', handleClickOutside); } return () => document.removeEventListener('mousedown', handleClickOutside); }, [showOptions]); const handleInputHeight = (e) => { const textarea = e.target; textarea.style.height = 'auto'; textarea.style.height = `${Math.min(textarea.scrollHeight, 300)}px`; }; const handleSubmit = (e) => { e.preventDefault(); if (disabled || sending || !inputValue.trim()) return; onSend(inputValue.trim()); const textarea = e.target.querySelector('.studio-run-chat-textarea'); if (textarea) textarea.style.height = 'auto'; }; const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (!disabled && !sending && inputValue.trim()) { onSend(inputValue.trim()); e.target.style.height = 'auto'; } } }; const cycleRenderMode = () => { const idx = RENDER_MODES.indexOf(renderMode); setRenderMode(RENDER_MODES[(idx + 1) % RENDER_MODES.length]); }; return (
{historyMessages.length > 0 && ( )}
{!hasFocusContent ? (
暂无本轮对话,在下方输入开始交流
) : ( <> {lastUserMessage && !pendingUserText && (
{lastUserMessage.content}
)} {pendingUserText && (
{pendingUserText}
)} {sending && !thinking && (
思考 正在等待模型回复…
)} {thinking && (
思考
{renderBody(thinking, renderMode, false)}
)} {summaryText && (
评价与修改建议
{renderBody(summaryText, renderMode, false)}
)} )}
{showOptions && (
显示
功能
)}