Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/components/GroupedHistoryView/ProjectGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,24 @@ export default function ProjectGroup({
// Project doesn't exist, load final state (no replay animation)
const firstTask = project.tasks?.[0];
if (firstTask) {
const question = firstTask.question || project.last_prompt || '';
const historyId = firstTask.id?.toString() || '';
const taskIdsList = project.tasks
?.map((t) => t.task_id)
.filter(Boolean) || [project.project_id];
const taskPairs = (project.tasks || [])
.filter((t) => t.task_id)
.map((t) => ({ taskId: t.task_id!, question: t.question || '' }));
const taskIdsList = taskPairs.length
? taskPairs.map((p) => p.taskId)
: [project.project_id];
const questions = taskPairs.length
? taskPairs.map((p) => p.question)
: [project.last_prompt || ''];

setIsLoadingProject(true);
try {
await loadProjectFromHistory(
projectStore,
navigate,
project.project_id,
question,
questions,
historyId,
taskIdsList,
project.project_name
Expand Down
14 changes: 9 additions & 5 deletions src/components/HistorySidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,20 @@ export default function HistorySidebar() {
) => {
close();
const project = historyTasks.find((p) => p.project_id === projectId);
const taskIdsList = project?.tasks.map(
(task: HistoryTask) => task.task_id
) || [projectId];
const tasks = project?.tasks || [];
const taskIdsList = tasks
.map((t: HistoryTask) => t.task_id)
.filter(Boolean) as string[];
const questions = tasks.map((t: HistoryTask) => t.question || '');
const finalTaskIds = taskIdsList.length ? taskIdsList : [projectId];
const finalQuestions = questions.length ? questions : [question];
Comment on lines +146 to +152
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @bytecii Could you confirm whether it's guaranteed that every task with a non-null task_id also appears at the same index in both arrays? If not, it might be safer to derive both from a filtered pair like ProjectGroup.tsx already does taskPairs.

await loadProjectFromHistory(
projectStore,
navigate,
projectId,
question,
finalQuestions,
historyId,
taskIdsList,
finalTaskIds,
project?.project_name
);
};
Expand Down
10 changes: 8 additions & 2 deletions src/components/SearchHistoryDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export function SearchHistoryDialog() {
projectId: string,
question: string,
historyId: string,
project?: { tasks: { task_id: string }[]; project_name?: string }
project?: {
tasks: { task_id: string; question?: string }[];
project_name?: string;
}
) => {
const existingProject = projectStore.getProjectById(projectId);
if (existingProject) {
Expand All @@ -63,11 +66,14 @@ export function SearchHistoryDialog() {
const taskIdsList = project?.tasks
?.map((t) => t.task_id)
.filter(Boolean) || [projectId];
const questions = project?.tasks?.map((t) => t.question || question) || [
question,
];
await loadProjectFromHistory(
projectStore,
navigate,
projectId,
question,
questions,
historyId,
taskIdsList,
project?.project_name
Expand Down
58 changes: 13 additions & 45 deletions src/lib/replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ export const loadProjectFromHistory = async (
projectStore: ProjectStore,
navigate: NavigateFunction,
projectId: string,
question: string,
questions: string[],
historyId: string,
taskIdsList?: string[],
projectName?: string
) => {
const taskIds = taskIdsList || [projectId];
await projectStore.loadProjectFromHistory(
taskIds,
question,
questions,
projectId,
historyId,
projectName
Expand All @@ -64,12 +64,12 @@ export const replayProject = async (
projectStore: ProjectStore,
navigate: NavigateFunction,
projectId: string,
question: string,
questions: string[],
historyId: string,
taskIdsList?: string[]
) => {
if (!taskIdsList) taskIdsList = [projectId];
projectStore.replayProject(taskIdsList, question, projectId, historyId);
projectStore.replayProject(taskIdsList, questions, projectId, historyId);
navigate({ pathname: '/' });
};

Expand All @@ -94,48 +94,16 @@ export const replayActiveTask = async (
return;
}

// Extract the very first available question from all chat stores and tasks
// Get the question from the current active task's messages
let question = '';
let earliestTimestamp = Infinity;

// Get the project data to access all chat stores
const project = projectStore.projects[projectId];
if (project && project.chatStores) {
Object.entries(project.chatStores).forEach(
([chatStoreId, chatStoreData]: [string, any]) => {
const timestamp = project.chatStoreTimestamps[chatStoreId] || 0;
const chatState = chatStoreData.getState();

if (chatState.tasks) {
Object.values(chatState.tasks).forEach((task: any) => {
// Check messages for user content
if (task.messages && task.messages.length > 0) {
const userMessage = task.messages.find(
(msg: any) => msg.role === 'user'
);
if (
userMessage &&
userMessage.content &&
timestamp < earliestTimestamp
) {
question = userMessage.content.trim();
earliestTimestamp = timestamp;
}
}
});
}
}
const currentTask = chatStore.tasks[taskId];
if (currentTask?.messages?.length > 0) {
const userMessage = currentTask.messages.find(
(msg: any) => msg.role === 'user'
);
}

// Fallback to current task's first message if no question found
if (
!question &&
chatStore.tasks[taskId] &&
chatStore.tasks[taskId].messages[0]
) {
question = chatStore.tasks[taskId].messages[0].content;
console.log('[REPLAY] question fall back to ', question);
if (userMessage?.content) {
question = userMessage.content.trim();
}
}

const historyId = projectStore.getHistoryId(projectId);
Expand All @@ -144,7 +112,7 @@ export const replayActiveTask = async (
const taskIdsList = [taskId];
projectStore.replayProject(
taskIdsList,
question,
[question],
projectId,
historyId || undefined
);
Expand Down
14 changes: 9 additions & 5 deletions src/store/chatStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2711,11 +2711,15 @@ const chatStore = (initial?: Partial<ChatStore>) =>
(task) => task.content !== ''
);
setTaskInfo(taskId, taskInfo);
// Sync taskRunning with the filtered taskInfo (user edits should be reflected
setTaskRunning(
taskId,
taskInfo.map((task) => ({ ...task }))
);
// Sync taskRunning with the filtered taskInfo (user edits should be reflected).
// Skip for replay: taskRunning already reflects the completed state from playback
// and resetting it would revert the Done counters back to Pending.
if (!type) {
setTaskRunning(
taskId,
taskInfo.map((task) => ({ ...task }))
);
}

// IMPORTANT: Set isConfirm BEFORE sending API requests to prevent race condition
// where backend sends to_sub_tasks SSE event before we mark task as confirmed
Expand Down
27 changes: 16 additions & 11 deletions src/store/projectStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ interface ProjectStore {
) => void;
replayProject: (
taskIds: string[],
question?: string,
questions?: string[],
projectId?: string,
historyId?: string
) => string;
/** Load project from history with final state (no animation). Resolves when loading completes. */
loadProjectFromHistory: (
taskIds: string[],
question: string,
questions: string[],
projectId: string,
historyId?: string,
projectName?: string
Expand Down Expand Up @@ -525,16 +525,17 @@ const projectStore = create<ProjectStore>()((set, get) => ({
*/
replayProject: (
taskIds: string[],
question: string = 'Replay task',
questions: string[] = [],
projectId?: string,
historyId?: string
) => {
const { projects, removeProject, createProject, createChatStore } = get();

const firstQuestion = questions[0] ?? 'Replay task';
let replayProjectId: string;

//TODO: For now handle the question as unique identifier to avoid duplicate
if (!projectId) projectId = 'Replay: ' + question;
if (!projectId) projectId = 'Replay: ' + firstQuestion;

// If projectId is provided, reset that project
if (projectId) {
Expand All @@ -544,16 +545,16 @@ const projectStore = create<ProjectStore>()((set, get) => ({
}
// Create project with the specific naming
replayProjectId = createProject(
`Replay Project ${question}`,
`Replayed project from ${question}`,
`Replay Project ${firstQuestion}`,
`Replayed project from ${firstQuestion}`,
projectId,
ProjectType.REPLAY,
historyId
);
} else {
// Create a new project only once
replayProjectId = createProject(
`Replay Project ${question}`,
`Replay Project ${firstQuestion}`,
`Replayed project with ${taskIds.length} tasks`,
projectId,
ProjectType.REPLAY,
Expand Down Expand Up @@ -591,7 +592,9 @@ const projectStore = create<ProjectStore>()((set, get) => ({

if (chatStore) {
try {
await chatStore.getState().replay(taskId, question, 0.2);
await chatStore
.getState()
.replay(taskId, questions[index] ?? firstQuestion, 0.2);
console.log(`[ProjectStore] Started replay for task ${taskId}`);
} catch (error) {
console.error(
Expand All @@ -614,7 +617,7 @@ const projectStore = create<ProjectStore>()((set, get) => ({

loadProjectFromHistory: async (
taskIds: string[],
question: string,
questions: string[],
projectId: string,
historyId?: string,
projectName?: string
Expand All @@ -628,7 +631,7 @@ const projectStore = create<ProjectStore>()((set, get) => ({
removeProject(projectId);
}

const displayName = projectName || question.slice(0, 50) || 'Project';
const displayName = projectName || questions[0]?.slice(0, 50) || 'Project';
const loadProjectId = createProject(
displayName,
`Loaded from history`,
Expand Down Expand Up @@ -661,7 +664,9 @@ const projectStore = create<ProjectStore>()((set, get) => ({
const chatStore = project.chatStores[chatId];
if (chatStore) {
try {
await chatStore.getState().replay(taskId, question, 0);
await chatStore
.getState()
.replay(taskId, questions[index] ?? questions[0] ?? '', 0);
console.log(`[ProjectStore] Loaded task ${taskId}`);
} catch (error) {
console.error(
Expand Down