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 = '
';
+
+ 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 = `
+
+
+
+
+ | Timestamp |
+ Task |
+ Project |
+ Output |
+ Duration |
+
+
+
+ `;
+
+ activities.forEach(activity => {
+ const badgeClass = activity.output_type === 'AI' ? 'badge-ai' : 'badge-human';
+ tableHtml += `
+
+ | ${activity.timestamp} |
+ ${escapeHTML(activity.task)} |
+ ${escapeHTML(activity.project)} |
+ ${escapeHTML(activity.output_type)} |
+ ${escapeHTML(activity.duration)} |
+
+ `;
+ });
+
+ tableHtml += '
';
+ 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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
+
+
+
+
+
+ Hello! How can I help you today?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
%
+
Human Contribution
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+ | Timestamp |
+ Task |
+ Project |
+ Output Type |
+ Duration |
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+ |
+ |
+
+
+
+
+ | No activity logged yet. |
+
+
+
+
+
+
+
+
+
+
+
+
+
Past timesheets will be shown here.
+
+
+
+
+
+
+
-
-
+
+
+
-
+