up
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
// Shared helpers for chat UIs
|
||||
|
||||
async function apiCall(endpoint, options = {}) {
|
||||
const defaultOptions = { method: 'GET', headers: { 'Content-Type': 'application/json' } };
|
||||
const finalOptions = { ...defaultOptions, ...options };
|
||||
if (options.body && typeof options.body === 'object') {
|
||||
finalOptions.method = 'POST';
|
||||
finalOptions.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
let url = endpoint;
|
||||
if (!/^https?:\/\//i.test(url) && !url.startsWith('api/')) url = 'api/' + url;
|
||||
|
||||
const resp = await fetch(url, finalOptions);
|
||||
const text = await resp.text();
|
||||
|
||||
if (!resp.ok) {
|
||||
const snippet = text && text.length ? (text.length > 2000 ? text.substr(0,2000)+'... (truncated)' : text) : '<no body>';
|
||||
console.error('apiCall HTTP error', resp.status, url, snippet);
|
||||
throw new Error('HTTP error! status: ' + resp.status + ' -- ' + snippet);
|
||||
}
|
||||
|
||||
if (!text || text.trim() === '') return null;
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (err) {
|
||||
console.error('apiCall parse error for', url, err);
|
||||
const snippet = text.length > 1000 ? text.substr(0,1000) : text;
|
||||
throw new Error('Invalid JSON response from ' + url + ': ' + err.message + ' -- response snippet: ' + snippet);
|
||||
}
|
||||
}
|
||||
|
||||
function isValidMediaUrl(u) {
|
||||
return (typeof u === 'string') && u.trim() !== '' && u !== 'null' && u !== 'undefined';
|
||||
}
|
||||
|
||||
function ensureProxyUrl(u, download = false) {
|
||||
if (!isValidMediaUrl(u)) return '';
|
||||
if (u.startsWith('/') || u.indexOf('/api/version/media-url.php') === 0) return u;
|
||||
try {
|
||||
const parsed = new URL(u);
|
||||
const host = parsed.host.toLowerCase();
|
||||
if (host.includes('lookaside.fbsbx.com') || host.includes('facebook.com') || host.includes('graph.facebook.com')) {
|
||||
return `/api/version/media-url.php?url=${encodeURIComponent(u)}${download ? '&download=1' : ''}`;
|
||||
}
|
||||
} catch (e) {}
|
||||
return u;
|
||||
}
|
||||
|
||||
function renderMediaMessage(msg) {
|
||||
if (!msg) return '';
|
||||
const mediaType = msg.message_type || msg.media_type || 'text';
|
||||
const content = msg.content || '';
|
||||
const mediaExternal = msg.media_url_external || msg.media_url || '';
|
||||
const localFile = msg.local_file || '';
|
||||
const localThumb = msg.local_thumb || '';
|
||||
|
||||
const effective = isValidMediaUrl(mediaExternal) ? mediaExternal : (isValidMediaUrl(localFile) ? `/`+localFile : (isValidMediaUrl(localThumb) ? `/`+localThumb : (isValidMediaUrl(msg.media_url) ? msg.media_url : '')));
|
||||
|
||||
switch (mediaType) {
|
||||
case 'image': {
|
||||
const thumb = effective || '';
|
||||
const full = isValidMediaUrl(localFile) ? `/api/version/media-url.php?local=${encodeURIComponent(localFile)}` : (isValidMediaUrl(mediaExternal) ? `/api/version/media-url.php?url=${encodeURIComponent(mediaExternal)}` : (msg.media_url || ''));
|
||||
if (thumb) {
|
||||
return `<div class="message-media"><a href="#" data-full="${escapeHtml(full)}" onclick="openImageLightbox(this.dataset.full); return false;"><img src="${escapeHtml(thumb)}" alt="Imagen" style="cursor:zoom-in"></a></div>${content?`<div>${escapeHtml(content)}</div>`:''}`;
|
||||
}
|
||||
return `<div class="text-muted"><em>Imagen no disponible</em></div>`;
|
||||
}
|
||||
case 'video': {
|
||||
const src = ensureProxyUrl(effective || msg.media_url || '');
|
||||
if (src) return `<div class="message-media"><video controls><source src="${escapeHtml(src)}" type="video/mp4">Tu navegador no soporta video.</video></div>${content?`<div>${escapeHtml(content)}</div>`:''}`;
|
||||
return `<div class="text-muted"><em>Video no disponible</em></div>`;
|
||||
}
|
||||
case 'audio': {
|
||||
const src = ensureProxyUrl(effective || msg.media_url || '');
|
||||
if (src) return `<div class="message-media"><audio controls><source src="${escapeHtml(src)}"></audio></div>`;
|
||||
return `<div class="text-muted"><em>Audio no disponible</em></div>`;
|
||||
}
|
||||
case 'document': {
|
||||
const filename = msg.filename || content || 'Documento';
|
||||
let docUrl = '';
|
||||
if (isValidMediaUrl(mediaExternal) && mediaExternal.indexOf('api/get_media.php?id=') !== -1) {
|
||||
const parts = mediaExternal.split('id=');
|
||||
docUrl = `/api/version/media-url.php?id=${encodeURIComponent(parts[1])}&download=1`;
|
||||
} else if (isValidMediaUrl(mediaExternal) && /^https?:\/\//i.test(mediaExternal)) {
|
||||
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(mediaExternal)}&download=1`;
|
||||
} else if (isValidMediaUrl(localFile)) {
|
||||
docUrl = `/api/version/media-url.php?local=${encodeURIComponent(localFile)}&download=1`;
|
||||
}
|
||||
if (docUrl) return `<a href="${docUrl}" class="message-document" target="_blank" rel="noopener noreferrer"><i class="fas fa-file-pdf"></i><span>${escapeHtml(filename)}</span></a>`;
|
||||
return `<div class="message-document" style="opacity:0.6;"><i class="fas fa-file-pdf"></i><span>${escapeHtml(filename)} (No disponible)</span></div>`;
|
||||
}
|
||||
default:
|
||||
return escapeHtml(content || '');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTemplatesInto(selector) {
|
||||
try {
|
||||
const resp = await apiCall('check_templates.php');
|
||||
const sel = document.querySelector(selector);
|
||||
if (!sel) return;
|
||||
sel.innerHTML = '<option value="">Seleccionar plantilla...</option>';
|
||||
if (resp && resp.templates && Array.isArray(resp.templates)) {
|
||||
resp.templates.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = t.name;
|
||||
const lang = t.language_code || t.language || 'en_US';
|
||||
opt.textContent = `${t.display_name || t.name} (${lang})`;
|
||||
opt.dataset.language = lang;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('loadTemplatesInto error', e);
|
||||
}
|
||||
}
|
||||
|
||||
// FFmpeg helpers (client-side conversion)
|
||||
let _ffmpegInstance = null;
|
||||
let _ffmpegLoaded = false;
|
||||
async function ensureFFmpeg() {
|
||||
if (_ffmpegLoaded) return;
|
||||
if (typeof WebAssembly === 'undefined') throw new Error('WebAssembly no disponible');
|
||||
if (!window.FFmpeg || !window.FFmpeg.createFFmpeg) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://unpkg.com/@ffmpeg/ffmpeg@0.11.8/dist/ffmpeg.min.js';
|
||||
s.onload = resolve; s.onerror = reject; document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
const { createFFmpeg, fetchFile } = window.FFmpeg;
|
||||
_ffmpegInstance = createFFmpeg({ log: false });
|
||||
await _ffmpegInstance.load();
|
||||
_ffmpegInstance._fetchFile = fetchFile;
|
||||
_ffmpegLoaded = true;
|
||||
}
|
||||
|
||||
async function convertWebmToOgg(file) {
|
||||
await ensureFFmpeg();
|
||||
const inName = 'input.webm';
|
||||
const outName = 'output.ogg';
|
||||
let data;
|
||||
if (_ffmpegInstance._fetchFile) data = await _ffmpegInstance._fetchFile(file);
|
||||
else data = new Uint8Array(await file.arrayBuffer());
|
||||
_ffmpegInstance.FS('writeFile', inName, data);
|
||||
await _ffmpegInstance.run('-i', inName, '-c:a', 'libopus', '-b:a', '64k', outName);
|
||||
const outData = _ffmpegInstance.FS('readFile', outName);
|
||||
const blob = new Blob([outData.buffer], { type: 'audio/ogg' });
|
||||
const newFile = new File([blob], (file.name || 'audio').replace(/\.[^/.]+$/, '') + '.ogg', { type: 'audio/ogg' });
|
||||
return newFile;
|
||||
}
|
||||
|
||||
// Expose
|
||||
window.apiCall = apiCall;
|
||||
window.isValidMediaUrl = isValidMediaUrl;
|
||||
window.ensureProxyUrl = ensureProxyUrl;
|
||||
window.renderMediaMessage = renderMediaMessage;
|
||||
window.loadTemplatesInto = loadTemplatesInto;
|
||||
window.ensureFFmpeg = ensureFFmpeg;
|
||||
window.convertWebmToOgg = convertWebmToOgg;
|
||||
|
||||
// small utility
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return (''+text).replace(/[&<>"']/g, function(m){ return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[m]; });
|
||||
}
|
||||
window.escapeHtml = escapeHtml;
|
||||
+191
-41
@@ -579,9 +579,28 @@
|
||||
<button class="btn" id="attach-btn" title="Adjuntar archivo">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</button>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<select id="message-type" class="form-select form-select-sm" style="width:auto; margin-right:8px;">
|
||||
<option value="text">Texto</option>
|
||||
<option value="template">Plantilla</option>
|
||||
</select>
|
||||
<div id="templateSelectContainer" style="display:none; margin-right:8px;">
|
||||
<select id="templateSelect" class="form-select form-select-sm">
|
||||
<option value="">Seleccionar plantilla...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="quick-replies" id="quick-replies" style="display:flex;gap:6px;align-items:center;">
|
||||
<!-- Quick replies buttons inserted dynamically -->
|
||||
</div>
|
||||
<button class="btn" id="mic-btn" title="Grabar audio" style="margin-left:6px; background:#f8f9fa; border:1px solid #ddd;">
|
||||
<i class="fas fa-microphone"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="recording-indicator" style="display:none; padding:8px; background:#fff3cd; border-radius:6px; margin-top:8px;">
|
||||
<i class="fas fa-microphone text-danger"></i> Grabando... <span id="recording-time">0:00</span>
|
||||
<button class="btn btn-sm btn-danger" id="stop-recording" style="margin-left:8px;">Detener</button>
|
||||
<button class="btn btn-sm btn-secondary" id="cancel-recording" style="margin-left:6px;">Cancelar</button>
|
||||
</div>
|
||||
<input type="text" placeholder="Escribe un mensaje..." id="message-input" maxlength="4096">
|
||||
<button class="btn" id="send-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
@@ -592,6 +611,7 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/chat-common.js"></script>
|
||||
<script>
|
||||
class WhatsAppChat {
|
||||
constructor() {
|
||||
@@ -727,6 +747,32 @@
|
||||
this.sendMessage();
|
||||
});
|
||||
|
||||
// Message type (text/template)
|
||||
const typeSelect = document.getElementById('message-type');
|
||||
if (typeSelect) {
|
||||
typeSelect.addEventListener('change', (e) => {
|
||||
const tplContainer = document.getElementById('templateSelectContainer');
|
||||
tplContainer.style.display = (e.target.value === 'template') ? 'inline-block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Mic / recording
|
||||
const micBtn = document.getElementById('mic-btn');
|
||||
if (micBtn) {
|
||||
micBtn.addEventListener('click', () => {
|
||||
if (this._mediaRecorder && this._mediaRecorder.state === 'recording') {
|
||||
this.stopRecording();
|
||||
} else {
|
||||
this.startRecording();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const stopRecBtn = document.getElementById('stop-recording');
|
||||
if (stopRecBtn) stopRecBtn.addEventListener('click', () => this.stopRecording());
|
||||
const cancelRecBtn = document.getElementById('cancel-recording');
|
||||
if (cancelRecBtn) cancelRecBtn.addEventListener('click', () => this.cancelRecording());
|
||||
|
||||
document.getElementById('message-input').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
@@ -1226,8 +1272,86 @@
|
||||
chatContainer._infiniteScrollAdded = true;
|
||||
}
|
||||
}
|
||||
// Cargar respuestas rápidas
|
||||
// Cargar respuestas rápidas y plantillas
|
||||
await this.loadQuickReplies();
|
||||
// templates loaded into select by loadQuickReplies
|
||||
// show template container only if templates exist
|
||||
const tplContainer = document.getElementById('templateSelectContainer');
|
||||
if (tplContainer && document.getElementById('templateSelect').children.length) {
|
||||
tplContainer.style.display = (document.getElementById('message-type').value === 'template') ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
// Recording support state
|
||||
this._mediaRecorder = null;
|
||||
this._recordingInterval = null;
|
||||
this._recordingStart = null;
|
||||
|
||||
// Recording handlers
|
||||
this.startRecording = async function() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
this._mediaRecorder = new MediaRecorder(stream);
|
||||
const chunks = [];
|
||||
this._mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
|
||||
this._mediaRecorder.onstop = async () => {
|
||||
const blob = new Blob(chunks, { type: 'audio/webm' });
|
||||
let file = new File([blob], `record_${Date.now()}.webm`, { type: 'audio/webm' });
|
||||
// Try convert if FFmpeg available
|
||||
try {
|
||||
if (window && window.ensureFFmpeg) {
|
||||
await window.ensureFFmpeg();
|
||||
const converted = await window.convertWebmToOgg(file);
|
||||
file = converted;
|
||||
}
|
||||
} catch (convErr) { console.warn('conversion failed', convErr); }
|
||||
|
||||
// set as selected file and show preview
|
||||
this.selectedFile = file;
|
||||
this.showMediaPreview(file);
|
||||
};
|
||||
|
||||
this._mediaRecorder.start();
|
||||
this._recordingStart = Date.now();
|
||||
document.getElementById('recording-indicator').style.display = 'block';
|
||||
document.getElementById('mic-btn').classList.add('recording');
|
||||
const update = () => {
|
||||
if (!this._recordingStart) return;
|
||||
const elapsed = Math.floor((Date.now() - this._recordingStart) / 1000);
|
||||
const m = Math.floor(elapsed/60); const s = elapsed % 60;
|
||||
document.getElementById('recording-time').textContent = `${m}:${String(s).padStart(2,'0')}`;
|
||||
};
|
||||
update();
|
||||
this._recordingInterval = setInterval(update, 1000);
|
||||
} catch (err) {
|
||||
console.error('startRecording error', err);
|
||||
alert('No se pudo acceder al micrófono: ' + (err.message||err));
|
||||
}
|
||||
};
|
||||
|
||||
this.stopRecording = function() {
|
||||
if (this._mediaRecorder && this._mediaRecorder.state !== 'inactive') {
|
||||
this._mediaRecorder.stop();
|
||||
}
|
||||
if (this._recordingInterval) clearInterval(this._recordingInterval);
|
||||
this._recordingInterval = null;
|
||||
this._recordingStart = null;
|
||||
document.getElementById('recording-indicator').style.display = 'none';
|
||||
document.getElementById('mic-btn').classList.remove('recording');
|
||||
};
|
||||
|
||||
this.cancelRecording = function() {
|
||||
if (this._mediaRecorder && this._mediaRecorder.state !== 'inactive') {
|
||||
this._mediaRecorder.stop();
|
||||
}
|
||||
this._mediaRecorder = null;
|
||||
this.selectedFile = null;
|
||||
if (this._recordingInterval) clearInterval(this._recordingInterval);
|
||||
this._recordingInterval = null;
|
||||
this._recordingStart = null;
|
||||
document.getElementById('recording-indicator').style.display = 'none';
|
||||
document.getElementById('mic-btn').classList.remove('recording');
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
async loadconversations(userId, showLoading = true) {
|
||||
@@ -1306,6 +1430,10 @@
|
||||
// Renderizar y ajustar scroll
|
||||
this.renderconversations();
|
||||
|
||||
// Replace media rendering using shared helper for consistency
|
||||
// (renderconversations will include the HTML from renderMediaMessage in content)
|
||||
|
||||
|
||||
if (initial) {
|
||||
this.scrollToBottom();
|
||||
} else if (container) {
|
||||
@@ -1367,11 +1495,9 @@
|
||||
|
||||
const statusIcon = this.getStatusIcon(msg.status);
|
||||
|
||||
// Renderizar contenido (texto o multimedia)
|
||||
// Preferir miniatura local para previsualización si existe
|
||||
const content = (msg.local_thumb || msg.local_file || msg.media_url_external)
|
||||
? this.renderMediaMessage(msg)
|
||||
: (msg.content || msg.message_text || '[Mensaje vacío]');
|
||||
// Renderizar contenido (texto o multimedia) usando helper común
|
||||
const mediaPresent = (msg.local_thumb || msg.local_file || msg.media_url_external || msg.media_url);
|
||||
const content = mediaPresent ? (window.renderMediaMessage ? window.renderMediaMessage(msg) : (msg.content || '[Mensaje]')) : (msg.content || '[Mensaje vacío]');
|
||||
|
||||
// Mostrar preview si es reply/context
|
||||
let replyHtml = '';
|
||||
@@ -1394,8 +1520,8 @@
|
||||
${content}
|
||||
${reactionHtml}
|
||||
<div class="message-actions mt-1">
|
||||
<button class="btn btn-sm btn-link" onclick="app.replyToMessage('${msg.message_id}','${msg.user_phone}')" title="Responder"><i class="fas fa-reply"></i></button>
|
||||
<button class="btn btn-sm btn-link" onclick="app.reactToMessage('${msg.message_id}','${msg.user_phone}')" title="Reaccionar"><i class="far fa-grin"></i></button>
|
||||
<button class="btn btn-sm btn-link" onclick="chat.promptReply('${msg.message_id}')" title="Responder"><i class="fas fa-reply"></i></button>
|
||||
<button class="btn btn-sm btn-link" onclick="chat.reactToMessage('${msg.message_id}')" title="Reaccionar"><i class="far fa-grin"></i></button>
|
||||
</div>
|
||||
<div class="message-time">
|
||||
${time}
|
||||
@@ -1422,14 +1548,15 @@
|
||||
const container = document.getElementById('quick-replies');
|
||||
container.innerHTML = '';
|
||||
try {
|
||||
const resp = await fetch('api/get_templates.php?approved_only=1&limit=6');
|
||||
const json = await resp.json();
|
||||
if (json && json.success && Array.isArray(json.data)) {
|
||||
json.data.forEach(t => {
|
||||
const resp = await apiCall('get_templates.php?approved_only=1&limit=10');
|
||||
if (resp && resp.success && Array.isArray(resp.data)) {
|
||||
resp.data.slice(0,6).forEach(t => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-outline-primary btn-sm';
|
||||
btn.className = 'btn btn-sm btn-outline-secondary';
|
||||
btn.style.marginRight = '6px';
|
||||
btn.textContent = t.name;
|
||||
const text = (t.body_text || t.name || t.template_name || '').replace(/\n/g,' ');
|
||||
btn.textContent = text.length > 30 ? text.substring(0,30) + '...' : text;
|
||||
btn.title = t.name;
|
||||
btn.addEventListener('click', () => this.sendTemplateQuick(t.template_name, t.language_code || 'es'));
|
||||
container.appendChild(btn);
|
||||
});
|
||||
@@ -1437,6 +1564,9 @@
|
||||
} catch (e) {
|
||||
console.error('Error loading quick replies', e);
|
||||
}
|
||||
|
||||
// Also populate the template select
|
||||
try { await loadTemplatesInto('#templateSelect'); } catch(e){console.warn('Could not load templates into select', e);}
|
||||
}
|
||||
|
||||
async sendTemplateQuick(templateName, language) {
|
||||
@@ -1471,8 +1601,10 @@
|
||||
async sendMessage() {
|
||||
const input = document.getElementById('message-input');
|
||||
const message = input.value.trim();
|
||||
const messageType = document.getElementById('message-type') ? document.getElementById('message-type').value : 'text';
|
||||
|
||||
if (!message || !this.currentUserId) return;
|
||||
if (!message && messageType === 'text') return;
|
||||
if (!this.currentUserId) return;
|
||||
|
||||
// Deshabilitar input
|
||||
input.disabled = true;
|
||||
@@ -1480,36 +1612,54 @@
|
||||
|
||||
try {
|
||||
const replyTo = input.dataset.replyTo || null;
|
||||
const response = await fetch('api/send_message.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: this.currentUserId,
|
||||
message: message,
|
||||
reply_to: replyTo
|
||||
})
|
||||
});
|
||||
// Clear reply data attribute
|
||||
input.dataset.replyTo = null;
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
input.value = '';
|
||||
// Recargar mensajes
|
||||
await this.loadconversations(this.currentUserId, false);
|
||||
// Actualizar lista de conversaciones
|
||||
this.loadConversations();
|
||||
// Actualizar respuestas rápidas
|
||||
await this.loadQuickReplies();
|
||||
|
||||
if (messageType === 'template') {
|
||||
const template = document.getElementById('templateSelect').value;
|
||||
if (!template) {
|
||||
alert('Seleccione una plantilla');
|
||||
return;
|
||||
}
|
||||
// send template
|
||||
const resp = await apiCall('send_message.php', { body: { recipient: this.getConversationPhone(this.currentUserId), type: 'template', template_name: template, language: (document.getElementById('templateSelect').selectedOptions[0] ? document.getElementById('templateSelect').selectedOptions[0].dataset.language : 'es') } });
|
||||
if (resp && resp.success) {
|
||||
showAlert('Plantilla enviada', 'success');
|
||||
await this.loadconversations(this.currentUserId, false);
|
||||
await this.loadConversations();
|
||||
} else {
|
||||
throw new Error(resp && resp.error ? resp.error : 'Error enviando plantilla');
|
||||
}
|
||||
} else {
|
||||
alert('Error al enviar mensaje: ' + (result.error || 'Error desconocido'));
|
||||
const response = await fetch('api/send_message.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: this.currentUserId,
|
||||
message: message,
|
||||
reply_to: replyTo
|
||||
})
|
||||
});
|
||||
// Clear reply data attribute
|
||||
input.dataset.replyTo = null;
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
input.value = '';
|
||||
// Recargar mensajes
|
||||
await this.loadconversations(this.currentUserId, false);
|
||||
// Actualizar lista de conversaciones
|
||||
this.loadConversations();
|
||||
// Actualizar respuestas rápidas
|
||||
await this.loadQuickReplies();
|
||||
} else {
|
||||
alert('Error al enviar mensaje: ' + (result.error || 'Error desconocido'));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
alert('Error al enviar mensaje');
|
||||
alert('Error al enviar mensaje: ' + (error.message||error));
|
||||
} finally {
|
||||
input.disabled = false;
|
||||
document.getElementById('send-btn').disabled = false;
|
||||
|
||||
Reference in New Issue
Block a user