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 && (
{historyMessages.map((msg) => (
你
{msg.content}
))}
)}
{!hasFocusContent ? (
暂无本轮对话,在下方输入开始交流
) : (
<>
{lastUserMessage && !pendingUserText && (
你
{lastUserMessage.content}
)}
{pendingUserText && (
你
{pendingUserText}
)}
{sending && !thinking && (
思考
正在等待模型回复…
)}
{thinking && (
思考
{renderBody(thinking, renderMode, false)}
)}
{summaryText && (
评价与修改建议
{renderBody(summaryText, renderMode, false)}
)}
>
)}
);
}
export default StudioRunChat;