Files
DeskcommCRM/hooks/ai/useAgentVersions.ts
T
webtecnica 4a8a65bcdd feat(agent): janela de rajada do WhatsApp configurável por agente (0498)
O debounce de rajada (mensagens do MESMO contato dentro da janela viram uma
resposta só) era só a env do worker INBOUND_DEBOUNCE_MS (default 8000, global,
invisível na tela, exige VPS). Agora vira campo opcional por agente em
'Freios de segurança': vazio = usa a env da instalação (regressão zero),
0 desliga a coalescência, teto de 60s clampa pelo leitor (debounceEfetivo).

O drain lê a config do agente da conversa (loadConversationAgentConfig, mesma
resolução do turno) e usa o debounce do agente no lugar do literal.

DoD: migration 0498 + apêndice idempotente no baseline + linha no MANIFEST;
leitor testado (default/env, campo, teto no limite); gate de consumidor de
knob atendido.
2026-09-30 10:03:31 -03:00

77 lines
2.3 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "@/lib/api/client";
export type Provider = "anthropic" | "openai" | "google";
export interface AgentVersionRow {
id: string;
organization_id: string;
agent_id: string;
version_number: number;
system_prompt: string;
provider: Provider;
model: string;
credential_id: string;
tool_ids: string[];
trigger_config: Record<string, unknown> | null;
channel_session_id: string;
max_steps: number;
token_budget: number;
cost_budget_cents: number;
history_message_window: number;
history_token_window: number;
handoff_keywords: string[];
handoff_tool_enabled: boolean;
proposal_ai_draft_enabled: boolean;
cases_enabled: boolean;
operator_enabled: boolean;
operator_model: string | null;
operator_tool_ids: string[];
pipeline_ids: string[];
knowledge_source_ids: string[];
split_messages: boolean;
split_max_chars: number;
inbound_debounce_ms: number | null;
followup: {
enabled: boolean;
flow_pointer_ids: string[];
callback_enabled?: boolean;
send_window?: { start: string; end: string; weekdays: number[] } | null;
};
status: "draft" | "published" | "superseded" | "archived";
published_at: string | null;
superseded_at: string | null;
created_at: string;
created_by: string | null;
}
interface ListResponse {
data: AgentVersionRow[];
}
export const agentVersionsKey = (agentId: string) =>
["ai", "agents", agentId, "versions"] as const;
export function useAgentVersions(agentId: string, opts?: { initialData?: AgentVersionRow[] }) {
return useQuery({
queryKey: agentVersionsKey(agentId),
queryFn: async () => {
const res = await apiClient.get<ListResponse>(`/api/v1/ai/agents/${agentId}/versions`);
return res.data;
},
initialData: opts?.initialData,
enabled: !!agentId,
});
}
export function pickLatestDraft(versions: AgentVersionRow[]): AgentVersionRow | null {
const drafts = versions.filter((v) => v.status === "draft");
if (drafts.length === 0) return null;
return drafts.reduce((a, b) => (a.version_number > b.version_number ? a : b));
}
export function pickPublished(versions: AgentVersionRow[]): AgentVersionRow | null {
return versions.find((v) => v.status === "published") ?? null;
}