From 2cbbf88e57c0ff46ae50983a839bf21133c04768 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 22 Jan 2026 04:06:30 +0000 Subject: [PATCH] random AI 4.0 --- api/chat.php | 41 +++++--- api/share.php | 50 ++++++++++ assets/css/custom.css | 44 +++++++-- assets/js/main.js | 197 ++++++++++++++++++++++++++++--------- db/migrations/02_share.sql | 6 ++ index.php | 51 ++++++++++ view.php | 22 +++++ 7 files changed, 341 insertions(+), 70 deletions(-) create mode 100644 api/share.php create mode 100644 db/migrations/02_share.sql create mode 100644 view.php diff --git a/api/chat.php b/api/chat.php index 6bce988..99a243e 100644 --- a/api/chat.php +++ b/api/chat.php @@ -14,10 +14,19 @@ $userMsg = $data['message']; $mode = $data['mode'] ?? 'regular'; $chatId = $data['chat_id'] ?? null; $creativity = $data['creativity'] ?? 0.7; -$limitsOff = $data['limits_off'] ?? 0; +$limitsOff = (int)($data['limits_off'] ?? 0); +$model = $data['model'] ?? 'gpt-4o'; try { - // 1. Create chat if not exists + // 1. Verify/Create chat + if ($chatId) { + $stmt = db()->prepare("SELECT id FROM chats WHERE id = ?"); + $stmt->execute([$chatId]); + if (!$stmt->fetch()) { + $chatId = null; // Reset if ID not found + } + } + if (!$chatId) { $stmt = db()->prepare("INSERT INTO chats (mode, title) VALUES (?, ?)"); $title = mb_substr($userMsg, 0, 50) . (mb_strlen($userMsg) > 50 ? '...' : ''); @@ -30,23 +39,21 @@ try { $stmt->execute([$chatId, $userMsg]); // 3. Prepare AI Prompt based on mode - $systemPrompt = "You are a helpful, intelligent AI assistant."; + $systemPrompt = "You are a helpful, intelligent AI assistant. Respond in clear, concise language."; if ($mode === 'coding') { - $systemPrompt = "You are an elite senior software engineer and architect. Provide clean, secure, efficient, and perfectly documented code. Explain your reasoning briefly."; + $systemPrompt = "You are an elite senior software engineer. Provide clean, secure, efficient code snippets. Always use triple backticks with the language specified. Briefly explain the logic."; } elseif ($mode === 'game' || $mode === 'app') { - $systemPrompt = "You are a world-class single-file application developer. Your goal is to generate a complete, functional, and visually stunning HTML/CSS/JS application or game in a SINGLE message. - Always wrap the code in a triple-backtick block starting with ```html. - Ensure the app is self-contained (no external assets unless they are from reliable CDNs). - Include CSS in + + + $content + +"; +} + +try { + $token = bin2hex(random_bytes(16)); + $stmt = db()->prepare("INSERT INTO shared_apps (token, content) VALUES (?, ?)"); + $stmt->execute([$token, $content]); + + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http'; + $host = $_SERVER['HTTP_HOST']; + $shareUrl = "$protocol://$host/view.php?t=$token"; + + echo json_encode([ + 'success' => true, + 'token' => $token, + 'url' => $shareUrl + ]); +} catch (Exception $e) { + echo json_encode(['error' => $e->getMessage()]); +} + diff --git a/assets/css/custom.css b/assets/css/custom.css index 608d810..eacd42f 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -199,16 +199,11 @@ body, #sidebar, #main-content, .message, #chat-input { transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; } -/* Hover effects */ -.btn-success:hover { - 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; + border-left: 3px solid transparent; } .history-item .delete-chat { @@ -226,7 +221,8 @@ body, #sidebar, #main-content, .message, #chat-input { .history-item.active { background-color: var(--active-bg); - border-left: 3px solid var(--accent-color); + border-left-color: var(--accent-color); + color: var(--accent-color); } /* Enhanced Theme Swatches */ @@ -260,3 +256,37 @@ body, #sidebar, #main-content, .message, #chat-input { border-color: var(--accent-color); box-shadow: 0 0 0 2px var(--accent-color); } + +/* Code Block Enhancements */ +.code-block-wrapper { + box-shadow: 0 4px 20px rgba(0,0,0,0.2); + border-radius: 8px; + overflow: hidden; +} + +.copy-btn { + transition: all 0.2s; +} + +.copy-btn:hover { + color: var(--accent-color) !important; +} + +/* Action Buttons */ +.btn-outline-accent { + color: var(--accent-color); + border-color: var(--accent-color); +} +.btn-outline-accent:hover { + background-color: var(--accent-color); + color: #fff; +} +.text-accent { + color: var(--accent-color) !important; +} + +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: 0.15em; +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 218b7af..8b9e6ef 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -2,6 +2,7 @@ document.addEventListener('DOMContentLoaded', () => { const chatWindow = document.getElementById('chat-window'); const chatInput = document.getElementById('chat-input'); const sendBtn = document.getElementById('send-btn'); + const stopBtn = document.getElementById('stop-btn'); const modeItems = document.querySelectorAll('.mode-item'); const currentModeBadge = document.getElementById('current-mode-badge'); const newChatBtn = document.getElementById('new-chat-btn'); @@ -11,11 +12,19 @@ document.addEventListener('DOMContentLoaded', () => { const creativityRange = document.getElementById('creativity-range'); const creativityVal = document.getElementById('creativity-val'); const limitsToggle = document.getElementById('limits-toggle'); + const modelSelect = document.getElementById('model-select'); const themeSwatches = document.querySelectorAll('.theme-swatch'); const saveSettingsBtn = document.getElementById('save-settings-btn'); + // Share elements + const shareModal = new bootstrap.Modal(document.getElementById('shareModal')); + const shareUrlInput = document.getElementById('share-url-input'); + const copyShareBtn = document.getElementById('copy-share-btn'); + const viewSharedLink = document.getElementById('view-shared-link'); + let currentMode = 'regular'; let currentChatId = null; + let abortController = null; // --- Sidebar & Mode Switching --- modeItems.forEach(item => { @@ -33,15 +42,14 @@ document.addEventListener('DOMContentLoaded', () => { function startNewChat() { currentChatId = null; - chatWindow.innerHTML = ` -
- -

New ${currentMode} Chat

-

How can I help you in this mode?

+ chatWindow.innerHTML = "` +
+ +

New ${currentMode} Chat

+

How can I help you in this mode?

- `; + `"; - // Remove active class from history items document.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); } @@ -74,7 +82,6 @@ document.addEventListener('DOMContentLoaded', () => {
`).join(''); - // Add event listeners to history items chatHistoryList.querySelectorAll('.history-item').forEach(item => { item.addEventListener('click', (e) => { if (e.target.classList.contains('delete-chat')) { @@ -107,30 +114,25 @@ document.addEventListener('DOMContentLoaded', () => { 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 + appendMessage(msg.role, msg.content, false); - // Special handling for game/app mode launch buttons if ((currentMode === 'game' || currentMode === 'app') && msg.role === 'assistant') { - addLaunchButton(msg.content); + addActionButtons(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'); @@ -166,14 +168,14 @@ document.addEventListener('DOMContentLoaded', () => { const isNewChat = !currentChatId; - // Clear input and disable chatInput.value = ''; chatInput.style.height = 'auto'; toggleLoading(true); - // Append User Message appendMessage('user', message); + abortController = new AbortController(); + try { const response = await fetch('api/chat.php', { method: 'POST', @@ -182,9 +184,11 @@ document.addEventListener('DOMContentLoaded', () => { message: message, mode: currentMode, chat_id: currentChatId, + model: modelSelect.value, creativity: creativityRange.value, limits_off: limitsToggle.checked ? 1 : 0 - }) + }), + signal: abortController.signal }); const data = await response.json(); @@ -192,12 +196,10 @@ document.addEventListener('DOMContentLoaded', () => { currentChatId = data.chat_id; appendMessage('assistant', data.message); - // Special handling for game/app mode if (currentMode === 'game' || currentMode === 'app') { - addLaunchButton(data.message); + addActionButtons(data.message); } - // If it was a new chat, refresh history to show the title if (isNewChat) { loadHistory(); } @@ -205,16 +207,20 @@ document.addEventListener('DOMContentLoaded', () => { appendMessage('assistant', 'Error: ' + (data.error || 'Unknown error')); } } catch (error) { - appendMessage('assistant', 'Error: ' + error.message); + if (error.name === 'AbortError') { + appendMessage('assistant', ' Generation stopped by user.'); + } else { + appendMessage('assistant', 'Error: ' + error.message); + } } finally { toggleLoading(false); + abortController = null; } } 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(); @@ -225,25 +231,46 @@ document.addEventListener('DOMContentLoaded', () => { chatWindow.appendChild(msgDiv); chatWindow.scrollTop = chatWindow.scrollHeight; + + msgDiv.querySelectorAll('.copy-btn').forEach(btn => { + btn.addEventListener('click', () => { + const code = btn.closest('.code-block-wrapper').querySelector('code').textContent; + navigator.clipboard.writeText(code).then(() => { + const original = btn.innerHTML; + btn.innerHTML = ' Copied!'; + setTimeout(() => btn.innerHTML = original, 2000); + }); + }); + }); } function formatText(text) { - let formatted = text; + if (!text) return ''; - // Code blocks: ```[lang]\n[code]``` - formatted = formatted.replace(/```(\w+)?\s*([\s\S]*?)```/g, (match, lang, code) => { - const safeCode = code.trim().replace(/`/g, '\`'); - return `
- ${lang || 'code'} - Copy -
-
${escapeHtml(code.trim())}
`; + const codeBlocks = []; + let formatted = text.replace(/```(\w+)?\s*([\s\S]*?)```/g, (match, lang, code) => { + const id = `CODE_BLOCK_${codeBlocks.length}`; + codeBlocks.push({ + id, + lang: lang || 'code', + code: code.trim() + }); + return id; }); - // Simple line breaks for non-code parts - if (!formatted.includes('
'); - } + formatted = escapeHtml(formatted).replace(/\n/g, '
'); + + codeBlocks.forEach(block => { + const html = ` +
+
+ ${block.lang} + Copy +
+
${escapeHtml(block.code)}
+
`; + formatted = formatted.replace(block.id, html); + }); return formatted; } @@ -254,26 +281,83 @@ document.addEventListener('DOMContentLoaded', () => { return div.innerHTML; } - function addLaunchButton(content) { + function addActionButtons(content) { const match = content.match(/```(?:html|xml)?\s*([\s\S]*?)```/i); let codeToLaunch = match ? match[1] : content; const hasHtmlTags = / { - if (!codeToLaunch.toLowerCase().includes('AI Generated App${codeToLaunch}`; + const btnContainer = document.createElement('div'); + btnContainer.className = 'd-flex flex-wrap gap-2 mt-2 action-buttons'; + + // Launch Button + const launchBtn = document.createElement('button'); + launchBtn.className = 'btn btn-sm btn-success d-inline-flex align-items-center gap-2 shadow-sm'; + launchBtn.innerHTML = ' Launch'; + launchBtn.onclick = () => { + let fullCode = codeToLaunch; + if (!fullCode.toLowerCase().includes('AI Generated App${codeToLaunch}`; } - const blob = new Blob([codeToLaunch], { type: 'text/html' }); + const blob = new Blob([fullCode], { type: 'text/html' }); const url = URL.createObjectURL(blob); window.open(url, '_blank'); }; + + // Download Button + const downloadBtn = document.createElement('button'); + downloadBtn.className = 'btn btn-sm btn-outline-primary d-inline-flex align-items-center gap-2 shadow-sm'; + downloadBtn.innerHTML = ' Download'; + downloadBtn.onclick = () => { + let fullCode = codeToLaunch; + if (!fullCode.toLowerCase().includes('AI Generated App${codeToLaunch}`; + } + const blob = new Blob([fullCode], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ai-app-${Date.now()}.html`; + a.click(); + URL.revokeObjectURL(url); + }; + + // Share Button + const shareBtn = document.createElement('button'); + shareBtn.className = 'btn btn-sm btn-outline-accent d-inline-flex align-items-center gap-2 shadow-sm'; + shareBtn.innerHTML = ' Share'; + shareBtn.onclick = async () => { + shareBtn.disabled = true; + shareBtn.innerHTML = ' Sharing...'; + + try { + const resp = await fetch('api/share.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: codeToLaunch }) + }); + const data = await resp.json(); + if (data.success) { + shareUrlInput.value = data.url; + viewSharedLink.href = data.url; + shareModal.show(); + } else { + showToast('Failed to share: ' + (data.error || 'Unknown error'), 'danger'); + } + } catch (e) { + console.error(e); + showToast('Error sharing application', 'danger'); + } finally { + shareBtn.disabled = false; + shareBtn.innerHTML = ' Share'; + } + }; - chatWindow.lastElementChild.appendChild(btn); + btnContainer.appendChild(launchBtn); + btnContainer.appendChild(downloadBtn); + btnContainer.appendChild(shareBtn); + chatWindow.lastElementChild.appendChild(btnContainer); } } @@ -282,12 +366,20 @@ document.addEventListener('DOMContentLoaded', () => { chatInput.disabled = isLoading; if (isLoading) { sendBtn.innerHTML = ''; + stopBtn.style.display = 'flex'; } else { sendBtn.innerHTML = ''; + stopBtn.style.display = 'none'; chatInput.focus(); } } + stopBtn.addEventListener('click', () => { + if (abortController) { + abortController.abort(); + } + }); + sendBtn.addEventListener('click', sendMessage); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { @@ -320,7 +412,8 @@ document.addEventListener('DOMContentLoaded', () => { const settings = { theme: theme, creativity: creativityRange.value, - limits_off: limitsToggle.checked ? '1' : '0' + limits_off: limitsToggle.checked ? '1' : '0', + model: modelSelect.value }; saveSettingsBtn.disabled = true; @@ -347,6 +440,14 @@ document.addEventListener('DOMContentLoaded', () => { } }); + copyShareBtn.addEventListener('click', () => { + shareUrlInput.select(); + document.execCommand('copy'); + const original = copyShareBtn.innerHTML; + copyShareBtn.innerHTML = ' Copied!'; + setTimeout(() => copyShareBtn.innerHTML = original, 2000); + }); + function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `position-fixed bottom-0 start-50 translate-middle-x mb-4 bg-${type} text-white px-4 py-2 rounded-pill shadow-lg animate-fade-in`; @@ -360,11 +461,9 @@ document.addEventListener('DOMContentLoaded', () => { }, 3000); } - // Set active theme swatch on load 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(); -}); \ No newline at end of file +}); diff --git a/db/migrations/02_share.sql b/db/migrations/02_share.sql new file mode 100644 index 0000000..c1011a7 --- /dev/null +++ b/db/migrations/02_share.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS shared_apps ( + id INT AUTO_INCREMENT PRIMARY KEY, + token VARCHAR(64) UNIQUE NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/index.php b/index.php index afe75f8..589d651 100644 --- a/index.php +++ b/index.php @@ -20,6 +20,7 @@ try { $theme = $settings['theme'] ?? 'theme-dark-modern'; $creativity = $settings['creativity'] ?? '0.7'; $limitsOff = $settings['limits_off'] ?? '0'; +$model = $settings['model'] ?? 'gpt-4o'; // Default model ?> @@ -141,6 +142,20 @@ $limitsOff = $settings['limits_off'] ?? '0'; color: var(--text-muted); } + #stop-btn { + position: absolute; + right: 45px; + bottom: 12px; + background: none; + border: none; + color: var(--text-muted); + font-size: 1.2rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + } + /* Sidebar Components */ .sidebar-header { padding: 1.5rem; @@ -254,6 +269,7 @@ $limitsOff = $settings['limits_off'] ?? '0';
+
@@ -275,6 +291,17 @@ $limitsOff = $settings['limits_off'] ?? '0';
AI Parameters
+ +
+ + +
+
+ + + diff --git a/view.php b/view.php new file mode 100644 index 0000000..c17ddf9 --- /dev/null +++ b/view.php @@ -0,0 +1,22 @@ +prepare("SELECT content FROM shared_apps WHERE token = ?"); + $stmt->execute([$token]); + $app = $stmt->fetch(); + + if (!$app) { + die('App not found'); + } + + echo $app['content']; +} catch (Exception $e) { + die('Error: ' . $e->getMessage()); +}