From 00a5c5a638383da15bb41872105712fbd7c99cdc Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 19 Jan 2026 00:06:34 +0000 Subject: [PATCH] Version 1 --- api/chat.php | 109 +++++++++++++ assets/css/custom.css | 118 ++++++++++++++ assets/js/main.js | 276 +++++++++++++++++++++++++++++++ index.php | 367 ++++++++++++++++++++++++++---------------- 4 files changed, 729 insertions(+), 141 deletions(-) create mode 100644 api/chat.php create mode 100644 assets/css/custom.css create mode 100644 assets/js/main.js diff --git a/api/chat.php b/api/chat.php new file mode 100644 index 0000000..48c674b --- /dev/null +++ b/api/chat.php @@ -0,0 +1,109 @@ +query("SELECT DATE(created_at) as timesheet_date, JSON_ARRAYAGG(JSON_OBJECT('id', id, 'task', task, 'project', project, 'output_type', output_type, 'duration', duration, 'timestamp', DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s'))) as activities FROM activity_log GROUP BY timesheet_date ORDER BY timesheet_date DESC"); + $past_timesheets = $stmt->fetchAll(PDO::FETCH_ASSOC); + echo json_encode(['past_timesheets' => $past_timesheets]); + } catch (PDOException $e) { + http_response_code(500); + error_log("DB Error: " . $e->getMessage()); + echo json_encode(['error' => 'Database error while fetching past timesheets']); + } + exit; +} + +if (isset($input['action']) && $input['action'] === 'manual_log') { + $task_description = $input['type'] . ': ' . $input['task']; + $project = $input['project']; + // For manual entries, we can set a duration of 0 or make it an optional field. + $duration_formatted = '00:00:00'; + + try { + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO activity_log (task, project, output_type, duration) VALUES (?, ?, ?, ?)"); + $stmt->execute([$task_description, $project, 'Human', $duration_formatted]); + + $new_log_id = $pdo->lastInsertId(); + $log_stmt = $pdo->prepare("SELECT *, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') as timestamp FROM activity_log WHERE id = ?"); + $log_stmt->execute([$new_log_id]); + $new_log_entry = $log_stmt->fetch(PDO::FETCH_ASSOC); + + echo json_encode(['new_log_entry' => $new_log_entry]); + + } catch (PDOException $e) { + http_response_code(500); + error_log("DB Error: " . $e->getMessage()); + echo json_encode(['error' => 'Database error while logging manual entry']); + } + exit; +} + + +$user_message = $input['message'] ?? ''; + +if (empty($user_message)) { + echo json_encode(['error' => 'Empty message']); + exit; +} + +$start_time = microtime(true); + +// Use the LocalAIApi to get a response from the AI +$ai_response_data = LocalAIApi::createResponse([ + 'input' => [ + ['role' => 'system', 'content' => 'You are a helpful assistant.'], + ['role' => 'user', 'content' => $user_message], + ], +]); + +if (empty($ai_response_data['success'])) { + http_response_code(500); + error_log('AI API Error: ' . ($ai_response_data['error'] ?? 'Unknown error')); + echo json_encode(['error' => 'Failed to get response from AI', 'details' => $ai_response_data['error'] ?? '']); + exit; +} + +$ai_message = LocalAIApi::extractText($ai_response_data); + +if ($ai_message === '') { + $decoded = LocalAIApi::decodeJsonFromResponse($ai_response_data); + $ai_message = $decoded ? json_encode($decoded, JSON_UNESCAPED_UNICODE) : 'Empty response from AI.'; +} + + +$end_time = microtime(true); +$duration_seconds = round($end_time - $start_time); +$duration_formatted = gmdate("H:i:s", $duration_seconds); + +$project_name = 'Chat Interaction'; + +try { + $pdo = db(); + $stmt = $pdo->prepare("INSERT INTO activity_log (task, project, output_type, duration) VALUES (?, ?, ?, ?)"); + $stmt->execute([$ai_message, $project_name, 'AI', $duration_formatted]); + + // Fetch the new log entry to return it + $new_log_id = $pdo->lastInsertId(); + $log_stmt = $pdo->prepare("SELECT *, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') as timestamp FROM activity_log WHERE id = ?"); + $log_stmt->execute([$new_log_id]); + $new_log_entry = $log_stmt->fetch(PDO::FETCH_ASSOC); + +} catch (PDOException $e) { + http_response_code(500); + error_log("DB Error: " . $e->getMessage()); + echo json_encode(['error' => 'Database error while logging']); + exit; +} + +echo json_encode([ + 'reply' => $ai_message, + 'new_log_entry' => $new_log_entry +]); diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..0607e66 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,118 @@ +/* Inter font from Google Fonts */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background-color: #F8F9FA; + color: #212529; +} + +.card { + border-radius: 0.25rem; + border: 1px solid #DEE2E6; +} + +.table { + border: 1px solid #DEE2E6; +} + +.badge-ai { + background-color: #007BFF; + color: white; +} + +.badge-human { + background-color: #6C757D; + color: white; +} + +#chat-container { + height: 70vh; + display: flex; + flex-direction: column; +} + +#chat-box { + flex-grow: 1; + overflow-y: auto; + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.message { + padding: 0.5rem 1rem; + border-radius: 1rem; + max-width: 80%; + word-wrap: break-word; +} + +.user-message { + background-color: #007BFF; + color: white; + align-self: flex-end; + border-bottom-right-radius: 0; +} + +.bot-message { + background-color: #E9ECEF; + color: #212529; + align-self: flex-start; + border-bottom-left-radius: 0; +} + +.bot-message.typing-indicator { + display: flex; + align-items: center; + gap: 5px; +} + +.dot-flashing { + position: relative; + width: 6px; + height: 6px; + border-radius: 5px; + background-color: #6C757D; + color: #6C757D; + animation: dotFlashing 1s infinite linear alternate; + animation-delay: .5s; +} + +.dot-flashing::before, .dot-flashing::after { + content: ''; + display: inline-block; + position: absolute; + top: 0; +} + +.dot-flashing::before { + left: -10px; + width: 6px; + height: 6px; + border-radius: 5px; + background-color: #6C757D; + color: #6C757D; + animation: dotFlashing 1s infinite alternate; + animation-delay: 0s; +} + +.dot-flashing::after { + left: 10px; + width: 6px; + height: 6px; + border-radius: 5px; + background-color: #6C757D; + color: #6C757D; + animation: dotFlashing 1s infinite alternate; + animation-delay: 1s; +} + +@keyframes dotFlashing { + 0% { + background-color: #6C757D; + } + 50%, 100% { + background-color: #E9ECEF; + } +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..c180efc --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,276 @@ +document.addEventListener('DOMContentLoaded', () => { + const chatForm = document.getElementById('chat-form'); + const chatInput = document.getElementById('chat-input'); + const chatBox = document.getElementById('chat-box'); + const activityLogBody = document.getElementById('activity-log-body'); + const logCount = document.getElementById('log-count'); + const emptyLogRew = document.getElementById('empty-log-row'); + const manualLogForm = document.getElementById('manual-log-form'); + const clockElement = document.getElementById('clock'); + const pastTimesheetsTab = document.getElementById('past-timesheets-tab'); + const pastTimesheetsContainer = document.getElementById('past-timesheets'); + + function updateClock() { + if (clockElement) { + const now = new Date(); + const time = now.toLocaleTimeString(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const year = now.getFullYear(); + const date = `${month}-${day}-${year}`; + clockElement.textContent = `${time} ${date}`; + } + } + + updateClock(); + setInterval(updateClock, 1000); + + if (pastTimesheetsTab) { + pastTimesheetsTab.addEventListener('shown.bs.tab', () => { + loadPastTimesheets(); + }); + } + + async function loadPastTimesheets() { + pastTimesheetsContainer.innerHTML = '
Loading...
'; + + try { + const response = await fetch('api/chat.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ action: 'get_past_timesheets' }) + }); + + if (!response.ok) { + pastTimesheetsContainer.innerHTML = '

Failed to load past timesheets.

'; + return; + } + + const data = await response.json(); + + if (data.error) { + pastTimesheetsContainer.innerHTML = `

${data.error}

`; + return; + } + + if (!data.past_timesheets || data.past_timesheets.length === 0) { + pastTimesheetsContainer.innerHTML = '
No past timesheets found.
'; + return; + } + + const accordion = document.createElement('div'); + accordion.className = 'accordion'; + accordion.id = 'past-timesheets-accordion'; + + data.past_timesheets.forEach((sheet, index) => { + const activities = JSON.parse(sheet.activities); + const item = document.createElement('div'); + item.className = 'accordion-item'; + + const header = document.createElement('h2'); + header.className = 'accordion-header'; + header.innerHTML = ` + + `; + + const collapse = document.createElement('div'); + collapse.id = `collapse-${index}`; + collapse.className = `accordion-collapse collapse ${index === 0 ? 'show' : ''}`; + collapse.setAttribute('data-bs-parent', '#past-timesheets-accordion'); + + const body = document.createElement('div'); + body.className = 'accordion-body'; + + let tableHtml = ` +
+ + + + + + + + + + + + `; + + activities.forEach(activity => { + const badgeClass = activity.output_type === 'AI' ? 'badge-ai' : 'badge-human'; + tableHtml += ` + + + + + + + + `; + }); + + tableHtml += '
TimestampTaskProjectOutputDuration
${activity.timestamp}${escapeHTML(activity.task)}${escapeHTML(activity.project)}${escapeHTML(activity.output_type)}${escapeHTML(activity.duration)}
'; + body.innerHTML = tableHtml; + + collapse.appendChild(body); + item.appendChild(header); + item.appendChild(collapse); + accordion.appendChild(item); + }); + + pastTimesheetsContainer.innerHTML = ''; + pastTimesheetsContainer.appendChild(accordion); + + } catch (error) { + console.error('Error loading past timesheets:', error); + pastTimesheetsContainer.innerHTML = '

An error occurred while fetching data.

'; + } + } + + chatForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const userMessage = chatInput.value.trim(); + if (!userMessage) return; + + appendMessage(userMessage, 'user'); + chatInput.value = ''; + showTypingIndicator(); + + try { + const response = await fetch('api/chat.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ message: userMessage }) + }); + + removeTypingIndicator(); + + if (!response.ok) { + const errorData = await response.json(); + const errorMsg = errorData.error || 'An unknown error occurred.'; + appendMessage(`Error: ${errorMsg}`, 'bot'); + return; + } + + const data = await response.json(); + + if (data.error) { + appendMessage(`Error: ${data.error}`, 'bot'); + return; + } + + appendMessage(data.reply, 'bot'); + + if (data.new_log_entry) { + updateActivityLog(data.new_log_entry); + } + + } catch (error) { + removeTypingIndicator(); + appendMessage('Could not connect to the server. Please try again later.', 'bot'); + console.error("Fetch Error:", error); + } + }); + + manualLogForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const actionType = document.getElementById('action-type').value; + const project = document.getElementById('manual-project').value.trim(); + const task = document.getElementById('manual-task').value.trim(); + + if (!project || !task) { + // You might want to show an error to the user + return; + } + + const payload = { + action: 'manual_log', + type: actionType, + project: project, + task: task + }; + + try { + const response = await fetch('api/chat.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + console.error('Manual log submission failed'); + return; + } + + const data = await response.json(); + + if (data.new_log_entry) { + updateActivityLog(data.new_log_entry); + manualLogForm.reset(); + } + } catch (error) { + console.error("Fetch Error:", error); + } + }); + + function appendMessage(message, sender) { + const messageElement = document.createElement('div'); + messageElement.classList.add('message', `${sender}-message`); + messageElement.textContent = message; + chatBox.appendChild(messageElement); + chatBox.scrollTop = chatBox.scrollHeight; + } + + function showTypingIndicator() { + const typingIndicator = document.createElement('div'); + typingIndicator.classList.add('message', 'bot-message', 'typing-indicator'); + typingIndicator.innerHTML = '
'; + typingIndicator.id = 'typing-indicator'; + chatBox.appendChild(typingIndicator); + chatBox.scrollTop = chatBox.scrollHeight; + } + + function removeTypingIndicator() { + const typingIndicator = document.getElementById('typing-indicator'); + if (typingIndicator) { + typingIndicator.remove(); + } + } + + function updateActivityLog(logEntry) { + if(emptyLogRew) { + emptyLogRew.remove(); + } + + const newRow = document.createElement('tr'); + const badgeClass = logEntry.output_type === 'AI' ? 'badge-ai' : 'badge-human'; + newRow.innerHTML = ` + ${logEntry.timestamp} + ${escapeHTML(logEntry.task)} + ${escapeHTML(logEntry.project)} + + + ${escapeHTML(logEntry.output_type)} + + + ${escapeHTML(logEntry.duration)} + `; + activityLogBody.prepend(newRow); + logCount.textContent = parseInt(logCount.textContent) + 1; + } + + function escapeHTML(str) { + if (typeof str !== 'string') return ''; + const p = document.createElement('p'); + p.appendChild(document.createTextNode(str)); + return p.innerHTML; + } +}); diff --git a/index.php b/index.php index 7205f3d..16942f7 100644 --- a/index.php +++ b/index.php @@ -1,150 +1,235 @@ prepare("INSERT INTO activity_log (task, project, output_type, duration) VALUES (?, ?, ?, ?)"); + $stmt->execute([$task, $project, $output_type, $duration]); + header("Location: " . $_SERVER['PHP_SELF']); + exit; + } catch (PDOException $e) { + error_log("DB Error: " . $e->getMessage()); + } + } +} + + +try { + $pdo = db(); + + // Seed data if the table is empty + $stmt = $pdo->query("SELECT COUNT(*) FROM activity_log"); + if ($stmt->fetchColumn() == 0) { + $seed_data = [ + ['task' => 'Analyzed quarterly sales report for new market trends.', 'output_type' => 'AI', 'duration' => '00:02:35', 'project' => 'Q1 Sales Analysis'], + ['task' => 'Verified data extraction accuracy from scanned invoices.', 'output_type' => 'Human', 'duration' => '00:05:10', 'project' => 'Invoice Processing'], + ['task' => 'Generated Python script for automating data cleanup.', 'output_type' => 'AI', 'duration' => '00:03:50', 'project' => 'Data Migration Tool'], + ]; + + $stmt = $pdo->prepare("INSERT INTO activity_log (task, output_type, duration, project) VALUES (?, ?, ?, ?)"); + foreach ($seed_data as $row) { + $stmt->execute([$row['task'], $row['output_type'], $row['duration'], $row['project']]); + } + } + + // Fetch data for the log + $log_stmt = $pdo->query("SELECT *, DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') as timestamp FROM activity_log ORDER BY created_at DESC"); + $log_data = $log_stmt->fetchAll(); + +} catch (PDOException $e) { + die("DB ERROR: " . $e->getMessage()); +} + + +// Calculate AI vs Human contribution +$total_duration_ai = 0; +$total_duration_human = 0; + +foreach ($log_data as $log) { + list($h, $m, $s) = explode(':', $log['duration']); + $duration_in_seconds = $h * 3600 + $m * 60 + $s; + if ($log['output_type'] === 'AI') { + $total_duration_ai += $duration_in_seconds; + } else { + $total_duration_human += $duration_in_seconds; + } +} + +$total_duration = $total_duration_ai + $total_duration_human; +$ai_percentage = $total_duration > 0 ? round(($total_duration_ai / $total_duration) * 100) : 0; +$human_percentage = $total_duration > 0 ? round(($total_duration_human / $total_duration) * 100) : 0; -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); ?> - + - - - New Style - - - - - - - - - - - - - - - - - - - + + + <?php echo htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'AI Agent Digital Labor'); ?> + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… -
-

AI is collecting your requirements and applying the first changes.

-

This page will update automatically as the plan is implemented.

-

Runtime: PHP — UTC

+ +
+
+

+

+
+
+ +
+
+
+
+ AI Agent Chat +
+
+
+ Hello! How can I help you today? +
+
+ +
+
+
+
+
+ Log Human Action +
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+
+
+
+
+
+
+ Today's Contribution +
+
+
+
+

%

+

AI Contribution

+
+
+

%

+

Human Contribution

+
+
+
+
+
+
+ + + +
+
+
+
+ Live Activity Log +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
TimestampTaskProjectOutput TypeDuration
+ + + +
No activity logged yet.
+
+ +
+
+
+
+
+ Past Daily Timesheets +
+
+

Past timesheets will be shown here.

+
+
+
+
+
+
+
-
- + + + - + \ No newline at end of file