import { ArrowLeft, Send, Sparkles } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { type CreationAgentProgressCopy, normalizeCreationAgentProgress, resolveCreationAgentProgressHint, resolveCreationAnchorStatusLabel, } from '../../services/creation-agent'; export type CreationAgentAnchorView = { key: string; label: string; value: string; status: string; }; export type CreationAgentMessageView = { id: string; role: string; kind?: string; text: string; createdAt?: string; }; export type CreationAgentOperationView = { operationId?: string; type?: string; status: string; phaseLabel: string; phaseDetail?: string; progress: number; error?: string | null; }; export type CreationAgentSessionView = { sessionId: string; title: string; assistantSummary?: string | null; currentTurn: number; progressPercent: number; anchors: CreationAgentAnchorView[]; messages: CreationAgentMessageView[]; recommendedReplies?: string[]; }; export type CreationAgentTheme = { accentTextClass: string; accentBgClass: string; accentButtonClass: string; userBubbleClass: string; heroClass: string; anchorGridClass?: string; }; export type CreationAgentQuickAction = { key: string; label: string; minTurn?: number; minProgress?: number; showWhenComplete?: boolean; }; type CreationAgentWorkspaceProps = { session: CreationAgentSessionView | null; theme: CreationAgentTheme; loadingText: string; composerPlaceholder: string; primaryActionLabel: string; progressCopy?: CreationAgentProgressCopy; activeOperation?: CreationAgentOperationView | null; streamingReplyText?: string; isStreamingReply?: boolean; isBusy?: boolean; error?: string | null; quickActions?: CreationAgentQuickAction[]; onBack: () => void; onSubmitText: (text: string, quickActionKey?: string) => void; onPrimaryAction: () => void; onQuickAction?: (action: CreationAgentQuickAction) => void; }; function uniqueRecommendedReplies(recommendedReplies: string[] = []) { return [...new Set(recommendedReplies.map((reply) => reply.trim()).filter(Boolean))].slice( 0, 3, ); } function CreationAgentOperationBanner({ operation, }: { operation: CreationAgentOperationView | null | undefined; }) { const [visibleOperation, setVisibleOperation] = useState(operation ?? null); useEffect(() => { setVisibleOperation(operation ?? null); if (operation?.status !== 'completed') { return; } const timeoutId = window.setTimeout(() => { setVisibleOperation((current) => current?.operationId === operation.operationId ? null : current, ); }, 1200); return () => window.clearTimeout(timeoutId); }, [operation]); if (!visibleOperation) { return null; } const isFailed = visibleOperation.status === 'failed'; const isRunning = visibleOperation.status === 'running' || visibleOperation.status === 'queued'; const bannerToneClass = isFailed ? 'platform-banner--danger' : isRunning ? 'platform-banner--info' : 'platform-banner--success'; const progress = normalizeCreationAgentProgress(visibleOperation.progress); const progressFillStyle = isFailed ? { background: 'linear-gradient(90deg, #fb7185 0%, #f43f5e 100%)' } : isRunning ? { background: 'var(--platform-button-primary-fill)' } : { background: 'linear-gradient(90deg, #86efac 0%, #34d399 100%)' }; return (
{visibleOperation.phaseLabel}
{progress}%
{visibleOperation.phaseDetail ? (
{visibleOperation.phaseDetail}
) : null} {visibleOperation.error ? (
{visibleOperation.error}
) : null}
); } function CreationAgentMessageBubble({ message, theme, recommendedReplies, onRecommendedReply, }: { message: CreationAgentMessageView; theme: CreationAgentTheme; recommendedReplies?: string[]; onRecommendedReply: (text: string) => void; }) { const isUser = message.role === 'user'; const isSystem = message.role === 'system'; const visibleRecommendedReplies = isUser ? [] : uniqueRecommendedReplies(recommendedReplies); const bubbleToneClass = isUser ? theme.userBubbleClass : isSystem ? 'border border-[var(--platform-warm-border)] bg-[var(--platform-warm-bg)] text-[var(--platform-warm-text)]' : 'platform-subpanel text-[var(--platform-text-strong)]'; return (
{message.text}
{visibleRecommendedReplies.length > 0 ? (
{visibleRecommendedReplies.map((reply, replyIndex) => ( ))}
) : null}
); } function CreationAgentAnchorChip({ anchor, theme, }: { anchor: CreationAgentAnchorView; theme: CreationAgentTheme; }) { return (
{anchor.label} {resolveCreationAnchorStatusLabel(anchor.status)}
{anchor.value || '等待补齐'}
); } function shouldShowQuickAction( action: CreationAgentQuickAction, session: CreationAgentSessionView, progress: number, ) { if (action.showWhenComplete && progress < 100) { return false; } if (!action.showWhenComplete && progress >= 100 && action.minProgress !== 100) { return false; } if (typeof action.minTurn === 'number' && session.currentTurn < action.minTurn) { return false; } if (typeof action.minProgress === 'number' && progress < action.minProgress) { return false; } return true; } export function CreationAgentWorkspace({ session, theme, loadingText, composerPlaceholder, primaryActionLabel, progressCopy, activeOperation = null, streamingReplyText = '', isStreamingReply = false, isBusy = false, error = null, quickActions = [], onBack, onSubmitText, onPrimaryAction, onQuickAction, }: CreationAgentWorkspaceProps) { const [draftText, setDraftText] = useState(''); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end', }); }, [session?.messages, streamingReplyText, isStreamingReply]); if (!session) { return (
{error || loadingText}
); } const progress = normalizeCreationAgentProgress(session.progressPercent); const visibleQuickActions = quickActions.filter((action) => shouldShowQuickAction(action, session, progress), ); const lastAssistantMessageIndex = session.messages.reduce( (lastIndex, message, index) => message.role === 'assistant' ? index : lastIndex, -1, ); const submit = () => { const text = draftText.trim(); if (!text || isBusy) { return; } onSubmitText(text); setDraftText(''); }; return (
{session.title}
{session.assistantSummary ? (
{session.assistantSummary}
) : null}
创作进度 {progress}%
{resolveCreationAgentProgressHint(progress, progressCopy)}
{visibleQuickActions.length > 0 ? (
{visibleQuickActions.map((action) => ( ))}
) : null}
{session.anchors.length > 0 ? (
{session.anchors.map((anchor) => ( ))}
) : null}
{session.messages.length === 0 ? (
暂无消息
) : ( session.messages.map((message, index) => ( onSubmitText(text)} /> )) )} {isStreamingReply ? (
{streamingReplyText ? (
{streamingReplyText}
) : (
)}
) : null}
{error ? (
{error}
) : null}