godcrm/components/AIChatPanel/hooks/useMessageSubmit.ts
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

160 lines
8.8 KiB
TypeScript

/**
* useMessageSubmit — Handles message submission logic for AI agents and user/group chats.
* Extracted from AIChatPanel.tsx handleSubmit (lines 1873-2042).
*/
import { useCallback, useRef, type FormEvent } from 'react';
import { logger } from '@/shared/utils/logger';
import { useMutation } from '@tanstack/react-query';
import { apiClient } from '@/shared/utils/apiClient';
import { filesApi } from '@/features/files/api/filesApi';
import { validateAndWrapMentions, validateAndWrapCommands } from '../../../utils/invocationTokens';
import type { MentionUser } from '../../MentionInput';
import type { BoundRow } from '../../RowBindingV2';
import type { ApiResponse } from '../../AIChatPanel.types';
interface UseMessageSubmitParams {
inputValue: string;
setInputValue: (v: string) => void;
attachments: File[];
setAttachments: (fn: (prev: File[]) => File[]) => void;
mentionedUsers: MentionUser[];
setMentionedUsers: (v: MentionUser[]) => void;
messageBoundRows: BoundRow[];
setMessageBoundRows: (v: BoundRow[]) => void;
chatPartner: { type: string; id: number; name: string; participants?: Array<{ id: number }> } | null;
currentAgent: { id: number } | null;
agentMode: string;
thinkingEnabled: boolean;
subAgents: number[];
sendMessage: (content: string, files?: File[], extra1?: unknown, mentions?: Array<{ id: number; name: string; type: string }>, useAgent?: boolean, extra2?: unknown, subAgents?: number[], rowAtts?: unknown[]) => Promise<void>;
userConversationId: number | null;
setUserConversationId: (id: number | null) => void;
getOrCreateUserConversation: (participantIds: number[]) => Promise<number>;
refetchUserMessages: () => void;
effectiveSpaceId: number | string | undefined;
setLocalError: (error: string | null) => void;
availableMentionUsers: MentionUser[];
availableSlashAgents: MentionUser[];
currentSpaceId: number | string | undefined;
}
export function useMessageSubmit(params: UseMessageSubmitParams) {
const {
inputValue, setInputValue, attachments, setAttachments,
mentionedUsers, setMentionedUsers, messageBoundRows, setMessageBoundRows,
chatPartner, currentAgent, agentMode, thinkingEnabled, subAgents,
sendMessage, userConversationId, setUserConversationId,
getOrCreateUserConversation, refetchUserMessages,
effectiveSpaceId, setLocalError, availableMentionUsers, availableSlashAgents,
} = params;
const isSendingRef = useRef(false);
// Send message in user conversation mutation
const sendUserMessageMutation = useMutation({
mutationFn: async ({ conversationId, content, agentMode: mode, thinking, mentions, attachments: msgAttachments }: {
conversationId: number; content: string;
agentMode?: 'ask' | 'read' | 'agent'; thinking?: boolean;
mentions?: Array<{ user_id: number; name?: string; type?: string }>;
attachments?: Array<{ id: string; name: string; type: string; size: number; url?: string; preview?: string; rowReference?: { table_id: number; row_id: number; table_name: string; table_icon?: string; row_title?: string } }>;
}) => {
const response = await apiClient.post<ApiResponse<{ id: number; content: string; sender_id: number }>>(`/chat/conversations/${conversationId}/messages`, {
content, content_type: 'text',
...(mode && { agent_mode: mode }),
...(thinking !== undefined && { thinking_enabled: thinking }),
...(mentions && mentions.length > 0 && { mentions }),
...(msgAttachments && msgAttachments.length > 0 && {
attachments: msgAttachments.map(a => ({ name: a.name, type: a.type, size: a.size, url: a.url, ...(a.rowReference && { rowReference: a.rowReference }) }))
}),
});
return response?.data;
},
onSuccess: () => { refetchUserMessages(); }
});
const handleSubmit = useCallback(async (e?: FormEvent) => {
e?.preventDefault();
let trimmedInput = inputValue.trim();
trimmedInput = validateAndWrapMentions(trimmedInput, availableMentionUsers);
trimmedInput = validateAndWrapCommands(trimmedInput, availableSlashAgents || []);
if (!trimmedInput && attachments.length === 0 && messageBoundRows.length === 0) return;
if (isSendingRef.current) {
logger.debug('[Chat v2] Blocked duplicate send');
return;
}
isSendingRef.current = true;
setLocalError(null);
try {
if (chatPartner?.type === 'agent' && currentAgent) {
const filesToSend = [...attachments];
const mentionsToSend = mentionedUsers.length > 0 ? mentionedUsers.map(u => ({ id: u.id, name: u.name, type: u.type })) : undefined;
const rowAtts = messageBoundRows.length > 0 ? messageBoundRows.map(br => ({
id: `row_${br.table_id}_${br.row_id}`, name: br.row_title || `Row #${br.row_id}`, type: 'row_reference', size: 0,
rowReference: { table_id: br.table_id, row_id: br.row_id, table_name: br.table_name || '', table_icon: br.table_icon, row_title: br.row_title }
})) : undefined;
setInputValue(''); setAttachments(() => []); setMentionedUsers([]); setMessageBoundRows([]);
const useAgentMode = agentMode === 'agent';
await sendMessage(trimmedInput, filesToSend.length > 0 ? filesToSend : undefined, undefined, mentionsToSend, useAgentMode, undefined, subAgents.length > 0 ? subAgents : undefined, rowAtts);
return;
}
if (chatPartner?.type === 'user' || chatPartner?.type === 'group') {
try {
const participantIds = chatPartner.type === 'group'
? (chatPartner.participants?.map(p => p.id) || [])
: [chatPartner.id];
let convId = userConversationId;
if (!convId) { convId = await getOrCreateUserConversation(participantIds); setUserConversationId(convId); }
const hasAgentTrigger = /(^|\s)[/@][a-z0-9_-]+/i.test(trimmedInput);
const userMentions = mentionedUsers.length > 0 ? mentionedUsers.map(u => ({ user_id: u.id, name: u.name, type: u.type })) : undefined;
let uploadedAttachments: Array<{ id: string; name: string; type: string; size: number; url?: string; preview?: string }> | undefined;
if (attachments.length > 0) {
try {
const uploadResult = await filesApi.upload([...attachments], { spaceId: effectiveSpaceId });
const uploadedFiles = Array.isArray(uploadResult) ? uploadResult : [uploadResult];
uploadedAttachments = attachments.map((file, idx) => {
const uploaded = uploadedFiles[idx];
return { id: uploaded?.id || `att_${Date.now()}_${file.name}`, name: file.name, type: file.type, size: file.size, url: uploaded?.url || '', preview: file.type.startsWith('image/') ? (uploaded?.url || URL.createObjectURL(file)) : undefined };
});
} catch {
uploadedAttachments = attachments.map(file => ({ id: `att_${Date.now()}_${file.name}`, name: file.name, type: file.type, size: file.size, preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined }));
}
}
const rowRefAttachments = messageBoundRows.length > 0 ? messageBoundRows.map(br => ({
id: `row_${br.table_id}_${br.row_id}`, name: br.row_title || `Row #${br.row_id}`, type: 'row_reference', size: 0,
rowReference: { table_id: br.table_id, row_id: br.row_id, table_name: br.table_name || '', table_icon: br.table_icon, row_title: br.row_title }
})) : [];
const allAttachments = [...(uploadedAttachments || []), ...rowRefAttachments];
setInputValue(''); setAttachments(() => []); setMentionedUsers([]); setMessageBoundRows([]);
await sendUserMessageMutation.mutateAsync({
conversationId: convId, content: trimmedInput,
...(hasAgentTrigger && { agentMode: agentMode as 'ask' | 'read' | 'agent', thinking: thinkingEnabled }),
...(userMentions && { mentions: userMentions }),
...(allAttachments.length > 0 && { attachments: allAttachments }),
});
} catch (error) {
logger.error('[Chat v2] Error sending user message:', error);
setLocalError('Ошибка отправки сообщения');
}
return;
}
if (!currentAgent && !chatPartner) {
setLocalError('Выберите агента или пользователя для чата');
}
} finally {
isSendingRef.current = false;
}
}, [inputValue, attachments, messageBoundRows, chatPartner, currentAgent, agentMode, thinkingEnabled, subAgents, mentionedUsers, availableMentionUsers, availableSlashAgents, userConversationId, effectiveSpaceId, sendMessage, getOrCreateUserConversation, setInputValue, setAttachments, setMentionedUsers, setMessageBoundRows, setUserConversationId, setLocalError, sendUserMessageMutation, refetchUserMessages]);
return {
handleSubmit,
sendUserMessageMutation,
};
}