import { ArrowDown, Bot, ChevronDown, Clock3, Copy, Download, Sparkles, UserRound } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'
import clsx from 'clsx'
import {
MESSAGE_COLLAPSE_AT,
buildListModeMessages,
copyTextWithFallback,
downloadTextFile,
formatElapsed,
} from './chatHistoryUtils'
function ExpandableText({ text = '', threshold = MESSAGE_COLLAPSE_AT, expandLabel, collapseLabel, buttonClassName = 'text-white hover:text-white/80' }) {
const shouldCollapse = text.length > threshold
const [expanded, setExpanded] = useState(false)
const contentRef = useRef(null)
const [maxHeight, setMaxHeight] = useState('none')
useEffect(() => {
setExpanded(false)
}, [text])
const visibleText = shouldCollapse && !expanded ? `${text.slice(0, threshold)}...` : text
useEffect(() => {
if (!contentRef.current) return
setMaxHeight(`${contentRef.current.scrollHeight}px`)
}, [expanded, visibleText])
return (
{shouldCollapse && (
)}
)
}
function RequestMessages({ item, t, messages }) {
const requestMessages = Array.isArray(messages) && messages.length > 0
? messages
: [{ role: 'user', content: item?.user_input || t('chatHistory.emptyUserInput') }]
return (
{requestMessages.map((message, index) => {
const role = message.role || 'user'
const isUser = role === 'user'
const isAssistant = role === 'assistant'
const isTool = role === 'tool'
const label = isUser
? t('chatHistory.role.user')
: (isAssistant ? t('chatHistory.role.assistant') : (isTool ? t('chatHistory.role.tool') : t('chatHistory.role.system')))
return (
{isUser ? : }
{label}
{message.content || t('chatHistory.emptyUserInput')}
)
})}
)
}
function PromptTextActions({ text, filename, copyTitle, downloadTitle, t, onMessage, buttonClassName }) {
const handleCopy = async () => {
try {
await copyTextWithFallback(text)
onMessage?.('success', t('chatHistory.copySuccess'))
} catch {
onMessage?.('error', t('chatHistory.copyFailed'))
}
}
const handleDownload = () => {
try {
downloadTextFile(filename, text)
onMessage?.('success', t('chatHistory.downloadSuccess'))
} catch {
onMessage?.('error', t('chatHistory.downloadFailed'))
}
}
return (
)
}
function MergedPromptView({ item, t, onMessage }) {
const merged = item?.final_prompt || ''
return (
{t('chatHistory.mergedInput')}
)
}
function HistoryTextView({ item, t, onMessage }) {
const historyText = (item?.history_text || '').trim()
if (!historyText) return null
return (
)
}
function MetaGrid({ selectedItem, t }) {
return (
{t('chatHistory.metaTitle')}
{t('chatHistory.metaAccount')}
{selectedItem.account_id || t('chatHistory.metaUnknown')}
{t('chatHistory.metaElapsed')}
{formatElapsed(selectedItem.elapsed_ms, t)}
{t('chatHistory.metaSurface')}
{selectedItem.surface || t('chatHistory.metaUnknown')}
{t('chatHistory.metaModel')}
{selectedItem.model || t('chatHistory.metaUnknown')}
{t('chatHistory.metaStatusCode')}
{selectedItem.status_code || '-'}
{t('chatHistory.metaStream')}
{selectedItem.stream ? t('chatHistory.streamMode') : t('chatHistory.nonStreamMode')}
{t('chatHistory.metaCaller')}
{selectedItem.caller_id || t('chatHistory.metaUnknown')}
)
}
export default function DetailConversation({ selectedItem, t, viewMode, detailScrollRef, assistantStartRef, bottomButtonClassName, onMessage }) {
if (!selectedItem) return null
const listModeState = viewMode === 'list' ? buildListModeMessages(selectedItem, t) : null
const showHistoryAtTop = viewMode !== 'list' || !listModeState?.historyMerged
return (
<>
{showHistoryAtTop && }
{viewMode === 'list'
?
: }
{(selectedItem.reasoning_content || '').trim() && (
{t('chatHistory.reasoningTrace')}
{selectedItem.reasoning_content}
)}
{selectedItem.status === 'error'
? {selectedItem.error || t('chatHistory.failedOutput')}
: (selectedItem.content || t('chatHistory.emptyAssistantOutput'))}
>
)
}