fix chat AI

This commit is contained in:
Filipinos 2025-07-29 17:15:23 +02:00
parent d6957d7959
commit 020ba6526a
3 changed files with 135 additions and 99 deletions

View File

@ -4011,7 +4011,7 @@ body.miniplayer-active #fab-container {
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
gap: 1.5rem; /* Aumentamos el espacio entre mensajes */
}
.chat-messages::-webkit-scrollbar {
@ -4027,10 +4027,47 @@ body.miniplayer-active #fab-container {
border-radius: 4px;
}
/* NUEVO: Contenedores para alinear mensajes y avatares */
.message-wrapper {
display: flex;
align-items: flex-end;
gap: 0.75rem;
max-width: 90%;
animation: slide-in-bottom 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.assistant-wrapper {
align-self: flex-start;
}
.user-wrapper {
align-self: flex-end;
flex-direction: row-reverse; /* Mensaje a la izquierda del (inexistente) avatar */
}
/* NUEVO: Estilo del avatar del asistente */
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--gradient);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 0 10px rgba(0, 224, 255, 0.3);
}
.avatar svg {
width: 20px;
height: 20px;
color: var(--primary);
}
/* MEJORADO: Estilos de las burbujas de mensaje */
.message {
padding: 0.8rem 1.2rem;
border-radius: var(--border-radius-md);
max-width: 85%;
line-height: 1.6;
word-wrap: break-word;
}
@ -4039,31 +4076,38 @@ body.miniplayer-active #fab-container {
white-space: pre-wrap;
word-break: break-word;
}
.message p:last-child {
margin-bottom: 0;
}
.user-message {
background: var(--gradient);
color: var(--primary);
align-self: flex-end;
border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px; /* Cola de la burbuja */
}
.assistant-message {
background-color: var(--secondary);
color: var(--text-primary);
align-self: flex-start;
border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px; /* Cola de la burbuja */
}
/* MEJORADO: Estilo de las acciones contextuales */
.chat-item-actions {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--glass-border);
}
.chat-action-title {
display: block;
font-weight: 600;
color: var(--accent);
margin-bottom: 0.75rem;
font-size: 0.9rem;
}
.chat-action-buttons {
display: flex;
flex-wrap: wrap;
gap: .5rem;
margin-top: .8rem;
padding-top: .8rem;
border-top: 1px solid var(--glass-border);
}
.chat-action-buttons button {
@ -4081,42 +4125,42 @@ body.miniplayer-active #fab-container {
background-color: var(--accent);
color: var(--primary);
border-color: var(--accent);
transform: translateY(-2px);
}
.assistant-message code {
background-color: var(--primary);
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
border-radius: 6px;
font-family: monospace;
.chat-download-all {
margin-top: 1rem;
}
.typing-indicator {
/* MEJORADO: Indicador de escritura */
.typing-indicator-bubble {
display: flex;
gap: 5px;
gap: 6px;
align-items: center;
padding: 14px 16px;
}
.typing-indicator span {
.typing-indicator-bubble span {
width: 8px;
height: 8px;
background-color: var(--text-secondary);
border-radius: 50%;
animation: typing 1s infinite;
animation: typing-pulse 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
.typing-indicator-bubble span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator-bubble span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
@keyframes typing-pulse {
0%, 100% { opacity: 0.2; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1); }
}
.chat-input-form {
/* MEJORADO: Formulario de entrada */
#chat-input-form {
display: flex;
padding: 0.8rem;
border-top: 1px solid var(--glass-border);
gap: 0.8rem;
background-color: rgba(255,255,255,0.05);
background: linear-gradient(to top, rgba(16, 17, 22, 0.8), rgba(16, 17, 22, 0.5));
align-items: flex-end;
}
@ -4125,16 +4169,19 @@ body.miniplayer-active #fab-container {
background: var(--glass);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 0.6rem 1rem;
padding: 0.7rem 1.2rem;
color: var(--text-primary);
resize: none;
font-family: 'Montserrat', sans-serif;
font-size: 0.95rem;
transition: all 0.3s ease;
}
.chat-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(0, 224, 255, 0.2);
box-shadow: 0 0 15px rgba(0, 224, 255, 0.2);
background: var(--secondary);
}
.chat-send-btn {
@ -4142,20 +4189,47 @@ body.miniplayer-active #fab-container {
background: var(--accent);
color: var(--primary);
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 1rem;
border-radius: 12px; /* Más tecnológico que un círculo */
width: 42px;
height: 42px;
font-size: 1.1rem;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.chat-send-btn svg {
transition: transform 0.3s ease;
}
.chat-send-btn:hover {
background: var(--accent-dark);
transform: scale(1.1);
box-shadow: 0 0 10px rgba(0, 224, 255, 0.4);
}
.chat-send-btn:hover svg {
transform: translateX(2px) rotate(-15deg);
}
.chat-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.chat-send-btn:disabled:hover svg {
transform: none;
}
/* NUEVO: Animación para los mensajes */
@keyframes slide-in-bottom {
0% {
transform: translateY(20px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@media (max-width: 768px) {

View File

@ -100,10 +100,20 @@ export class Chat {
}
addMessage(text, sender, isError = false) {
const wrapper = document.createElement('div');
wrapper.className = `message-wrapper ${sender}-wrapper`;
const messageEl = document.createElement('div');
messageEl.classList.add('message', `${sender}-message`);
if(isError) messageEl.style.color = 'var(--danger)';
if (sender === 'assistant') {
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm-2.5-5h5v2h-5v-2z"/></svg>`;
wrapper.appendChild(avatar);
}
const p = document.createElement('p');
p.textContent = text;
messageEl.appendChild(p);
@ -112,16 +122,27 @@ export class Chat {
this.addMediaActionButtons(messageEl, text);
}
this.dom.messagesContainer.appendChild(messageEl);
wrapper.appendChild(messageEl);
this.dom.messagesContainer.appendChild(wrapper);
this.scrollToBottom();
}
addTypingIndicator() {
const wrapper = document.createElement('div');
wrapper.id = 'typing-indicator';
wrapper.className = 'message-wrapper assistant-wrapper';
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm-2.5-5h5v2h-5v-2z"/></svg>`;
wrapper.appendChild(avatar);
const indicator = document.createElement('div');
indicator.id = 'typing-indicator';
indicator.classList.add('message', 'assistant-message', 'typing-indicator');
indicator.classList.add('message', 'assistant-message', 'typing-indicator-bubble');
indicator.innerHTML = '<span></span><span></span><span></span>';
this.dom.messagesContainer.appendChild(indicator);
wrapper.appendChild(indicator);
this.dom.messagesContainer.appendChild(wrapper);
this.scrollToBottom();
}

View File

@ -1,59 +0,0 @@
import { showNotification, _ } from './utils.js';
export const phpScriptGenerator = (() => {
let dom = {};
function cacheDom() {
const settingsModal = document.getElementById('settingsModal');
if (!settingsModal) return false;
dom.secretKeyCheck = settingsModal.querySelector('#phpSecretKeyCheck');
dom.secretKey = settingsModal.querySelector('#phpSecretKey');
dom.savePath = settingsModal.querySelector('#phpSavePath');
dom.filename = settingsModal.querySelector('#phpFilename');
dom.fileActionAppendRadio = settingsModal.querySelector('#phpFileActionAppend');
dom.generatedCode = settingsModal.querySelector('#generatedPhpCode');
dom.generateBtn = settingsModal.querySelector('#generatePhpScriptBtn');
dom.copyBtn = settingsModal.querySelector('#copyPhpScriptBtn');
return dom.generateBtn && dom.copyBtn;
}
function init() {
if (!cacheDom()) {
return;
}
dom.generateBtn.addEventListener('click', generatePhpScript);
dom.copyBtn.addEventListener('click', copyScript);
}
function generatePhpScript() {
const useSecretKey = dom.secretKeyCheck.checked;
const secretKey = dom.secretKey.value.trim();
const savePath = dom.savePath.value.trim();
const filename = dom.filename.value.trim() || 'CinePlex_Playlist.m3u';
const appendToFile = dom.fileActionAppendRadio.checked;
let script = `<?php\nheader('Content-Type: application/json');\nheader('Access-Control-Allow-Origin: *');\nheader('Access-Control-Allow-Methods: POST, OPTIONS');\nheader('Access-Control-Allow-Headers: Content-Type, Origin, X-Secret-Key');\n\nif ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {\n http_response_code(200);\n exit(0);\n}\n\ndefine('SAVE_DIRECTORY', '${savePath.replace(/'/g, "\\'")}');\ndefine('FILENAME', '${filename.replace(/'/g, "\\'")}');\ndefine('FILE_ACTION_APPEND', ${appendToFile ? 'true' : 'false'});\n${useSecretKey ? `define('SECRET_KEY', '${secretKey.replace(/'/g, "\\'")}');` : ''}\n\nfunction sendResponse($success, $message, $filename = '', $http_code = 200) {\n if (!$success && $http_code === 200) {\n $http_code = 400;\n }\n http_response_code($http_code);\n echo json_encode(['success' => $success, 'message' => $message, 'filename' => $filename]);\n exit;\n}\n`;
if (useSecretKey) {
script += `\n$auth_key = isset($_SERVER['HTTP_X_SECRET_KEY']) ? $_SERVER['HTTP_X_SECRET_KEY'] : '';\nif (!defined('SECRET_KEY') || SECRET_KEY === '' || $auth_key !== SECRET_KEY) {\n sendResponse(false, 'Acceso no autorizado. Clave secreta inválida o no proporcionada.', '', 403);\n}\n`;
}
script += `\n$json_data = file_get_contents('php://input');\n$data = json_decode($json_data, true);\n\nif (json_last_error() !== JSON_ERROR_NONE) {\n sendResponse(false, 'Error: Datos JSON inválidos.');\n}\n\nif (!isset($data['streams']) || !is_array($data['streams']) || empty($data['streams'])) {\n sendResponse(false, 'Error: El JSON debe contener un array "streams" no vacío.');\n}\n\n$save_dir = SAVE_DIRECTORY !== '' ? rtrim(SAVE_DIRECTORY, '/\\') : __DIR__;\n\nif (!is_dir($save_dir) || !is_writable($save_dir)) {\n sendResponse(false, 'Error del servidor: El directorio de destino no existe o no tiene permisos de escritura.', '', 500);\n}\n\n$safe_filename = preg_replace('/[^\\w\\s._-]/', '', basename(FILENAME));\n$safe_filename = preg_replace('/\\s+/', '_', $safe_filename);\n$target_path = $save_dir . DIRECTORY_SEPARATOR . $safe_filename;\n\n$content_to_write = "";\n\nif (FILE_ACTION_APPEND) {\n $file_exists = file_exists($target_path);\n if (!$file_exists) {\n $content_to_write .= "#EXTM3U\\n";\n }\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, FILE_APPEND | LOCK_EX) !== false) {\n sendResponse(true, 'Streams añadidos correctamente al archivo.', $safe_filename, 200);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo añadir contenido al archivo.', '', 500);\n }\n} else { // Overwrite mode\n $content_to_write = "#EXTM3U\\n";\n foreach ($data['streams'] as $stream) {\n if (isset($stream['extinf'], $stream['url'])) {\n $content_to_write .= trim($stream['extinf']) . "\\n";\n $content_to_write .= trim($stream['url']) . "\\n";\n }\n }\n if (file_put_contents($target_path, $content_to_write, LOCK_EX) !== false) {\n sendResponse(true, 'Archivo de streams sobrescrito correctamente.', $safe_filename, 201);\n } else {\n sendResponse(false, 'Error del servidor: No se pudo escribir el archivo.', '', 500);\n }\n}\n?>`;
dom.generatedCode.value = script;
showNotification(_("scriptGenerated"), "success");
}
function copyScript() {
if (!dom.generatedCode.value || dom.generatedCode.value.trim() === '') {
showNotification(_("errorGeneratingScript"), "warning");
return;
}
navigator.clipboard.writeText(dom.generatedCode.value).then(() => {
showNotification(_("scriptCopied"), "success");
}).catch(err => {
showNotification(_("errorCopyingScript"), "error");
});
}
return { init };
})();