AI analysis v1

This commit is contained in:
Flatlogic Bot 2025-11-19 15:51:26 +00:00
parent b96dfdc8af
commit 89e735575c
5 changed files with 671 additions and 143 deletions

207
assets/css/custom.css Normal file
View File

@ -0,0 +1,207 @@
/* General Body Styling */
body {
background-color: #F3F4F6;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color: #111827;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
}
/* Main Chat Container */
.chat-container {
width: 100%;
max-width: 768px;
height: 90vh;
max-height: 800px;
background-color: #FFFFFF;
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Chat Header */
.chat-header {
padding: 1rem 1.5rem;
background: linear-gradient(45deg, #4F46E5, #6366F1);
color: white;
border-bottom: 1px solid #E5E7EB;
text-align: center;
flex-shrink: 0;
}
.chat-header h1 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
/* Message List */
.message-list {
flex-grow: 1;
padding: 1.5rem;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Individual Message Styling */
.message {
display: flex;
align-items: flex-end;
gap: 0.75rem;
max-width: 85%;
animation: fadeIn 0.3s ease-out;
}
.message .avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #E5E7EB;
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
flex-shrink: 0;
}
.message .message-content {
padding: 0.75rem 1rem;
border-radius: 0.75rem;
line-height: 1.5;
}
/* AI (Bot) Message */
.message.bot {
align-self: flex-start;
}
.message.bot .avatar {
background-color: #4F46E5;
color: white;
}
.message.bot .message-content {
background-color: #EEF2FF;
color: #3730A3;
border-top-left-radius: 0;
}
/* User Message */
.message.user {
align-self: flex-end;
}
.message.user .message-content {
background-color: #10B981;
color: white;
border-top-right-radius: 0;
}
.message.user .avatar {
display: none; /* Hide avatar for user messages for a cleaner look */
}
/* Input Area */
.input-area {
padding: 1rem 1.5rem;
border-top: 1px solid #E5E7EB;
background-color: #F9FAFB;
display: flex;
gap: 0.75rem;
flex-shrink: 0;
}
.input-area input {
flex-grow: 1;
padding: 0.75rem 1rem;
border: 1px solid #D1D5DB;
border-radius: 0.375rem;
font-size: 1rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
.input-area input:focus {
outline: none;
border-color: #4F46E5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
}
.input-area button {
padding: 0.75rem 1.5rem;
border: none;
background-color: #4F46E5;
color: white;
border-radius: 0.375rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
}
.input-area button:hover {
background-color: #4338CA;
}
.input-area button:disabled {
background-color: #A5B4FC;
cursor: not-allowed;
}
#analyze-button {
width: 100%;
padding: 1rem;
border: none;
background-color: #10B981; /* Emerald Green */
color: white;
border-radius: 0.375rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
}
#analyze-button:hover {
background-color: #059669;
}
/* Typing indicator */
.typing-indicator {
display: flex;
align-items: center;
padding: 5px 0;
}
.typing-indicator span {
height: 8px;
width: 8px;
float: left;
margin: 0 2px;
background-color: #9E9E9E;
display: block;
border-radius: 50%;
opacity: 0.4;
animation: 1s blink infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: .2s; }
.typing-indicator span:nth-child(3) { animation-delay: .4s; }
@keyframes blink {
50% { opacity: 1; }
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

135
assets/js/main.js Normal file
View File

@ -0,0 +1,135 @@
document.addEventListener('DOMContentLoaded', () => {
const questions = [
"I wake up feeling tired, even after a full nights sleep.",
"By midday, I already feel mentally drained and out of energy.",
"I feel emotionally numb, detached, or “on autopilot” most of the time.",
"Things that used to excite me now feel pointless or like a chore.",
"I feel irritated or cynical about people I work or study with.",
"I struggle to focus and constantly procrastinate, even on important tasks.",
"I feel guilty for not doing “enough”, no matter how much I actually do.",
"I often think about quitting everything for a while or disappearing from social media and work.",
"I use caffeine, sugar, nicotine, alcohol, or scrolling to “numb out” instead of resting.",
"I feel like my life is just surviving, not really living."
];
const messageList = document.getElementById('message-list');
const inputArea = document.getElementById('input-area');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
let currentQuestionIndex = 0;
const userAnswers = [];
function addMessage(text, sender) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', sender);
const avatar = document.createElement('div');
avatar.classList.add('avatar');
avatar.textContent = sender === 'bot' ? 'AI' : 'You';
const messageContent = document.createElement('div');
messageContent.classList.add('message-content');
messageContent.textContent = text;
if (sender === 'bot') {
messageElement.appendChild(avatar);
}
messageElement.appendChild(messageContent);
messageList.appendChild(messageElement);
messageList.scrollTop = messageList.scrollHeight;
}
function showTypingIndicator() {
const typingElement = document.createElement('div');
typingElement.id = 'typing-indicator';
typingElement.classList.add('message', 'bot');
typingElement.innerHTML = `
<div class="avatar">AI</div>
<div class="message-content">
<div class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
`;
messageList.appendChild(typingElement);
messageList.scrollTop = messageList.scrollHeight;
}
function removeTypingIndicator() {
const typingElement = document.getElementById('typing-indicator');
if (typingElement) {
typingElement.remove();
}
}
function askNextQuestion() {
if (currentQuestionIndex < questions.length) {
showTypingIndicator();
setTimeout(() => {
removeTypingIndicator();
addMessage(questions[currentQuestionIndex], 'bot');
userInput.disabled = false;
sendButton.disabled = false;
userInput.focus();
}, 1000); // Simulate AI "thinking"
} else {
showTypingIndicator();
setTimeout(() => {
removeTypingIndicator();
addMessage("Thank you for your responses. Click the button below to see your analysis.", 'bot');
// Create a form to submit the results
const form = document.createElement('form');
form.method = 'POST';
form.action = 'results.php';
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'conversation';
hiddenInput.value = JSON.stringify(userAnswers);
form.appendChild(hiddenInput);
const submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = 'Analyze My Results';
submitButton.id = 'analyze-button';
form.appendChild(submitButton);
inputArea.innerHTML = ''; // Clear the input field and send button
inputArea.appendChild(form);
inputArea.style.display = 'flex';
}, 1500);
}
}
function handleUserInput() {
const text = userInput.value.trim();
if (text === '') return;
addMessage(text, 'user');
userAnswers.push({ question: questions[currentQuestionIndex], answer: text });
userInput.value = '';
userInput.disabled = true;
sendButton.disabled = true;
currentQuestionIndex++;
askNextQuestion();
}
sendButton.addEventListener('click', handleUserInput);
userInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
handleUserInput();
}
});
// Start the conversation
setTimeout(() => {
addMessage("Hello! I'm here to help you assess your level of burnout. I'll ask you 10 questions. Please answer them honestly.", 'bot');
askNextQuestion();
}, 500);
});

40
followup.php Normal file
View File

@ -0,0 +1,40 @@
<?php
require_once __DIR__ . '/ai/LocalAIApi.php';
$request_body = file_get_contents('php://input');
$data = json_decode($request_body, true);
$conversation = $data['conversation'] ?? null;
$analysis = $data['analysis'] ?? null;
$follow_up_question = $data['follow_up_question'] ?? null;
if ($conversation && $analysis && $follow_up_question) {
$prompt = "The user has received an initial burnout analysis and has a follow-up question. Here is the context:\n\n";
$prompt .= "**Original Survey Answers:**\n";
foreach ($conversation as $item) {
$prompt .= "Q: " . $item['question'] . "\nA: " . $item['answer'] . "\n";
}
$prompt .= "\n**Initial AI Analysis:**\n" . strip_tags($analysis) . "\n";
$prompt .= "\n**User's Follow-up Question:**\n" . $follow_up_question . "\n";
$prompt .= "Please provide a concise and helpful response to the user's question, keeping the context in mind. Format the response as simple HTML.";
$resp = LocalAIApi::createResponse(
[
'input' => [
['role' => 'system', 'content' => 'You are a helpful assistant specializing in mental health and burnout analysis.'],
['role' => 'user', 'content' => $prompt],
],
]
);
if (!empty($resp['success'])) {
echo LocalAIApi::extractText($resp);
} else {
echo "Sorry, I couldn't process your request. Please try again later.";
}
} else {
http_response_code(400);
echo "Invalid request. Missing data.";
}

176
index.php
View File

@ -1,150 +1,42 @@
<?php
declare(strict_types=1);
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>New Style</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burnout Survey & Analysis</title>
<meta name="description" content="AI-driven burnout survey that asks questions conversationally, analyzes responses, and provides structured results and charts. Built with Flatlogic Generator.">
<meta name="keywords" content="burnout survey, mental health, ai chatbot, stress analysis, employee wellness, self-assessment, mental wellbeing, burnout prevention, ai health, personalized advice, Flatlogic Generator">
<meta property="og:title" content="Burnout Survey & Analysis">
<meta property="og:description" content="AI-driven burnout survey that asks questions conversationally, analyzes responses, and provides structured results and charts.">
<meta property="og:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="<?php echo htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? ''); ?>">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
</head>
<body>
<main>
<div class="card">
<h1>Analyzing your requirements and generating your website…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
</div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
<div class="chat-container">
<header class="chat-header">
<h1>Burnout Assessment</h1>
</header>
<main id="message-list" class="message-list">
<!-- Messages will be dynamically inserted here -->
</main>
<footer id="input-area" class="input-area">
<input type="text" id="user-input" placeholder="Type your answer..." autocomplete="off" disabled>
<button id="send-button" disabled>Send</button>
</footer>
</div>
</main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body>
</html>

254
results.php Normal file
View File

@ -0,0 +1,254 @@
<?php
// results.php
require_once __DIR__ . '/ai/LocalAIApi.php';
$conversation_json = $_POST['conversation'] ?? '';
$conversation = json_decode($conversation_json, true);
$ai_response_text = '';
$chart_data_json = '';
$error_message = '';
if ($conversation) {
$prompt = "Analyze the following conversation for signs of burnout. The user was asked 10 questions related to burnout. Based on their answers, provide a thoughtful analysis and actionable recommendations. Return a JSON object with two keys: 'analysis_html' and 'chart_data'.\n\n1. `analysis_html`: An HTML string containing a detailed analysis, identifying key themes (e.g., exhaustion, cynicism, inefficacy), a summary of the user's burnout level, and 3-5 actionable recommendations. Use headings, lists, and bold text for readability. Do not include `<html>` or `<body>` tags.\n2. `chart_data`: A JSON object with two keys: `labels` (an array of strings for the chart categories, e.g., ['Exhaustion', 'Cynicism', 'Inefficacy']) and `scores` (an array of integers from 0 to 10 representing the user's score in each category).\n\nHere is the conversation:
";
foreach ($conversation as $item) {
$prompt .= "Q: " . $item['question'] . "\nA: " . $item['answer'] . "\n\n";
}
$resp = LocalAIApi::createResponse(
[
'input' => [
['role' => 'system', 'content' => 'You are a helpful assistant specializing in mental health and burnout analysis. You always respond with a valid JSON object.'],
['role' => 'user', 'content' => $prompt],
],
]
);
if (!empty($resp['success'])) {
$ai_response_raw = LocalAIApi::extractText($resp);
$ai_response_data = json_decode($ai_response_raw, true);
if ($ai_response_data && isset($ai_response_data['analysis_html']) && isset($ai_response_data['chart_data'])) {
$ai_response_text = $ai_response_data['analysis_html'];
$chart_data_json = json_encode($ai_response_data['chart_data']);
} else {
$error_message = 'AI response was not in the expected format. Please try again.';
// For debugging: $error_message .= "<br>Raw response: " . htmlspecialchars($ai_response_raw);
}
} else {
$error_message = 'Failed to get response from AI. Error: ' . ($resp['error'] ?? 'Unknown error');
}
} else {
$error_message = 'No conversation data received.';
}
?><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Burnout Analysis Results</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.results-container {
width: 100%;
max-width: 768px;
background-color: #FFFFFF;
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
padding: 2rem;
text-align: left;
}
.loader {
border: 4px solid #f3f4f6;
border-radius: 50%;
border-top: 4px solid #4F46E5;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 2rem auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.ai-content h2 {
font-size: 1.5rem;
color: #4F46E5;
margin-top: 1.5rem;
margin-bottom: 1rem;
}
.ai-content ul {
list-style-position: inside;
padding-left: 0;
}
.ai-content li {
margin-bottom: 0.75rem;
line-height: 1.6;
}
#burnoutChart {
margin-top: 2rem;
margin-bottom: 2rem;
}
</style>
</head>
<body>
<div class="chat-container">
<header class="chat-header">
<h1>Your Burnout Analysis</h1>
</header>
<main class="message-list" id="results-main">
<div class="results-container">
<?php if ($error_message): ?>
<p style="color: red;"><?php echo htmlspecialchars($error_message); ?></p>
<?php elseif ($ai_response_text): ?>
<canvas id="burnoutChart"></canvas>
<div class="ai-content">
<?php echo $ai_response_text; ?>
</div>
<?php else: ?>
<p>Analyzing your responses...</p>
<div class="loader"></div>
<?php endif; ?>
</div>
</main>
<?php if (!$error_message && $ai_response_text): ?>
<footer id="input-area" class="input-area">
<input type="text" id="user-input" placeholder="Ask a follow-up question..." autocomplete="off">
<button id="send-button">Send</button>
</footer>
<?php endif; ?>
</div>
<?php if ($chart_data_json): ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const chartData = <?php echo $chart_data_json; ?>;
const ctx = document.getElementById('burnoutChart').getContext('2d');
new Chart(ctx, {
type: 'radar',
data: {
labels: chartData.labels,
datasets: [{
label: 'Burnout Score',
data: chartData.scores,
backgroundColor: 'rgba(79, 70, 229, 0.2)',
borderColor: '#4F46E5',
pointBackgroundColor: '#4F46E5',
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: '#4F46E5'
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
r: {
angleLines: {
color: '#E5E7EB'
},
grid: {
color: '#E5E7EB'
},
pointLabels: {
font: {
size: 14,
family: 'Inter'
},
color: '#111827'
},
ticks: {
backdropColor: '#f3f4f6',
color: '#6B7280',
stepSize: 2,
beginAtZero: true,
max: 10
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
});
</script>
<?php endif; ?>
<?php if (!$error_message && $ai_response_text): ?>
<script>
document.addEventListener('DOMContentLoaded', () => {
const sendButton = document.getElementById('send-button');
const userInput = document.getElementById('user-input');
const mainContainer = document.getElementById('results-main');
const originalConversation = <?php echo json_encode($conversation); ?>;
const originalAnalysis = <?php echo json_encode($ai_response_text); ?>;
function addMessageToUI(text, sender) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', sender);
const avatar = document.createElement('div');
avatar.classList.add('avatar');
avatar.textContent = sender === 'bot' ? 'AI' : 'You';
const messageContent = document.createElement('div');
messageContent.classList.add('message-content');
messageContent.innerHTML = text; // Use innerHTML to render formatted AI responses
if (sender === 'bot') {
messageElement.appendChild(avatar);
}
messageElement.appendChild(messageContent);
mainContainer.appendChild(messageElement);
mainContainer.scrollTop = mainContainer.scrollHeight;
}
async function handleFollowUp() {
const userText = userInput.value.trim();
if (userText === '') return;
addMessageToUI(userText, 'user');
userInput.value = '';
sendButton.disabled = true;
const response = await fetch('followup.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
conversation: originalConversation,
analysis: originalAnalysis,
follow_up_question: userText
})
});
const aiResponseText = await response.text();
addMessageToUI(aiResponseText, 'bot');
sendButton.disabled = false;
userInput.focus();
}
sendButton.addEventListener('click', handleFollowUp);
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
handleFollowUp();
}
});
});
</script>
<?php endif; ?>
</body>
</html>