functionality tests mostly pass
This commit is contained in:
@@ -1,78 +1,142 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const PromptDisplay = () => {
|
||||
const [prompts, setPrompts] = useState([]);
|
||||
const [prompts, setPrompts] = useState([]); // Changed to array to handle multiple prompts
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState(null);
|
||||
|
||||
// Mock data for demonstration
|
||||
const mockPrompts = [
|
||||
"Write about a time when you felt completely at peace with yourself and the world around you. What were the circumstances that led to this feeling, and how did it change your perspective on life?",
|
||||
"Imagine you could have a conversation with your future self 10 years from now. What questions would you ask, and what advice do you think your future self would give you?",
|
||||
"Describe a place from your childhood that holds special meaning to you. What made this place so significant, and how does remembering it make you feel now?",
|
||||
"Write about a skill or hobby you've always wanted to learn but never had the chance to pursue. What has held you back, and what would be the first step to starting?",
|
||||
"Reflect on a mistake you made that ultimately led to personal growth. What did you learn from the experience, and how has it shaped who you are today?",
|
||||
"Imagine you wake up tomorrow with the ability to understand and speak every language in the world. How would this change your life, and what would you do with this newfound ability?"
|
||||
];
|
||||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
const [viewMode, setViewMode] = useState('history'); // 'history' or 'drawn'
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setPrompts(mockPrompts);
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
fetchMostRecentPrompt();
|
||||
}, []);
|
||||
|
||||
const handleSelectPrompt = (index) => {
|
||||
setSelectedPrompt(index);
|
||||
const fetchMostRecentPrompt = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Try to fetch from actual API first
|
||||
const response = await fetch('/api/v1/prompts/history');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// API returns array directly, not object with 'prompts' key
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// Get the most recent prompt (first in array, position 0)
|
||||
// Show only one prompt from history
|
||||
setPrompts([{ text: data[0].text, position: data[0].position }]);
|
||||
setViewMode('history');
|
||||
} else {
|
||||
// No history yet, show placeholder
|
||||
setPrompts([{ text: "No recent prompts in history. Draw some prompts to get started!", position: 0 }]);
|
||||
}
|
||||
} else {
|
||||
// API not available, use mock data
|
||||
setPrompts([{ text: "Write about a time when you felt completely at peace with yourself and the world around you. What were the circumstances that led to this feeling, and how did it change your perspective on life?", position: 0 }]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching prompt:', err);
|
||||
// Fallback to mock data
|
||||
setPrompts([{ text: "Imagine you could have a conversation with your future self 10 years from now. What questions would you ask, and what advice do you think your future self would give you?", position: 0 }]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrawPrompts = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSelectedIndex(null);
|
||||
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
// const response = await fetch('/api/v1/prompts/draw');
|
||||
// const data = await response.json();
|
||||
// setPrompts(data.prompts);
|
||||
|
||||
// For now, use mock data
|
||||
setTimeout(() => {
|
||||
setPrompts(mockPrompts);
|
||||
setSelectedPrompt(null);
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
// Draw 3 prompts from pool (Task 4)
|
||||
const response = await fetch('/api/v1/prompts/draw?count=3');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// Draw API returns object with 'prompts' array
|
||||
if (data.prompts && data.prompts.length > 0) {
|
||||
// Show all drawn prompts
|
||||
const drawnPrompts = data.prompts.map((text, index) => ({
|
||||
text,
|
||||
position: index
|
||||
}));
|
||||
setPrompts(drawnPrompts);
|
||||
setViewMode('drawn');
|
||||
} else {
|
||||
setError('No prompts available in pool. Please fill the pool first.');
|
||||
}
|
||||
} else {
|
||||
setError('Failed to draw prompts. Please try again.');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to draw prompts. Please try again.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToHistory = async () => {
|
||||
if (selectedPrompt === null) {
|
||||
setError('Please select a prompt first');
|
||||
const handleAddToHistory = async (index) => {
|
||||
if (index < 0 || index >= prompts.length) {
|
||||
setError('Invalid prompt index');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
// await fetch(`/api/v1/prompts/select/${selectedPrompt}`, { method: 'POST' });
|
||||
const promptText = prompts[index].text;
|
||||
|
||||
// For now, just show success message
|
||||
alert(`Prompt ${selectedPrompt + 1} added to history!`);
|
||||
setSelectedPrompt(null);
|
||||
// Send the prompt to the API to add to history
|
||||
const response = await fetch('/api/v1/prompts/select', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ prompt_text: promptText }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
// Mark as selected and show success
|
||||
setSelectedIndex(index);
|
||||
|
||||
// Instead of showing an alert, refresh the page to show the updated history
|
||||
// The default view shows the most recent prompt from history
|
||||
alert(`Prompt added to history as ${data.position_in_history}! Refreshing to show updated history...`);
|
||||
|
||||
// Refresh the page to show the updated history
|
||||
// The default view shows the most recent prompt from history (position 0)
|
||||
fetchMostRecentPrompt();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
setError(`Failed to add prompt to history: ${errorData.detail || 'Unknown error'}`);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to add prompt to history');
|
||||
}
|
||||
};
|
||||
|
||||
const handleFillPool = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch('/api/v1/prompts/fill-pool', { method: 'POST' });
|
||||
if (response.ok) {
|
||||
alert('Prompt pool filled successfully!');
|
||||
// Refresh the prompt
|
||||
fetchMostRecentPrompt();
|
||||
} else {
|
||||
setError('Failed to fill prompt pool');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Failed to fill prompt pool');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="text-center p-8">
|
||||
<div className="spinner mx-auto"></div>
|
||||
<p className="mt-4">Loading prompts...</p>
|
||||
<p className="mt-4">Loading prompt...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -88,83 +152,98 @@ const PromptDisplay = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{prompts.length === 0 ? (
|
||||
<div className="text-center p-8">
|
||||
<i className="fas fa-inbox fa-3x mb-4" style={{ color: 'var(--gray-color)' }}></i>
|
||||
<h3>No Prompts Available</h3>
|
||||
<p className="mb-4">The prompt pool is empty. Please fill the pool to get started.</p>
|
||||
<button className="btn btn-primary" onClick={handleDrawPrompts}>
|
||||
<i className="fas fa-plus"></i> Fill Pool First
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
{prompts.length > 0 ? (
|
||||
<>
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{prompts.map((prompt, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`prompt-card cursor-pointer ${selectedPrompt === index ? 'selected' : ''}`}
|
||||
onClick={() => handleSelectPrompt(index)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${selectedPrompt === index ? 'bg-green-100 text-green-600' : 'bg-blue-100 text-blue-600'}`}>
|
||||
{selectedPrompt === index ? (
|
||||
<i className="fas fa-check"></i>
|
||||
) : (
|
||||
<span>{index + 1}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<p className="prompt-text">{prompt}</p>
|
||||
<div className="prompt-meta">
|
||||
<span>
|
||||
<i className="fas fa-ruler-combined mr-1"></i>
|
||||
{prompt.length} characters
|
||||
</span>
|
||||
<span>
|
||||
{selectedPrompt === index ? (
|
||||
<span className="text-green-600">
|
||||
<i className="fas fa-check-circle mr-1"></i>
|
||||
Selected
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-500">
|
||||
Click to select
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<div className="mb-6">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{prompts.map((promptObj, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`prompt-card cursor-pointer ${selectedIndex === index ? 'selected' : ''}`}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${selectedIndex === index ? 'bg-green-100 text-green-600' : 'bg-blue-100 text-blue-600'}`}>
|
||||
{selectedIndex === index ? (
|
||||
<i className="fas fa-check"></i>
|
||||
) : (
|
||||
<span>{index + 1}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<p className="prompt-text">{promptObj.text}</p>
|
||||
<div className="prompt-meta">
|
||||
<span>
|
||||
<i className="fas fa-ruler-combined mr-1"></i>
|
||||
{promptObj.text.length} characters
|
||||
</span>
|
||||
<span>
|
||||
{selectedIndex === index ? (
|
||||
<span className="text-green-600">
|
||||
<i className="fas fa-check-circle mr-1"></i>
|
||||
Selected
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-500">
|
||||
Click to select
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center mt-6">
|
||||
<div>
|
||||
{selectedPrompt !== null && (
|
||||
<button className="btn btn-success" onClick={handleAddToHistory}>
|
||||
<i className="fas fa-history"></i> Add Prompt #{selectedPrompt + 1} to History
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-2">
|
||||
<button className="btn btn-secondary" onClick={handleDrawPrompts}>
|
||||
<i className="fas fa-redo"></i> Draw New Set
|
||||
<button
|
||||
className="btn btn-success flex-1"
|
||||
onClick={() => handleAddToHistory(selectedIndex !== null ? selectedIndex : 0)}
|
||||
disabled={selectedIndex === null}
|
||||
>
|
||||
<i className="fas fa-history"></i>
|
||||
{selectedIndex !== null ? 'Use Selected Prompt' : 'Select a Prompt First'}
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={handleDrawPrompts}>
|
||||
<i className="fas fa-dice"></i> Draw 6 More
|
||||
<button className="btn btn-primary flex-1" onClick={handleDrawPrompts}>
|
||||
<i className="fas fa-dice"></i>
|
||||
{viewMode === 'history' ? 'Draw 3 New Prompts' : 'Draw 3 More Prompts'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-secondary" onClick={handleFillPool}>
|
||||
<i className="fas fa-sync"></i> Fill Prompt Pool
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-sm text-gray-600">
|
||||
<div className="mt-6 text-sm text-gray-600">
|
||||
<p>
|
||||
<i className="fas fa-info-circle mr-1"></i>
|
||||
Select a prompt by clicking on it, then add it to your history. The AI will use your history to generate more relevant prompts in the future.
|
||||
<strong>
|
||||
{viewMode === 'history' ? 'Most Recent Prompt from History' : `${prompts.length} Drawn Prompts`}:
|
||||
</strong>
|
||||
{viewMode === 'history'
|
||||
? ' This is the latest prompt from your history. Using it helps the AI learn your preferences.'
|
||||
: ' Select a prompt to use for journaling. The AI will learn from your selection.'}
|
||||
</p>
|
||||
<p className="mt-2">
|
||||
<i className="fas fa-lightbulb mr-1"></i>
|
||||
<strong>Tip:</strong> The prompt pool needs regular refilling. Check the stats panel
|
||||
to see how full it is.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center p-8">
|
||||
<i className="fas fa-inbox fa-3x mb-4" style={{ color: 'var(--gray-color)' }}></i>
|
||||
<h3>No Prompts Available</h3>
|
||||
<p className="mb-4">There are no prompts in history or pool. Get started by filling the pool.</p>
|
||||
<button className="btn btn-primary" onClick={handleFillPool}>
|
||||
<i className="fas fa-plus"></i> Fill Prompt Pool
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,59 +18,61 @@ const StatsDashboard = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Simulate API calls
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
// TODO: Replace with actual API calls
|
||||
// const poolResponse = await fetch('/api/v1/prompts/stats');
|
||||
// const historyResponse = await fetch('/api/v1/prompts/history/stats');
|
||||
// const poolData = await poolResponse.json();
|
||||
// const historyData = await historyResponse.json();
|
||||
|
||||
// Mock data for demonstration
|
||||
setTimeout(() => {
|
||||
setStats({
|
||||
pool: {
|
||||
total: 15,
|
||||
target: 20,
|
||||
sessions: Math.floor(15 / 6),
|
||||
needsRefill: 15 < 20
|
||||
},
|
||||
history: {
|
||||
total: 8,
|
||||
capacity: 60,
|
||||
available: 52,
|
||||
isFull: false
|
||||
}
|
||||
});
|
||||
setLoading(false);
|
||||
}, 800);
|
||||
} catch (error) {
|
||||
console.error('Error fetching stats:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchStats();
|
||||
}, []);
|
||||
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
// Fetch pool stats
|
||||
const poolResponse = await fetch('/api/v1/prompts/stats');
|
||||
const poolData = poolResponse.ok ? await poolResponse.json() : {
|
||||
total_prompts: 0,
|
||||
target_pool_size: 20,
|
||||
available_sessions: 0,
|
||||
needs_refill: true
|
||||
};
|
||||
|
||||
// Fetch history stats
|
||||
const historyResponse = await fetch('/api/v1/prompts/history/stats');
|
||||
const historyData = historyResponse.ok ? await historyResponse.json() : {
|
||||
total_prompts: 0,
|
||||
history_capacity: 60,
|
||||
available_slots: 60,
|
||||
is_full: false
|
||||
};
|
||||
|
||||
setStats({
|
||||
pool: {
|
||||
total: poolData.total_prompts || 0,
|
||||
target: poolData.target_pool_size || 20,
|
||||
sessions: poolData.available_sessions || 0,
|
||||
needsRefill: poolData.needs_refill || true
|
||||
},
|
||||
history: {
|
||||
total: historyData.total_prompts || 0,
|
||||
capacity: historyData.history_capacity || 60,
|
||||
available: historyData.available_slots || 60,
|
||||
isFull: historyData.is_full || false
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching stats:', error);
|
||||
// Use default values on error
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFillPool = async () => {
|
||||
try {
|
||||
// TODO: Replace with actual API call
|
||||
// await fetch('/api/v1/prompts/fill-pool', { method: 'POST' });
|
||||
|
||||
// For now, update local state
|
||||
setStats(prev => ({
|
||||
...prev,
|
||||
pool: {
|
||||
...prev.pool,
|
||||
total: prev.pool.target,
|
||||
sessions: Math.floor(prev.pool.target / 6),
|
||||
needsRefill: false
|
||||
}
|
||||
}));
|
||||
|
||||
alert('Prompt pool filled successfully!');
|
||||
const response = await fetch('/api/v1/prompts/fill-pool', { method: 'POST' });
|
||||
if (response.ok) {
|
||||
alert('Prompt pool filled successfully!');
|
||||
// Refresh stats
|
||||
fetchStats();
|
||||
} else {
|
||||
alert('Failed to fill prompt pool');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Failed to fill prompt pool');
|
||||
}
|
||||
@@ -120,7 +122,7 @@ const StatsDashboard = () => {
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${(stats.pool.total / stats.pool.target) * 100}%` }}
|
||||
style={{ width: `${Math.min((stats.pool.total / stats.pool.target) * 100, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 mt-1">
|
||||
@@ -146,7 +148,7 @@ const StatsDashboard = () => {
|
||||
<div className="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
className="bg-purple-600 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${(stats.history.total / stats.history.capacity) * 100}%` }}
|
||||
style={{ width: `${Math.min((stats.history.total / stats.history.capacity) * 100, 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 mt-1">
|
||||
|
||||
@@ -15,15 +15,7 @@ import StatsDashboard from '../components/StatsDashboard.jsx';
|
||||
<div class="lg:col-span-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2><i class="fas fa-scroll"></i> Today's Prompts</h2>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-primary">
|
||||
<i class="fas fa-redo"></i> Draw New Prompts
|
||||
</button>
|
||||
<button class="btn btn-secondary">
|
||||
<i class="fas fa-plus"></i> Fill Pool
|
||||
</button>
|
||||
</div>
|
||||
<h2><i class="fas fa-scroll"></i> Today's Writing Prompt</h2>
|
||||
</div>
|
||||
|
||||
<PromptDisplay client:load />
|
||||
@@ -45,17 +37,14 @@ import StatsDashboard from '../components/StatsDashboard.jsx';
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button class="btn btn-primary">
|
||||
<i class="fas fa-dice"></i> Draw 6 Prompts
|
||||
<button class="btn btn-primary" onclick="document.querySelector('button[onclick*=\"handleDrawPrompts\"]')?.click()">
|
||||
<i class="fas fa-dice"></i> Draw 3 Prompts
|
||||
</button>
|
||||
<button class="btn btn-secondary">
|
||||
<i class="fas fa-sync"></i> Refill Pool
|
||||
<button class="btn btn-secondary" onclick="document.querySelector('button[onclick*=\"handleFillPool\"]')?.click()">
|
||||
<i class="fas fa-sync"></i> Fill Pool
|
||||
</button>
|
||||
<button class="btn btn-success">
|
||||
<i class="fas fa-palette"></i> Generate Themes
|
||||
</button>
|
||||
<button class="btn btn-warning">
|
||||
<i class="fas fa-history"></i> View History
|
||||
<button class="btn btn-warning" onclick="window.location.href='/api/v1/prompts/history'">
|
||||
<i class="fas fa-history"></i> View History (API)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,8 +86,8 @@ a:hover {
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@@ -133,7 +133,6 @@ a:hover {
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user