Merge remote-tracking branch 'origin/staging' into add-report-to-collection-functionality

This commit is contained in:
dal 2025-08-22 10:05:24 -06:00
commit 13a1d38437
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
5 changed files with 62 additions and 82 deletions

View File

@ -1,7 +1,5 @@
import { queryKeys } from '@/api/query_keys';
import { ShimmerText } from '@/components/ui/typography/ShimmerText'; import { ShimmerText } from '@/components/ui/typography/ShimmerText';
import { cn } from '@/lib/classMerge'; import { cn } from '@/lib/classMerge';
import { useQuery } from '@tanstack/react-query';
export const GeneratingContent = ({ export const GeneratingContent = ({
messageId, messageId,
@ -10,18 +8,9 @@ export const GeneratingContent = ({
messageId: string; messageId: string;
className?: string; className?: string;
}) => { }) => {
const { data: text } = useQuery({
...queryKeys.chatsBlackBoxMessages(messageId),
notifyOnChangeProps: ['data'],
select: (data) => data
});
return ( return (
<div <div className={cn('right-0 bottom-0 left-0 -mt-68', className)}>
className={cn('right-0 bottom-0 left-0 -mt-68 flex items-center justify-center', className)}> <ShimmerText text="Generating..." className="text-lg" />
<div className="border-border item-center flex w-full justify-center rounded border px-8 py-1.5 shadow">
<ShimmerText text={text || 'Generating content...'} className="text-lg" />
</div>
</div> </div>
); );
}; };

View File

@ -11,6 +11,9 @@ import { ReportEditorSkeleton } from '@/components/ui/report/ReportEditorSkeleto
import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext'; import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext';
import { useTrackAndUpdateReportChanges } from '@/api/buster-electric/reports/hooks'; import { useTrackAndUpdateReportChanges } from '@/api/buster-electric/reports/hooks';
import { GeneratingContent } from './GeneratingContent'; import { GeneratingContent } from './GeneratingContent';
import { useQuery } from '@tanstack/react-query';
import { queryKeys } from '@/api/query_keys';
import type { BusterChatMessage } from '@/api/asset_interfaces/chat';
export const ReportPageController: React.FC<{ export const ReportPageController: React.FC<{
reportId: string; reportId: string;
@ -24,8 +27,25 @@ export const ReportPageController: React.FC<{
const isStreamingMessage = useChatIndividualContextSelector((x) => x.isStreamingMessage); const isStreamingMessage = useChatIndividualContextSelector((x) => x.isStreamingMessage);
const messageId = useChatIndividualContextSelector((x) => x.currentMessageId); const messageId = useChatIndividualContextSelector((x) => x.currentMessageId);
// Fetch the current message to check which files are being generated
const { data: currentMessage } = useQuery<BusterChatMessage>({
...queryKeys.chatsMessages(messageId || ''),
enabled: !!messageId && isStreamingMessage
});
// Check if this specific report is being generated in the current message
const isThisReportBeingGenerated = React.useMemo(() => {
if (!currentMessage || !isStreamingMessage || !messageId) return false;
// Check if the current report ID matches any file being generated
const responseMessages = Object.values(currentMessage.response_messages || {});
return responseMessages.some(
(msg) => msg.type === 'file' && msg.file_type === 'report' && msg.id === reportId
);
}, [currentMessage, isStreamingMessage, messageId, reportId]);
const content = report?.content || ''; const content = report?.content || '';
const showGeneratingContent = messageId && isStreamingMessage; const showGeneratingContent = isThisReportBeingGenerated;
const commonClassName = 'sm:px-[max(64px,calc(50%-350px))]'; const commonClassName = 'sm:px-[max(64px,calc(50%-350px))]';
const { mutate: updateReport } = useUpdateReport(); const { mutate: updateReport } = useUpdateReport();

View File

@ -219,9 +219,6 @@ export function createCreateReportsDelta(context: CreateReportsContext, state: C
// Update report content for all reports that have content // Update report content for all reports that have content
if (contentUpdates.length > 0) { if (contentUpdates.length > 0) {
// Track response messages to create in batch
const responseMessagesToCreate: ChatMessageResponseMessage[] = [];
for (const update of contentUpdates) { for (const update of contentUpdates) {
try { try {
await updateReportContent({ await updateReportContent({
@ -229,37 +226,16 @@ export function createCreateReportsDelta(context: CreateReportsContext, state: C
content: update.content, content: update.content,
}); });
// Mark the file as completed in state // Keep the file status as 'loading' during streaming
// Status will be updated to 'completed' in the execute phase
const stateFile = state.files?.find((f) => f.id === update.reportId); const stateFile = state.files?.find((f) => f.id === update.reportId);
if (stateFile) { if (stateFile) {
stateFile.status = 'completed'; // Ensure status remains 'loading' during delta phase
stateFile.status = 'loading';
}
// Create response message for this report if not already created // Note: Response messages should only be created in execute phase
if (!state.responseMessagesCreated?.has(update.reportId)) { // after all processing is complete
// Create response message for this report
responseMessagesToCreate.push({
id: update.reportId,
type: 'file' as const,
file_type: 'report' as const,
file_name: stateFile.file_name || '',
version_number: stateFile.version_number || 1,
filter_version_id: null,
metadata: [
{
status: 'completed' as const,
message: 'Report created successfully',
timestamp: Date.now(),
},
],
});
// Track that we've created a response message for this report
if (!state.responseMessagesCreated) {
state.responseMessagesCreated = new Set<string>();
}
state.responseMessagesCreated.add(update.reportId);
}
}
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Content update failed'; const errorMessage = error instanceof Error ? error.message : 'Content update failed';
console.error('[create-reports] Error updating report content:', { console.error('[create-reports] Error updating report content:', {
@ -268,35 +244,15 @@ export function createCreateReportsDelta(context: CreateReportsContext, state: C
stack: error instanceof Error ? error.stack : undefined, stack: error instanceof Error ? error.stack : undefined,
}); });
// Mark the file as failed in state with error message // Keep file as loading during delta phase even on error
// The execute phase will handle final status
const stateFile = state.files?.find((f) => f.id === update.reportId); const stateFile = state.files?.find((f) => f.id === update.reportId);
if (stateFile) { if (stateFile) {
stateFile.status = 'failed'; stateFile.status = 'loading';
stateFile.error = `Failed to update report content: ${errorMessage}`; stateFile.error = `Failed to update report content: ${errorMessage}`;
} }
} }
} }
// Update database with response messages if we have any
if (responseMessagesToCreate.length > 0 && context.messageId) {
try {
await updateMessageEntries({
messageId: context.messageId,
responseMessages: responseMessagesToCreate,
});
console.info('[create-reports] Created response messages during delta', {
count: responseMessagesToCreate.length,
reportIds: responseMessagesToCreate.map((m) => m.id),
});
} catch (error) {
console.error(
'[create-reports] Error creating response messages during delta:',
error
);
// Don't throw - continue processing
}
}
} }
} }
} }

View File

@ -46,15 +46,28 @@ export function createModifyReportsReasoningEntry(
let status: 'loading' | 'completed' | 'failed' = 'loading'; let status: 'loading' | 'completed' | 'failed' = 'loading';
let secondaryTitle: string | undefined; let secondaryTitle: string | undefined;
// Check if modification is complete based on state // Check if all edits have a final status (completed or failed), not just 'loading'
if (state.finalContent !== undefined) { const allEditsComplete =
state.edits && state.edits.length > 0
? state.edits.every((edit) => edit.status === 'completed' || edit.status === 'failed')
: false;
// Only mark as complete when all edits are actually done, not during streaming
if (allEditsComplete) {
// Check if any edits failed // Check if any edits failed
const hasFailedEdits = state.edits?.some((edit) => edit.status === 'failed') ?? false; const hasFailedEdits = state.edits?.some((edit) => edit.status === 'failed') ?? false;
if (hasFailedEdits) { if (hasFailedEdits) {
title = 'Failed to modify report'; title = 'Failed to modify report';
status = 'failed'; status = 'failed';
} else if (state.finalContent) { // Update the file status in filesRecord
if (state.reportId) {
const file = filesRecord[state.reportId];
if (file) {
file.status = 'failed';
}
}
} else {
title = 'Modified 1 report'; title = 'Modified 1 report';
status = 'completed'; status = 'completed';
// Update the file status in filesRecord // Update the file status in filesRecord
@ -66,14 +79,15 @@ export function createModifyReportsReasoningEntry(
} }
} }
// Only show elapsed time when all edits are complete (not during streaming) // Show elapsed time when complete
// Check if all edits have a final status (completed or failed), not just 'loading'
const allEditsComplete =
state.edits?.every((edit) => edit.status === 'completed' || edit.status === 'failed') ??
false;
if (allEditsComplete) {
secondaryTitle = formatElapsedTime(state.startTime); secondaryTitle = formatElapsedTime(state.startTime);
} else {
// Keep file status as loading during streaming
if (state.reportId) {
const file = filesRecord[state.reportId];
if (file) {
file.status = 'loading';
}
} }
} }

View File

@ -127,6 +127,7 @@ export async function updateMessageEntries({
WITH new_messages AS ( WITH new_messages AS (
SELECT SELECT
value, value,
ordinality as input_order,
value->>'role' AS role, value->>'role' AS role,
COALESCE( COALESCE(
CASE CASE
@ -138,7 +139,7 @@ export async function updateMessageEntries({
END, END,
'' ''
) AS tool_calls ) AS tool_calls
FROM jsonb_array_elements(${newData}::jsonb) AS value FROM jsonb_array_elements(${newData}::jsonb) WITH ORDINALITY AS t(value, ordinality)
), ),
existing_messages AS ( existing_messages AS (
SELECT SELECT
@ -170,8 +171,8 @@ export async function updateMessageEntries({
WHERE n.role = e.role AND n.tool_calls = e.tool_calls WHERE n.role = e.role AND n.tool_calls = e.tool_calls
) )
UNION ALL UNION ALL
-- Add all new messages -- Add all new messages, preserving their input order
SELECT n.value, 1000000 + row_number() OVER () AS ord SELECT n.value, 1000000 + n.input_order AS ord
FROM new_messages n FROM new_messages n
) combined ) combined
) )