feat(FN-1151): preserve and display planning conversation history
- Persist per-turn thinking output for planning and mission interview sessions, including history serialization and recovery-safe state fields - Add shared conversation history parsing/API types and a reusable timeline component with formatted answers and expandable AI reasoning - Restore and render conversation history across Planning Mode, Mission Interview, and Subtask Breakdown modals during resume and live question flow - Harden response submission with nullable-question guards and expand unit/e2e coverage for history rendering and persistence behavior
This commit is contained in:
@@ -6,7 +6,9 @@ import {
|
||||
createTasksFromBreakdown,
|
||||
cancelSubtaskBreakdown,
|
||||
fetchAiSession,
|
||||
parseConversationHistory,
|
||||
type SubtaskItem,
|
||||
type ConversationHistoryEntry,
|
||||
} from "../api";
|
||||
import {
|
||||
saveSubtaskDescription,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
clearSubtaskDescription,
|
||||
} from "../hooks/modalPersistence";
|
||||
import { CheckCircle, Loader2, ListTree, Plus, Trash2, X, GripVertical, ArrowUp, ArrowDown, Minimize2 } from "lucide-react";
|
||||
import { ConversationHistory } from "./ConversationHistory";
|
||||
|
||||
interface SubtaskBreakdownModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -64,6 +67,7 @@ function hasDependencyCycle(subtasks: SubtaskItem[]): boolean {
|
||||
export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onTasksCreated, parentTaskId, projectId, resumeSessionId }: SubtaskBreakdownModalProps) {
|
||||
const [view, setView] = useState<ViewState>({ type: "initial" });
|
||||
const [subtasks, setSubtasks] = useState<SubtaskItem[]>([]);
|
||||
const [conversationHistory, setConversationHistory] = useState<ConversationHistoryEntry[]>([]);
|
||||
const [thinkingOutput, setThinkingOutput] = useState("");
|
||||
const [showThinking, setShowThinking] = useState(true);
|
||||
const [isReconnecting, setIsReconnecting] = useState(false);
|
||||
@@ -102,6 +106,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
streamRef.current = null;
|
||||
setView({ type: "initial" });
|
||||
setSubtasks([]);
|
||||
setConversationHistory([]);
|
||||
setThinkingOutput("");
|
||||
setShowThinking(true);
|
||||
setIsReconnecting(false);
|
||||
@@ -134,6 +139,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
const beginBreakdown = useCallback(async () => {
|
||||
if (!localDescription.trim()) return;
|
||||
setError(null);
|
||||
setConversationHistory([]);
|
||||
setThinkingOutput("");
|
||||
setIsReconnecting(false);
|
||||
|
||||
@@ -191,6 +197,10 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
try {
|
||||
const session = await fetchAiSession(resumeSessionId);
|
||||
if (!session) return;
|
||||
|
||||
const parsedHistory = parseConversationHistory(session.conversationHistory);
|
||||
setConversationHistory(parsedHistory);
|
||||
|
||||
if (session.status === "generating" || session.status === "awaiting_input") {
|
||||
setThinkingOutput(session.thinkingOutput ?? "");
|
||||
setView({ type: "generating", sessionId: resumeSessionId });
|
||||
@@ -407,6 +417,12 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
|
||||
{view.type === "generating" && (
|
||||
<div className="planning-loading">
|
||||
{conversationHistory.length > 0 && (
|
||||
<>
|
||||
<ConversationHistory entries={conversationHistory} defaultShowThinking={true} />
|
||||
<div className="conversation-separator" />
|
||||
</>
|
||||
)}
|
||||
<Loader2 size={40} className="spin" style={{ color: "var(--todo)" }} />
|
||||
<p>AI is generating subtasks...</p>
|
||||
<div className="planning-thinking-container">
|
||||
@@ -425,6 +441,13 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
{(view.type === "editing" || view.type === "creating") && (
|
||||
<div className="planning-summary">
|
||||
<div className="planning-view-scroll planning-summary-scroll">
|
||||
{conversationHistory.length > 0 && (
|
||||
<>
|
||||
<ConversationHistory entries={conversationHistory} />
|
||||
<div className="conversation-separator" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="planning-summary-header">
|
||||
<CheckCircle size={24} style={{ color: "var(--color-success)" }} />
|
||||
<h4>Review your subtasks</h4>
|
||||
|
||||
Reference in New Issue
Block a user