fix chat AI
This commit is contained in:
parent
d6957d7959
commit
020ba6526a
146
css/main.css
146
css/main.css
@ -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) {
|
||||
|
29
js/chat.js
29
js/chat.js
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
})();
|
Loading…
x
Reference in New Issue
Block a user