fix: Retry sends message with retry flag to avoid duplicate storage
Added retry:true flag to sendChatMessage input. Server skips storing the user message when retry is set. Frontend uses a dedicated retryLastMessage function that skips the optimistic message add.
This commit is contained in:
@@ -26,6 +26,7 @@ export function chatSessionProcedures(publicProcedure: ProcedureBuilder) {
|
||||
targetId: z.string().min(1),
|
||||
initiativeId: z.string().min(1),
|
||||
message: z.string().min(1),
|
||||
retry: z.boolean().optional(),
|
||||
provider: z.string().optional(),
|
||||
}))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
@@ -49,18 +50,20 @@ export function chatSessionProcedures(publicProcedure: ProcedureBuilder) {
|
||||
});
|
||||
}
|
||||
|
||||
// Store user message
|
||||
await chatRepo.createMessage({
|
||||
chatSessionId: session.id,
|
||||
role: 'user',
|
||||
content: input.message,
|
||||
});
|
||||
// Store user message (skip on retry — message already exists)
|
||||
if (!input.retry) {
|
||||
await chatRepo.createMessage({
|
||||
chatSessionId: session.id,
|
||||
role: 'user',
|
||||
content: input.message,
|
||||
});
|
||||
|
||||
ctx.eventBus.emit({
|
||||
type: 'chat:message_created' as const,
|
||||
timestamp: new Date(),
|
||||
payload: { chatSessionId: session.id, role: 'user' as const },
|
||||
});
|
||||
ctx.eventBus.emit({
|
||||
type: 'chat:message_created' as const,
|
||||
timestamp: new Date(),
|
||||
payload: { chatSessionId: session.id, role: 'user' as const },
|
||||
});
|
||||
}
|
||||
|
||||
// Check if agent exists and is waiting for input
|
||||
if (session.agentId) {
|
||||
|
||||
@@ -42,7 +42,7 @@ function ChatSlideOverInner({
|
||||
initiativeId: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { messages, agentStatus, agentError, sendMessage, closeSession, isSending } =
|
||||
const { messages, agentStatus, agentError, sendMessage, retryLastMessage, closeSession, isSending } =
|
||||
useChatSession(target.type, target.id, initiativeId);
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
@@ -145,11 +145,7 @@ function ChatSlideOverInner({
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 gap-1.5 text-xs"
|
||||
onClick={() => {
|
||||
// Re-send the last user message to retry
|
||||
const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
|
||||
if (lastUserMsg) sendMessage(lastUserMsg.content);
|
||||
}}
|
||||
onClick={retryLastMessage}
|
||||
>
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
Retry
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface UseChatSessionResult {
|
||||
agentStatus: ChatAgentStatus;
|
||||
agentError: string | null;
|
||||
sendMessage: (message: string) => void;
|
||||
retryLastMessage: () => void;
|
||||
closeSession: () => void;
|
||||
isSending: boolean;
|
||||
isLoading: boolean;
|
||||
@@ -163,6 +164,21 @@ export function useChatSession(
|
||||
[targetType, targetId, initiativeId],
|
||||
);
|
||||
|
||||
const messagesRef = useRef(messages);
|
||||
messagesRef.current = messages;
|
||||
|
||||
const retryLastMessage = useCallback(() => {
|
||||
const lastUserMsg = [...messagesRef.current].reverse().find(m => m.role === 'user');
|
||||
if (!lastUserMsg) return;
|
||||
sendMutateRef.current({
|
||||
targetType,
|
||||
targetId,
|
||||
initiativeId,
|
||||
message: lastUserMsg.content,
|
||||
retry: true,
|
||||
});
|
||||
}, [targetType, targetId, initiativeId]);
|
||||
|
||||
const closeSession = useCallback(() => {
|
||||
const s = sessionRef.current;
|
||||
if (s) {
|
||||
@@ -179,6 +195,7 @@ export function useChatSession(
|
||||
agentStatus,
|
||||
agentError,
|
||||
sendMessage,
|
||||
retryLastMessage,
|
||||
closeSession,
|
||||
isSending,
|
||||
isLoading,
|
||||
|
||||
Reference in New Issue
Block a user