v.2
This commit is contained in:
parent
f521b6df27
commit
2e3424ad5c
104
api/analyze_chat.php
Normal file
104
api/analyze_chat.php
Normal 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
17
api/chat.php
Normal 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
|
||||
]);
|
||||
@ -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;
|
||||
}
|
||||
@ -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 */
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]);
|
||||
$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();
|
||||
}
|
||||
45
db/migrations/001_initial_schema.sql
Normal file
45
db/migrations/001_initial_schema.sql
Normal 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;
|
||||
44
index.php
44
index.php
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user