random AI 3.0

This commit is contained in:
Flatlogic Bot 2026-01-22 03:52:16 +00:00
parent c19e3debb4
commit 1633bd7927
4 changed files with 284 additions and 71 deletions

53
api/history.php Normal file
View File

@ -0,0 +1,53 @@
<?php
header('Content-Type: application/json');
require_once __DIR__ . '/../db/config.php';
$action = $_GET['action'] ?? 'list';
try {
if ($action === 'list') {
// Fetch last 20 chats
$stmt = db()->query("SELECT id, title, mode, created_at FROM chats ORDER BY created_at DESC LIMIT 20");
$chats = $stmt->fetchAll();
echo json_encode(['success' => true, 'chats' => $chats]);
}
elseif ($action === 'messages') {
$chatId = $_GET['chat_id'] ?? null;
if (!$chatId) {
echo json_encode(['error' => 'Chat ID is required']);
exit;
}
// Fetch all messages for this chat
$stmt = db()->prepare("SELECT role, content, created_at FROM messages WHERE chat_id = ? ORDER BY id ASC");
$stmt->execute([$chatId]);
$messages = $stmt->fetchAll();
// Also fetch chat details (to restore mode)
$stmt = db()->prepare("SELECT mode FROM chats WHERE id = ?");
$stmt->execute([$chatId]);
$chat = $stmt->fetch();
echo json_encode([
'success' => true,
'messages' => $messages,
'mode' => $chat['mode'] ?? 'regular'
]);
}
elseif ($action === 'delete') {
$chatId = $_GET['chat_id'] ?? null;
if (!$chatId) {
echo json_encode(['error' => 'Chat ID is required']);
exit;
}
$stmt = db()->prepare("DELETE FROM chats WHERE id = ?");
$stmt->execute([$chatId]);
echo json_encode(['success' => true]);
}
else {
echo json_encode(['error' => 'Invalid action']);
}
} catch (Exception $e) {
echo json_encode(['error' => $e->getMessage()]);
}

View File

@ -204,3 +204,59 @@ body, #sidebar, #main-content, .message, #chat-input {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
/* History Item Styling */
.history-item {
font-size: 0.9rem;
transition: all 0.2s ease;
}
.history-item .delete-chat {
visibility: hidden;
cursor: pointer;
}
.history-item:hover .delete-chat {
visibility: visible;
}
.history-item:hover {
background-color: var(--hover-bg);
}
.history-item.active {
background-color: var(--active-bg);
border-left: 3px solid var(--accent-color);
}
/* Enhanced Theme Swatches */
.theme-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 12px;
}
.theme-swatch {
height: 60px;
border-radius: 10px;
cursor: pointer;
border: 2px solid var(--border-color);
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
text-align: center;
padding: 5px;
}
.theme-swatch:hover {
transform: scale(1.05);
border-color: var(--accent-color);
}
.theme-swatch.active {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px var(--accent-color);
}

View File

@ -5,6 +5,7 @@ document.addEventListener('DOMContentLoaded', () => {
const modeItems = document.querySelectorAll('.mode-item');
const currentModeBadge = document.getElementById('current-mode-badge');
const newChatBtn = document.getElementById('new-chat-btn');
const chatHistoryList = document.getElementById('chat-history');
// Settings elements
const creativityRange = document.getElementById('creativity-range');
@ -39,15 +40,132 @@ document.addEventListener('DOMContentLoaded', () => {
<p class="text-muted">How can I help you in this mode?</p>
</div>
`;
// Remove active class from history items
document.querySelectorAll('.history-item').forEach(i => i.classList.remove('active'));
}
newChatBtn.addEventListener('click', startNewChat);
// --- History Loading ---
async function loadHistory() {
try {
const resp = await fetch('api/history.php?action=list');
const data = await resp.json();
if (data.success) {
renderHistory(data.chats);
}
} catch (e) {
console.error('Failed to load history:', e);
}
}
function renderHistory(chats) {
if (!chats || chats.length === 0) {
chatHistoryList.innerHTML = '<div class="text-muted small px-3">No recent chats</div>';
return;
}
chatHistoryList.innerHTML = chats.map(chat => `
<div class="history-item mode-item ${currentChatId == chat.id ? 'active' : ''}" data-id="${chat.id}" data-mode="${chat.mode}">
<i class="bi bi-${getModeIcon(chat.mode)}"></i>
<span class="text-truncate">${escapeHtml(chat.title)}</span>
<i class="bi bi-trash delete-chat ms-auto" data-id="${chat.id}" style="font-size: 0.8rem; opacity: 0.5;"></i>
</div>
`).join('');
// Add event listeners to history items
chatHistoryList.querySelectorAll('.history-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.classList.contains('delete-chat')) {
deleteChat(e.target.dataset.id);
return;
}
loadChat(item.dataset.id);
});
});
}
function getModeIcon(mode) {
switch(mode) {
case 'coding': return 'code-slash';
case 'game': return 'controller';
case 'app': return 'window';
default: return 'chat-left-dots';
}
}
async function loadChat(chatId) {
if (currentChatId == chatId) return;
chatWindow.innerHTML = '<div class="text-center my-auto"><span class="spinner-border text-primary"></span></div>';
try {
const resp = await fetch(`api/history.php?action=messages&chat_id=${chatId}`);
const data = await resp.json();
if (data.success) {
currentChatId = chatId;
currentMode = data.mode;
// Update Sidebar Mode UI
modeItems.forEach(i => {
if (i.dataset.mode === currentMode) i.classList.add('active');
else i.classList.remove('active');
});
// Update badge
const activeModeItem = Array.from(modeItems).find(i => i.dataset.mode === currentMode);
if (activeModeItem) {
currentModeBadge.textContent = activeModeItem.querySelector('span').textContent;
}
// Render messages
chatWindow.innerHTML = '';
data.messages.forEach(msg => {
appendMessage(msg.role, msg.content, false); // false = don't animate existing
// Special handling for game/app mode launch buttons
if ((currentMode === 'game' || currentMode === 'app') && msg.role === 'assistant') {
addLaunchButton(msg.content);
}
});
// Highlight active history item
document.querySelectorAll('.history-item').forEach(i => {
if (i.dataset.id == chatId) i.classList.add('active');
else i.classList.remove('active');
});
chatWindow.scrollTop = chatWindow.scrollHeight;
}
} catch (e) {
console.error(e);
chatWindow.innerHTML = '<div class="alert alert-danger m-3">Failed to load chat</div>';
}
}
async function deleteChat(chatId) {
if (!confirm('Are you sure you want to delete this chat?')) return;
try {
const resp = await fetch(`api/history.php?action=delete&chat_id=${chatId}`);
const data = await resp.json();
if (data.success) {
if (currentChatId == chatId) startNewChat();
loadHistory();
}
} catch (e) {
console.error(e);
}
}
// --- Chat Logic ---
async function sendMessage() {
const message = chatInput.value.trim();
if (!message) return;
const isNewChat = !currentChatId;
// Clear input and disable
chatInput.value = '';
chatInput.style.height = 'auto';
@ -78,6 +196,11 @@ document.addEventListener('DOMContentLoaded', () => {
if (currentMode === 'game' || currentMode === 'app') {
addLaunchButton(data.message);
}
// If it was a new chat, refresh history to show the title
if (isNewChat) {
loadHistory();
}
} else {
appendMessage('assistant', 'Error: ' + (data.error || 'Unknown error'));
}
@ -88,15 +211,16 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function appendMessage(role, text) {
function appendMessage(role, text, animate = true) {
if (role === 'system') return;
// Remove empty state if present
const emptyState = chatWindow.querySelector('.my-auto');
if (emptyState) emptyState.remove();
const msgDiv = document.createElement('div');
msgDiv.className = `message message-${role} animate-fade-in`;
msgDiv.className = `message message-${role} ${animate ? 'animate-fade-in' : ''}`;
// Use marked.js or simple formatting
msgDiv.innerHTML = formatText(text);
chatWindow.appendChild(msgDiv);
@ -104,24 +228,19 @@ document.addEventListener('DOMContentLoaded', () => {
}
function formatText(text) {
// Handle code blocks with more flexibility
let formatted = text;
// Escape HTML for non-code parts
// This is tricky without a library, let's just do code blocks first
// Code blocks: ```[lang]\n[code]```
formatted = formatted.replace(/```(\w+)?\s*([\s\S]*?)```/g, (match, lang, code) => {
const safeCode = code.trim().replace(/`/g, '\`');
return `<div class="code-header d-flex justify-content-between px-3 py-1 bg-dark text-muted small border-bottom border-secondary rounded-top mt-2">
<span>${lang || 'code'}</span>
<span class="copy-btn" style="cursor:pointer" onclick="navigator.clipboard.writeText(\\`${code.trim().replace(/`/g, '\\`')}\\`)"><i class="bi bi-clipboard"></i> Copy</span>
<span class="copy-btn" style="cursor:pointer" onclick="navigator.clipboard.writeText(\"${safeCode}\")"><i class="bi bi-clipboard"></i> Copy</span>
</div>
<pre class="bg-dark text-white p-3 rounded-bottom mb-2 overflow-auto" style="font-size: 0.85rem; border: 1px solid #444; border-top:none;"><code>${escapeHtml(code.trim())}</code></pre>`;
});
// Simple line breaks for non-code parts
// (Only for parts outside of the generated HTML above)
// This is a bit naive but works for simple chat
if (!formatted.includes('<div class="code-header"')) {
formatted = formatted.replace(/\n/g, '<br>');
}
@ -136,11 +255,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
function addLaunchButton(content) {
// Find code block content
const match = content.match(/```(?:html|xml)?\s*([\s\S]*?)```/i);
let codeToLaunch = match ? match[1] : content;
// Only add button if it looks like a full HTML document or contains significant HTML tags
const hasHtmlTags = /<html|<body|<script|<div|<style/i.test(codeToLaunch);
if (hasHtmlTags) {
@ -148,7 +265,6 @@ document.addEventListener('DOMContentLoaded', () => {
btn.className = 'btn btn-sm btn-success mt-2 d-inline-flex align-items-center gap-2 shadow-sm';
btn.innerHTML = '<i class="bi bi-rocket-takeoff-fill"></i> Launch Application in New Tab';
btn.onclick = () => {
// If it's not a full HTML, wrap it
if (!codeToLaunch.toLowerCase().includes('<html')) {
codeToLaunch = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AI Generated App</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><style>body{padding:20px;}</style></head><body>${codeToLaunch}</body></html>`;
}
@ -157,7 +273,6 @@ document.addEventListener('DOMContentLoaded', () => {
window.open(url, '_blank');
};
// Append to the last message div
chatWindow.lastElementChild.appendChild(btn);
}
}
@ -181,7 +296,6 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
// Auto-resize textarea
chatInput.addEventListener('input', () => {
chatInput.style.height = 'auto';
chatInput.style.height = (chatInput.scrollHeight) + 'px';
@ -222,7 +336,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (data.success) {
const modal = bootstrap.Modal.getInstance(document.getElementById('settingsModal'));
if (modal) modal.hide();
showToast('Settings saved successfully!');
}
} catch (e) {
@ -251,4 +364,7 @@ document.addEventListener('DOMContentLoaded', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const activeSwatch = document.querySelector(`.theme-swatch[data-theme="${currentTheme}"]`);
if (activeSwatch) activeSwatch.classList.add('active');
});
// Initial load
loadHistory();
});

View File

@ -188,26 +188,6 @@ $limitsOff = $settings['limits_off'] ?? '0';
.modal-footer {
border-top: 1px solid var(--border-color);
}
/* Themes Grid */
.theme-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.theme-swatch {
height: 40px;
border-radius: 6px;
cursor: pointer;
border: 2px solid transparent;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
}
.theme-swatch.active {
border-color: var(--accent-color);
}
</style>
</head>
<body>
@ -240,14 +220,13 @@ $limitsOff = $settings['limits_off'] ?? '0';
<div class="small text-uppercase text-muted mt-4 mb-3 fw-bold" style="font-size: 0.7rem; letter-spacing: 1px;">Recent History</div>
<div id="chat-history">
<!-- History items would go here -->
<div class="text-muted small px-3">No recent chats</div>
</div>
</div>
<div class="sidebar-footer">
<button class="btn btn-sm w-100 text-start text-muted d-flex align-items-center gap-2" data-bs-toggle="modal" data-bs-target="#settingsModal">
<i class="bi bi-gear"></i>
<i class="bi bi-palette"></i>
<span>Settings & Themes</span>
</button>
</div>
@ -286,47 +265,56 @@ $limitsOff = $settings['limits_off'] ?? '0';
<!-- Settings Modal -->
<div class="modal fade" id="settingsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Settings</h5>
<h5 class="modal-title"><i class="bi bi-gear-fill me-2"></i>Personalization & AI Settings</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-4">
<label class="form-label d-flex justify-content-between">
Creativity Level
<span id="creativity-val" class="text-primary fw-bold"><?php echo $creativity; ?></span>
</label>
<input type="range" class="form-range" id="creativity-range" min="0" max="1" step="0.1" value="<?php echo $creativity; ?>">
<div class="d-flex justify-content-between small text-muted">
<span>Precise</span>
<span>Creative</span>
<div class="row">
<div class="col-md-6 border-end">
<h6 class="mb-3 text-uppercase small fw-bold text-muted">AI Parameters</h6>
<div class="mb-4">
<label class="form-label d-flex justify-content-between">
Creativity Level (Temperature)
<span id="creativity-val" class="text-primary fw-bold"><?php echo $creativity; ?></span>
</label>
<input type="range" class="form-range" id="creativity-range" min="0" max="1" step="0.1" value="<?php echo $creativity; ?>">
<div class="d-flex justify-content-between small text-muted">
<span>Precise</span>
<span>Creative</span>
</div>
</div>
<div class="form-check form-switch mb-4">
<input class="form-check-input" type="checkbox" id="limits-toggle" <?php echo $limitsOff === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="limits-toggle">
<strong>Unlimited Creativity</strong><br>
<small class="text-muted">Removes safety constraints and standard filters.</small>
</label>
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3 text-uppercase small fw-bold text-muted">Visual Themes</h6>
<div class="theme-grid">
<div class="theme-swatch" data-theme="theme-dark-modern" style="background:#0f172a; color:#fff;">Dark Modern</div>
<div class="theme-swatch" data-theme="theme-light-minimal" style="background:#ffffff; color:#000; border:1px solid #ddd;">Light Minimal</div>
<div class="theme-swatch" data-theme="theme-midnight" style="background:#000000; color:#fff;">Midnight</div>
<div class="theme-swatch" data-theme="theme-forest" style="background:#064e3b; color:#fff;">Forest</div>
<div class="theme-swatch" data-theme="theme-ocean" style="background:#0c4a6e; color:#fff;">Ocean</div>
<div class="theme-swatch" data-theme="theme-slate" style="background:#334155; color:#fff;">Slate</div>
<div class="theme-swatch" data-theme="theme-nord" style="background:#2e3440; color:#eceff4;">Nord</div>
<div class="theme-swatch" data-theme="theme-sepia" style="background:#fdf6e3; color:#657b83; border:1px solid #eee8d5;">Sepia</div>
<div class="theme-swatch" data-theme="theme-cyberpunk" style="background:#1a1a1a; color:#f3f; border:1px solid #f3f;">Cyberpunk</div>
<div class="theme-swatch" data-theme="theme-matrix" style="background:#000; color:#0f0; border:1px solid #0f0;">Matrix</div>
</div>
</div>
</div>
<div class="form-check form-switch mb-4">
<input class="form-check-input" type="checkbox" id="limits-toggle" <?php echo $limitsOff === '1' ? 'checked' : ''; ?>>
<label class="form-check-label" for="limits-toggle">Turn off creativity limits</label>
</div>
<h6>Themes</h6>
<div class="theme-grid mt-2">
<div class="theme-swatch" data-theme="theme-dark-modern" style="background:#0f172a; color:#fff;">Dark Modern</div>
<div class="theme-swatch" data-theme="theme-light-minimal" style="background:#ffffff; color:#000; border:1px solid #ddd;">Light Minimal</div>
<div class="theme-swatch" data-theme="theme-midnight" style="background:#000000; color:#fff;">Midnight</div>
<div class="theme-swatch" data-theme="theme-forest" style="background:#064e3b; color:#fff;">Forest</div>
<div class="theme-swatch" data-theme="theme-ocean" style="background:#0c4a6e; color:#fff;">Ocean</div>
<div class="theme-swatch" data-theme="theme-slate" style="background:#334155; color:#fff;">Slate</div>
<div class="theme-swatch" data-theme="theme-nord" style="background:#2e3440; color:#eceff4;">Nord</div>
<div class="theme-swatch" data-theme="theme-sepia" style="background:#704214; color:#fdf6e3;">Sepia</div>
<div class="theme-swatch" data-theme="theme-cyberpunk" style="background:#1a1a1a; color:#f3f;">Cyberpunk</div>
<div class="theme-swatch" data-theme="theme-matrix" style="background:#000; color:#0f0;">Matrix</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="save-settings-btn">Save changes</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary px-4" id="save-settings-btn">Save All Changes</button>
</div>
</div>
</div>