import { t } from "./i18n"; const API = "/api/v1/webchat"; export type WidgetFeatures = { voiceMessages: boolean; audioCalls: boolean; videoCalls: boolean }; export type WebConfig = { // Язык обвязки виджета: язык организации, на котором отвечают агент и // оператор. Пусто — язык решает браузер посетителя. language?: string; features?: WidgetFeatures; available: boolean; reason?: string; widgetKey?: string; title?: string; accent?: string; greeting?: string; consent?: { text: string; version: string }; quickReplies?: string[]; fallback?: { label: string; url: string }[]; }; // kind: "" — текст, "contact_request" — виджет рисует форму телефона, // "contact" — клиент поделился номером, "VOICE" — голосовое (см. hasAudio). export type WebAttachment = { name: string; contentType: string; size: number; available: boolean }; export type WebMessage = { id: number; author: "client" | "ai" | "operator" | "system"; kind?: string; text: string; createdAt: string; durationSeconds?: number; hasAudio?: boolean; attachment?: WebAttachment }; // Приглашение/состояние онлайн-звонка (SPEC-CHATBALLS-0013). export type CallKind = "AUDIO" | "VIDEO"; export type CallInfo = { callId: string; status: string; kind: CallKind; expiresAt?: string; staffName?: string; endedBy?: string | null; durationSeconds?: number | null; }; export function callKindOf(call: CallInfo | null | undefined): CallKind | null { return call?.kind === "AUDIO" || call?.kind === "VIDEO" ? call.kind : null; } export const isVideoCall = (call: CallInfo | null | undefined): boolean => callKindOf(call) === "VIDEO"; export type CallBootstrap = { call: CallInfo; accessToken: string; iceServers: RTCIceServer[] }; export type CallStateEnvelope = { call: CallInfo; iceServers: RTCIceServer[] }; export type Poll = { state: "ai" | "operator" | "waiting"; lifecycle: string; messages: WebMessage[]; call?: CallInfo | null }; export type WidgetEntry = { widgetKey?: string; channel?: string }; function entryQuery(entry: WidgetEntry): string { if (entry.widgetKey) return `widgetKey=${encodeURIComponent(entry.widgetKey)}`; return `channel=${encodeURIComponent(entry.channel ?? "")}`; } export async function getConfig(entry: WidgetEntry, hostOrigin: string): Promise { const r = await fetch(`${API}/config/?${entryQuery(entry)}&hostOrigin=${encodeURIComponent(hostOrigin)}`); return r.json(); } export async function startSession(entry: WidgetEntry, hostOrigin: string): Promise { const r = await fetch(`${API}/session/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...entry, hostOrigin }), }); if (!r.ok) return null; return (await r.json()).token as string; } export async function sendMessage(token: string, text: string): Promise { const r = await fetch(`${API}/messages/`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ text }), }); return r.ok; } export async function sendVoice(token: string, audio: Blob, durationSeconds: number): Promise { const body = new FormData(); const type = (audio.type || "audio/webm").split(";")[0]; body.append("audio", audio, `voice.${type.split("/")[1] || "webm"}`); body.append("duration", String(Math.round(durationSeconds))); const r = await fetch(`${API}/messages/`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body }); return r.ok; } export const MAX_FILE_BYTES = 20 * 1024 * 1024; // Файл или фото из виджета: multipart file + подпись text. export async function sendFile(token: string, file: File, caption: string): Promise { const body = new FormData(); body.append("file", file, file.name); if (caption) body.append("text", caption); const r = await fetch(`${API}/messages/`, { method: "POST", headers: { Authorization: `Bearer ${token}` }, body }); return r.ok; } // URL вложения: / не умеют заголовки — токен идёт параметром. export function attachmentUrl(token: string, messageId: number, inline = false): string { return `${API}/messages/${messageId}/attachment/?token=${encodeURIComponent(token)}${inline ? "&inline" : ""}`; } // URL аудио голосового: