This commit is contained in:
Flatlogic Bot 2025-11-02 19:42:55 +00:00
parent f521b6df27
commit 2e3424ad5c
8 changed files with 641 additions and 54 deletions

104
api/analyze_chat.php Normal file
View File

@ -0,0 +1,104 @@
<?php
// api/analyze_chat.php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/config.php';
header('Content-Type: application/json');
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if (!isset($data['session_id'], $data['persona'], $data['history'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid input. session_id, persona, and history are required.']);
exit;
}
$sessionId = $data['session_id'];
$persona = $data['persona'];
$history = $data['history'];
// --- System Prompt & API Call (remains the same) ---
$system_prompt = "You are a market research analyst. Your task is to analyze a conversation between an entrepreneur and a potential customer persona. The persona's details and the conversation are provided below.\n\n"
. "**Persona Profile:**\n"
. "- Name: {$persona['name']}\n"
. "- Age: {$persona['age']}\n"
. "- Occupation: {$persona['occupation']}\n"
. "- Traits: {$persona['traits']}\n"
. "- Concerns: {$persona['concerns']}\n"
. "- Style: {$persona['style']}\n\n"
. "**Your Analysis Should Include:**\n"
. "1. **Key Takeaways:** A bulleted list of the most important points from the conversation.\n"
. "2. **Persona's Sentiment:** A brief assessment (Positive, Negative, Neutral, Mixed) of the persona's overall feeling about the business idea, with a brief justification.\n"
. "3. **Actionable Insights:** A bulleted list of concrete suggestions for the entrepreneur to improve their idea based on the persona's feedback.\n\n"
. "Please format your response in simple HTML, using <h3> for titles and <ul> and <li> for lists. Do not include <html>, <head>, or <body> tags.";
$conversation_text = "";
foreach ($history as $msg) {
$sender = $msg['sender'] === 'user' ? 'Entrepreneur' : $persona['name'];
$conversation_text .= "**{$sender}:** {$msg['message']}\n";
}
$user_content = "Please analyze the following conversation:\n\n{$conversation_text}";
$messages = [[ 'role' => 'user', 'content' => $user_content ]];
$apiKey = defined('ANTHROPIC_API_KEY') ? ANTHROPIC_API_KEY : '';
if (empty($apiKey) || $apiKey === 'YOUR_ANTHROPIC_API_KEY') {
http_response_code(500);
echo json_encode(['error' => 'Anthropic API key is not configured.']);
exit;
}
$payload = [
'model' => 'claude-3-sonnet-20240229',
'system' => $system_prompt,
'messages' => $messages,
'max_tokens' => 1024,
'temperature' => 0.5,
];
$ch = curl_init('https://api.anthropic.com/v1/messages');
// ... (cURL options remain the same)
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-api-key: ' . $apiKey,
'anthropic-version: 2023-06-01'
]);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error || $httpcode >= 400) {
http_response_code($httpcode > 0 ? $httpcode : 500);
$error_details = json_decode($response, true);
echo json_encode(['error' => 'Anthropic API Error', 'details' => $error_details, 'curl_error' => $curl_error]);
exit;
}
$result = json_decode($response, true);
$analysisContent = $result['content'][0]['text'] ?? null;
if ($analysisContent) {
// --- Save analysis to DB ---
try {
$pdo = db();
// Use INSERT ... ON DUPLICATE KEY UPDATE to avoid creating multiple analyses for one session.
// Note: This requires a UNIQUE index on session_id in the analyses table.
// For simplicity here, we'll just insert. A more robust app would handle this better.
$stmt = $pdo->prepare("INSERT INTO analyses (session_id, content) VALUES (?, ?)");
$stmt->execute([$sessionId, $analysisContent]);
} catch (Exception $e) {
error_log('Database error while saving analysis: ' . $e->getMessage());
}
echo json_encode(['analysis' => $analysisContent]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Unexpected API response structure for analysis.', 'details' => $result]);
}

17
api/chat.php Normal file
View File

@ -0,0 +1,17 @@
<?php
header('Content-Type: application/json');
// Simulate a delay
sleep(1);
// Get the message from the user
$input = json_decode(file_get_contents('php://input'), true);
$message = $input['message'] ?? 'No message received';
$persona_name = $input['persona']['name'] ?? 'Persona';
// A simple, canned response for now
$response = "This is a placeholder response from " . htmlspecialchars($persona_name) . ". You said: '" . htmlspecialchars($message) . "'. The real AI chat functionality will be implemented later.";
echo json_encode([
'reply' => $response
]);

View File

@ -1,6 +1,7 @@
<?php
// api/generate_personas.php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/config.php';
header('Content-Type: application/json');
@ -27,6 +28,7 @@ if (ANTHROPIC_API_KEY === 'YOUR_ANTHROPIC_API_KEY_HERE') {
exit;
}
// ... (Anthropic API call logic remains the same)
$prompt = "Based on the following business idea and target audience in Egypt/MENA, create 3 distinct user personas.
Target Audience: "$audience"
@ -69,14 +71,11 @@ curl_close($ch);
if ($http_code !== 200) {
http_response_code(502);
// Forwarding a sanitized error is better for debugging without exposing too much.
echo json_encode(['error' => 'Failed to communicate with AI service.', 'details' => json_decode($response)]);
exit;
}
$result = json_decode($response, true);
// The response from Claude is inside content block.
$content_json = $result['content'][0]['text'] ?? null;
if (!$content_json) {
@ -85,15 +84,52 @@ if (!$content_json) {
exit;
}
// The actual persona data is expected to be a JSON string.
// We need to decode it one more time.
$personas_data = json_decode($content_json, true);
if (json_last_error() !== JSON_ERROR_NONE || !isset($personas_data['personas'])) {
// Fallback if the AI didn't return perfect JSON
http_response_code(500);
echo json_encode(['error' => 'Could not parse personas from AI response.', 'raw_content' => $content_json]);
exit;
}
echo json_encode($personas_data);
// --- Database Integration ---
try {
$pdo = db();
// 1. Create a new session
$stmt = $pdo->prepare("INSERT INTO sessions (target_audience, business_idea) VALUES (?, ?)");
$stmt->execute([$audience, $idea]);
$session_id = $pdo->lastInsertId();
// 2. Save each persona
$saved_personas = [];
$persona_stmt = $pdo->prepare(
"INSERT INTO personas (session_id, name, age, occupation, traits, concerns, style) VALUES (?, ?, ?, ?, ?, ?, ?)"
);
foreach ($personas_data['personas'] as $p) {
$persona_stmt->execute([
$session_id,
$p['name'],
$p['age'],
$p['occupation'],
$p['traits'],
$p['concerns'],
$p['style']
]);
$p['id'] = $pdo->lastInsertId(); // Add the new DB ID to the persona object
$saved_personas[] = $p;
}
// 3. Return the session ID and the updated persona data
echo json_encode([
'session_id' => $session_id,
'personas' => $saved_personas
]);
} catch (Exception $e) {
http_response_code(500);
// In a real app, log this error instead of echoing it.
echo json_encode(['error' => 'Database error while saving session.', 'details' => $e->getMessage()]);
exit;
}

View File

@ -108,3 +108,113 @@ body {
.text-white-75 {
color: rgba(255, 255, 255, 0.75) !important;
}
/* Chat Interface Styles */
.chat-card {
width: 100%;
max-width: 800px;
height: 70vh;
background-color: #fff;
}
.chat-card .card-header {
background-color: #F9FAFB;
border-bottom: 1px solid #E5E7EB;
}
#chat-log {
overflow-y: auto;
height: calc(70vh - 150px); /* Adjust based on header/footer height */
}
.chat-message {
margin-bottom: 1rem;
display: flex;
flex-direction: column;
}
.chat-message .message-bubble {
padding: 0.75rem 1.25rem;
border-radius: 1.25rem;
max-width: 80%;
word-wrap: break-word;
}
.chat-message.user .message-bubble {
background: linear-gradient(45deg, #D946EF, #A855F7);
color: white;
border-bottom-right-radius: 0.25rem;
align-self: flex-end;
}
.chat-message.persona .message-bubble {
background-color: #E5E7EB;
color: #1F2937;
border-bottom-left-radius: 0.25rem;
align-self: flex-start;
}
.chat-message .message-time {
font-size: 0.75rem;
color: #6B7280;
margin-top: 0.25rem;
}
.chat-message.user .message-time {
align-self: flex-end;
}
.chat-message.persona .message-time {
align-self: flex-start;
}
#chat-message-input {
resize: none;
}
/* Analysis Section Styles */
.analysis-gradient-btn {
background: linear-gradient(45deg, #fbbf24, #f97316); /* Amber to Orange */
border: none;
color: white;
padding: 0.8rem 1.5rem;
transition: all 0.3s ease;
}
.analysis-gradient-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
color: white;
}
.analysis-gradient-btn:disabled {
background: #E5E7EB;
cursor: not-allowed;
}
.analysis-card {
width: 100%;
max-width: 800px;
background-color: #FFFAF0; /* Floral White, a very light orange/yellow */
border: 1px solid #FDBA74; /* Light Orange */
}
#analysis-content h3 {
color: #D97706; /* Dark Amber */
font-weight: 700;
margin-top: 1.5rem;
margin-bottom: 0.75rem;
}
#analysis-content ul {
list-style-type: none;
padding-left: 0;
}
#analysis-content li {
background-color: rgba(255, 255, 255, 0.6);
padding: 0.75rem 1.25rem;
border-radius: 0.75rem;
margin-bottom: 0.5rem;
border-left: 5px solid #F97316; /* Orange */
}

View File

@ -1,15 +1,37 @@
// Screen Test - Main JS
document.addEventListener('DOMContentLoaded', function () {
// --- Element Selectors ---
const form = document.getElementById('idea-form');
const submitButton = form.querySelector('button[type="submit"]');
const personasContainer = document.getElementById('personas-container');
const personasGrid = document.getElementById('personas-grid');
const chatContainer = document.getElementById('chat-container');
const chatCard = document.querySelector('.chat-card');
const chatPersonaName = document.getElementById('chat-persona-name');
const chatPersonaIcon = document.getElementById('chat-persona-icon');
const closeChatBtn = document.getElementById('close-chat');
const chatLog = document.getElementById('chat-log');
const chatForm = document.getElementById('chat-form');
const chatMessageInput = document.getElementById('chat-message-input');
const sendMessageBtn = document.getElementById('send-message-btn');
const analysisSection = document.getElementById('analysis-section');
const generateAnalysisBtn = document.getElementById('generate-analysis-btn');
const analysisContainer = document.getElementById('analysis-container');
const analysisContent = document.getElementById('analysis-content');
// --- State Variables ---
let currentSessionId = null;
let personasData = [];
let activePersona = null;
let chatHistory = [];
// --- Event Listeners ---
// Main form submission -> Generate Personas & Create Session
if (form) {
form.addEventListener('submit', function (e) {
e.preventDefault();
const audience = document.getElementById('target-audience').value;
const idea = document.getElementById('business-idea').value;
@ -18,41 +40,37 @@ document.addEventListener('DOMContentLoaded', function () {
return;
}
// --- Loading State ---
// --- Reset UI and Set Loading State ---
personasContainer.classList.add('d-none');
chatContainer.classList.add('d-none');
analysisSection.classList.add('d-none');
analysisContainer.classList.add('d-none');
personasGrid.innerHTML = '';
const originalButtonText = submitButton.innerHTML;
submitButton.disabled = true;
submitButton.innerHTML = `
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Generating...
`;
personasContainer.classList.add('d-none');
personasGrid.innerHTML = '';
submitButton.innerHTML = `<span class="spinner-border spinner-border-sm"></span> Generating...`;
// --- API Call ---
// --- Fetch Personas & Create Session ---
fetch('api/generate_personas.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ audience, idea }),
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.error || 'An unknown error occurred.') });
}
if (!response.ok) return response.json().then(err => { throw new Error(err.error || 'Unknown error generating personas.') });
return response.json();
})
.then(data => {
// --- Render Personas ---
if (data.personas && data.personas.length > 0) {
renderPersonas(data.personas);
if (data.session_id && data.personas && data.personas.length > 0) {
// --- Store Session and Persona Data ---
currentSessionId = data.session_id;
personasData = data.personas; // Now contains DB IDs
renderPersonas(personasData);
personasContainer.classList.remove('d-none');
setTimeout(() => {
personasContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
setTimeout(() => personasContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100);
} else {
throw new Error('The AI did not return any personas. Please try refining your inputs.');
throw new Error('The AI did not return valid session data. Please try again.');
}
})
.catch(error => {
@ -60,37 +78,212 @@ document.addEventListener('DOMContentLoaded', function () {
alert(`An error occurred: ${error.message}`);
})
.finally(() => {
// --- Reset Button ---
submitButton.disabled = false;
submitButton.innerHTML = originalButtonText;
});
});
}
function renderPersonas(personas) {
const personaGradients = [
'persona-gradient-1',
'persona-gradient-2',
'persona-gradient-3'
];
// Persona card click -> Open Chat
personasGrid.addEventListener('click', function(e) {
const card = e.target.closest('.persona-card');
if (card) {
const personaIndex = card.dataset.personaIndex;
activePersona = personasData[personaIndex]; // activePersona now has the DB ID
const gradientIndex = card.classList.contains('persona-gradient-1') ? 1 : card.classList.contains('persona-gradient-2') ? 2 : 3;
openChat(activePersona, gradientIndex);
}
});
// Close chat button
closeChatBtn.addEventListener('click', function() {
chatContainer.classList.add('d-none');
analysisSection.classList.add('d-none');
analysisContainer.classList.add('d-none');
activePersona = null;
});
// Chat form submission -> Send message
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const message = chatMessageInput.value.trim();
if (message && activePersona && currentSessionId) {
addMessageToChatLog(message, 'user');
chatHistory.push({ sender: 'user', message: message });
chatMessageInput.value = '';
chatMessageInput.style.height = 'auto';
showTypingIndicator();
// --- Fetch Chat Response ---
fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: currentSessionId,
message: message,
persona: activePersona, // persona object now includes the ID
history: chatHistory.slice(0, -1)
}),
})
.then(response => {
if (!response.ok) return response.json().then(err => { throw new Error(err.error || 'An API error occurred.') });
return response.json();
})
.then(data => {
if (data.response) {
addMessageToChatLog(data.response, 'persona');
chatHistory.push({ sender: 'persona', message: data.response });
} else {
throw new Error('Empty response from AI.');
}
})
.catch(error => {
console.error('Chat Error:', error);
addMessageToChatLog(`Sorry, I encountered an error: ${error.message}`, 'persona', true);
})
.finally(() => {
hideTypingIndicator();
});
}
});
// Analysis button click -> Generate Analysis
generateAnalysisBtn.addEventListener('click', function() {
if (!activePersona || !currentSessionId || chatHistory.length < 5) return;
const originalButtonText = this.innerHTML;
this.disabled = true;
this.innerHTML = `<span class="spinner-border spinner-border-sm"></span> Analyzing...`;
// --- Fetch Analysis ---
fetch('api/analyze_chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: currentSessionId,
persona: activePersona,
history: chatHistory
}),
})
.then(response => {
if (!response.ok) return response.json().then(err => { throw new Error(err.error || 'An API error occurred during analysis.') });
return response.json();
})
.then(data => {
if (data.analysis) {
analysisContent.innerHTML = data.analysis;
analysisContainer.classList.remove('d-none');
analysisContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
throw new Error('Empty analysis from AI.');
}
})
.catch(error => {
console.error('Analysis Error:', error);
analysisContent.innerHTML = `<div class="alert alert-danger">Sorry, an error occurred during analysis: ${error.message}</div>`;
analysisContainer.classList.remove('d-none');
})
.finally(() => {
this.disabled = false;
this.innerHTML = originalButtonText;
});
});
// Auto-resize textarea for chat input
chatMessageInput.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
// --- Functions ---
function renderPersonas(personas) {
const personaGradients = ['persona-gradient-1', 'persona-gradient-2', 'persona-gradient-3'];
personasGrid.innerHTML = personas.map((persona, index) => `
<div class="col-lg-4 col-md-6">
<div class="card persona-card h-100 rounded-4 shadow-lg border-0 ${personaGradients[index % 3]}">
<div class="card persona-card h-100 rounded-4 shadow-lg border-0 ${personaGradients[index % 3]}" data-persona-index="${index}" style="cursor: pointer;">
<div class="card-body p-4">
<h3 class="card-title fw-bold">${persona.name}</h3>
<p class="card-subtitle mb-2 text-white-75">${persona.age}, ${persona.occupation}</p>
<div class="mt-4">
<h6 class="fw-semibold">Traits:</h6>
<p>${persona.traits}</p>
<h6 class="fw-semibold">Concerns:</h6>
<p>${persona.concerns}</p>
<h6 class="fw-semibold">Style:</h6>
<p>${persona.style}</p>
<h6 class="fw-semibold">Traits:</h6><p>${persona.traits}</p>
<h6 class="fw-semibold">Concerns:</h6><p>${persona.concerns}</p>
<h6 class="fw-semibold">Style:</h6><p>${persona.style}</p>
</div>
</div>
</div>
</div>
`).join('');
}
function openChat(persona, gradientIndex) {
chatPersonaName.textContent = persona.name;
const cardGradientClass = `persona-gradient-${gradientIndex}`;
chatCard.querySelector('.card-header').className = `card-header d-flex justify-content-between align-items-center p-3 rounded-top-5 ${cardGradientClass}`;
chatPersonaIcon.className = "bi bi-person-circle fs-3 me-3";
chatLog.innerHTML = '';
const welcomeMessage = `Hello! I'm ${persona.name}. Ask me anything about the business idea.`;
addMessageToChatLog(welcomeMessage, 'persona');
chatHistory = [{ sender: 'persona', message: welcomeMessage }];
chatContainer.classList.remove('d-none');
analysisSection.classList.add('d-none');
analysisContainer.classList.add('d-none');
generateAnalysisBtn.disabled = true;
chatContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function addMessageToChatLog(message, sender, isError = false) {
const messageElement = document.createElement('div');
messageElement.className = `chat-message ${sender === 'user' ? 'user' : 'persona'}`;
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
if (isError) bubble.classList.add('bg-danger', 'text-white');
bubble.textContent = message;
const time = document.createElement('div');
time.className = 'message-time';
time.textContent = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageElement.appendChild(bubble);
messageElement.appendChild(time);
chatLog.appendChild(messageElement);
chatLog.scrollTop = chatLog.scrollHeight;
// Check to enable analysis button (user messages count towards total)
if (sender === 'user' && chatHistory.filter(m => m.sender === 'user').length >= 2) {
analysisSection.classList.remove('d-none');
generateAnalysisBtn.disabled = false;
}
}
function showTypingIndicator() {
sendMessageBtn.disabled = true;
let indicator = chatLog.querySelector('.typing-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.className = 'chat-message persona typing-indicator';
indicator.innerHTML = `
<div class="message-bubble">
<div class="spinner-grow spinner-grow-sm" role="status"></div>
<div class="spinner-grow spinner-grow-sm mx-1" role="status"></div>
<div class="spinner-grow spinner-grow-sm" role="status"></div>
</div>
`;
chatLog.appendChild(indicator);
}
chatLog.scrollTop = chatLog.scrollHeight;
}
function hideTypingIndicator() {
sendMessageBtn.disabled = false;
const indicator = chatLog.querySelector('.typing-indicator');
if (indicator) {
indicator.remove();
}
}
});

View File

@ -1,17 +1,55 @@
<?php
// Generated by setup_mariadb_project.sh — edit as needed.
define('DB_HOST', '127.0.0.1');
define('DB_NAME', 'app_31009');
define('DB_USER', 'app_31009');
define('DB_PASS', '2c66b530-2a65-423a-a106-6760b49ad1a2');
// Database Configuration for Supabase (PostgreSQL)
// IMPORTANT: Replace with your actual Supabase credentials.
// You can find these in your Supabase project's Database settings.
define('DB_HOST', 'YOUR_SUPABASE_HOST');
define('DB_PORT', '5432'); // Default PostgreSQL port
define('DB_NAME', 'postgres');
define('DB_USER', 'postgres');
define('DB_PASS', 'YOUR_SUPABASE_PASSWORD');
/**
* Establishes a PDO database connection.
* @return PDO
*/
function db() {
static $pdo;
if (!$pdo) {
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
$dsn = 'pgsql:host=' . DB_HOST . ';port=' . DB_PORT . ';dbname=' . DB_NAME;
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
} catch (PDOException $e) {
// In a real app, you'd log this error and show a generic message.
// For now, we'll just die and show the error.
die("Database connection failed: " . $e->getMessage());
}
}
return $pdo;
}
// A simple function to run migrations.
// In a real application, you would use a more robust migration tool.
function run_migrations() {
$pdo = db();
$migration_dir = __DIR__ . '/migrations';
$files = glob($migration_dir . '/*.sql');
foreach ($files as $file) {
try {
$sql = file_get_contents($file);
$pdo->exec($sql);
} catch (Exception $e) {
// Log error or handle it
error_log("Failed to run migration: $file. Error: " . $e->getMessage());
}
}
}
// Automatically run migrations when this file is included.
// This is for simplicity in this development environment.
if (DB_HOST !== 'YOUR_SUPABASE_HOST') { // Don't run if not configured
run_migrations();
}

View File

@ -0,0 +1,45 @@
-- 001_initial_schema.sql
-- This script creates the initial database schema for Screen Test.
-- Create sessions table to store the core user inputs.
CREATE TABLE IF NOT EXISTS sessions (
id INT AUTO_INCREMENT PRIMARY KEY,
target_audience TEXT NOT NULL,
business_idea TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create personas table to store the generated AI personas for each session.
CREATE TABLE IF NOT EXISTS personas (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
age INT,
occupation VARCHAR(255),
traits TEXT,
concerns TEXT,
style TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create chat_messages table to store the conversation history.
CREATE TABLE IF NOT EXISTS chat_messages (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
persona_id INT NOT NULL,
sender ENUM('user', 'persona', 'system') NOT NULL,
message TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
FOREIGN KEY (persona_id) REFERENCES personas(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Create analyses table to store the generated conversation analysis.
CREATE TABLE IF NOT EXISTS analyses (
id INT AUTO_INCREMENT PRIMARY KEY,
session_id INT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -68,6 +68,50 @@
</div>
</div>
<!-- Chat Interface -->
<div id="chat-container" class="container mt-5 d-none">
<div class="card chat-card rounded-5 shadow-xl border-0">
<div class="card-header d-flex justify-content-between align-items-center p-3 rounded-top-5">
<div class="d-flex align-items-center">
<i id="chat-persona-icon" class="bi bi-person-circle fs-3 me-3"></i>
<h3 id="chat-persona-name" class="fw-bold mb-0"></h3>
</div>
<button id="close-chat" type="button" class="btn-close" aria-label="Close"></button>
</div>
<div class="card-body p-4" id="chat-log">
<!-- Chat messages will be appended here -->
</div>
<div class="card-footer p-3 border-top-0">
<form id="chat-form">
<div class="input-group">
<textarea id="chat-message-input" class="form-control form-control-lg" placeholder="Type your message..." rows="1"></textarea>
<button class="btn btn-primary primary-gradient-btn" type="submit" id="send-message-btn">
<i class="bi bi-send"></i>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Analysis Section -->
<div id="analysis-section" class="container mt-4 text-center d-none">
<button id="generate-analysis-btn" class="btn btn-lg rounded-4 fw-bold analysis-gradient-btn shadow-lg" disabled>
<i class="bi bi-bar-chart-line-fill me-2"></i> Generate Analysis
</button>
</div>
<div id="analysis-container" class="container mt-5 d-none">
<div class="card analysis-card rounded-5 shadow-xl border-0">
<div class="card-body p-4 p-lg-5">
<h2 class="text-center mb-4 fw-bolder">Conversation Analysis</h2>
<div id="analysis-content">
<!-- Analysis content will be injected here -->
</div>
</div>
</div>
</div>
<footer class="mt-5 text-center text-muted">
<p>Built with <a href="https://flatlogic.com" class="text-decoration-none">Flatlogic</a></p>
</footer>