Add reflection for each analysis
This commit is contained in:
parent
ce86231796
commit
4c7f636097
|
|
@ -49,7 +49,12 @@ class FinancialSituationMemory:
|
|||
embeddings=embeddings,
|
||||
ids=ids,
|
||||
)
|
||||
|
||||
|
||||
def get_latest_situation(self):
|
||||
"""Retrieve the latest entry from a situation_collection."""
|
||||
latest_entry = self.situation_collection.query(sort_by="id:descending", limit=1)
|
||||
return latest_entry
|
||||
|
||||
def get_memories(self, current_situation, n_matches=1):
|
||||
"""Find matching recommendations using OpenAI embeddings"""
|
||||
query_embedding = self.get_embedding(current_situation)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from pydantic import BaseModel
|
|||
from typing import Dict, Any, Optional
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
import glob
|
||||
import uuid
|
||||
|
|
@ -59,10 +60,12 @@ class AnalysisResponse(BaseModel):
|
|||
|
||||
class JobStatus(BaseModel):
|
||||
job_id: str
|
||||
start_time: Optional[float] = None
|
||||
status: str
|
||||
progress: Optional[str] = None
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
error: Optional[str] = None
|
||||
trading_agent: Any = None # Store the trading agent instance here
|
||||
|
||||
# In-memory job storage (in production, use Redis or database)
|
||||
jobs: Dict[str, JobStatus] = {}
|
||||
|
|
@ -77,6 +80,15 @@ async def health_check():
|
|||
|
||||
async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config_overrides: Dict[str, Any] = None):
|
||||
"""Background task to run the trading analysis without blocking the event loop"""
|
||||
|
||||
def execution_time() -> Optional[str]:
|
||||
if jobs[job_id].status == "completed" or jobs[job_id].status == "failed":
|
||||
return f"{time.time() - jobs[job_id].start_time:.2f} seconds"
|
||||
else:
|
||||
return None
|
||||
|
||||
jobs[job_id].start_time = time.time() # Save the start time in the job
|
||||
|
||||
try:
|
||||
jobs[job_id].status = "running"
|
||||
jobs[job_id].progress = "Initializing TradingAgents..."
|
||||
|
|
@ -87,16 +99,13 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
|
|||
config.update(config_overrides)
|
||||
|
||||
jobs[job_id].progress = "Setting up trading graph..."
|
||||
|
||||
# Define blocking work as sync function
|
||||
def _do_work():
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
jobs[job_id].progress = f"Analyzing {symbol} for {analysis_date}..."
|
||||
_, decision = ta.propagate(symbol, analysis_date)
|
||||
return decision
|
||||
|
||||
# Run blocking work in threadpool so the event loop stays responsive
|
||||
decision = await run_in_threadpool(_do_work)
|
||||
# Create and store the trading agent instance
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
jobs[job_id].trading_agent = ta # Store the instance
|
||||
jobs[job_id].progress = f"Analyzing {symbol} for {analysis_date}..."
|
||||
|
||||
# Run the propagate method in a threadpool
|
||||
_, decision = await run_in_threadpool(ta.propagate, symbol, analysis_date)
|
||||
|
||||
jobs[job_id].status = "completed"
|
||||
jobs[job_id].result = {
|
||||
|
|
@ -104,6 +113,20 @@ async def run_analysis_task(job_id: str, symbol: str, analysis_date: str, config
|
|||
"date": analysis_date,
|
||||
"decision": decision,
|
||||
"completed_at": datetime.now().isoformat(),
|
||||
"execution_time": execution_time()
|
||||
}
|
||||
jobs[job_id].progress = "Analysis completed successfully"
|
||||
|
||||
except Exception as e:
|
||||
jobs[job_id].status = "failed"
|
||||
jobs[job_id].error = str(e)
|
||||
jobs[job_id].progress = f"Error: {str(e)}"
|
||||
jobs[job_id].result = {
|
||||
"symbol": symbol,
|
||||
"date": analysis_date,
|
||||
"decision": decision,
|
||||
"completed_at": datetime.now().isoformat(),
|
||||
"execution_time": execution_time()
|
||||
}
|
||||
jobs[job_id].progress = "Analysis completed successfully"
|
||||
|
||||
|
|
@ -342,15 +365,81 @@ async def get_jobs():
|
|||
"""Get all jobs"""
|
||||
job_lst = []
|
||||
for job_id, job in jobs.items():
|
||||
job_lst.append({
|
||||
job_dict = {
|
||||
"job_id": job_id,
|
||||
"status": job.status,
|
||||
"progress": job.progress,
|
||||
"result": job.result,
|
||||
"error": job.error
|
||||
})
|
||||
"error": job.error,
|
||||
}
|
||||
|
||||
# Add execution_time if available
|
||||
if hasattr(job, 'start_time') and job.start_time is not None:
|
||||
if job.status in ["completed", "failed"]:
|
||||
job_dict["execution_time"] = f"{time.time() - job.start_time:.2f} seconds"
|
||||
|
||||
job_lst.append(job_dict)
|
||||
|
||||
return {"jobs": job_lst}
|
||||
|
||||
@app.post("/reflect-on-analysis/{symbol}/{date}")
|
||||
async def reflect_on_analysis(symbol: str, date: str, request: dict):
|
||||
"""Get latest financial situation memory for a specific analysis"""
|
||||
returns_losses = request.get("returns_losses")
|
||||
if returns_losses is None:
|
||||
raise HTTPException(status_code=400, detail="returns_losses is required in request body")
|
||||
|
||||
# Find the job that matches the symbol and date
|
||||
matching_job = None
|
||||
for job_id, job in jobs.items():
|
||||
if (job.result.get("symbol") == symbol.upper() and
|
||||
job.result.get("date") == date and
|
||||
hasattr(job, 'trading_agent') and
|
||||
job.trading_agent):
|
||||
matching_job = job
|
||||
break
|
||||
|
||||
if not matching_job:
|
||||
raise HTTPException(status_code=404, detail=f"No active job found for {symbol} on {date}")
|
||||
|
||||
if not hasattr(matching_job.trading_agent, 'memory') or not matching_job.trading_agent.memory:
|
||||
raise HTTPException(status_code=404, detail="No memory found for this analysis")
|
||||
|
||||
matching_job.trading_agent.reflect_and_remember(returns_losses)
|
||||
|
||||
try:
|
||||
bull_memory = matching_job.trading_agent.bull_memory
|
||||
bear_memory = matching_job.trading_agent.bear_memory
|
||||
trader_memory = matching_job.trading_agent.trader_memory
|
||||
invest_judge_memory = matching_job.trading_agent.invest_judge_memory
|
||||
risk_manager_memory = matching_job.trading_agent.risk_manager_memory
|
||||
|
||||
reflections = {}
|
||||
|
||||
latest_entry = bull_memory.get_latest_situation()
|
||||
reflections["bull_memory"] = latest_entry
|
||||
|
||||
latest_entry = bear_memory.get_latest_situation()
|
||||
reflections["bear_memory"] = latest_entry
|
||||
|
||||
latest_entry = trader_memory.get_latest_situation()
|
||||
reflections["trader_memory"] = latest_entry
|
||||
|
||||
latest_entry = invest_judge_memory.get_latest_situation()
|
||||
reflections["invest_judge_memory"] = latest_entry
|
||||
|
||||
latest_entry = risk_manager_memory.get_latest_situation()
|
||||
reflections["risk_manager_memory"] = latest_entry
|
||||
|
||||
return {
|
||||
"symbol": symbol.upper(),
|
||||
"date": date,
|
||||
"job_id": matching_job.job_id,
|
||||
"reflections": reflections
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Error retrieving latest situation: {str(e)}")
|
||||
|
||||
@app.get("/config")
|
||||
async def get_default_config():
|
||||
"""Get the default configuration"""
|
||||
|
|
@ -358,4 +447,4 @@ async def get_default_config():
|
|||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
||||
|
|
|
|||
|
|
@ -687,7 +687,13 @@ function App() {
|
|||
{selectedTransformedData ? (
|
||||
<TransformedDataAdapter analysisData={selectedTransformedData} />
|
||||
) : resultDetail ? (
|
||||
<AnalysisDataAdapter tradingResult={resultDetail} />
|
||||
<AnalysisDataAdapter
|
||||
tradingResult={{
|
||||
...resultDetail,
|
||||
symbol: selectedResult?.company || resultDetail?.symbol,
|
||||
date: selectedResult?.date || resultDetail?.date
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="p-8 text-center">
|
||||
<div className="text-gray-500 mb-2">Loading analysis data...</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import AnalysisWidgets from '../pages/AnalysisWidgets.tsx';
|
|||
|
||||
interface TradingAgentsResult {
|
||||
symbol: string;
|
||||
date?: string;
|
||||
final_decision?: {
|
||||
decision: string;
|
||||
reasoning: string;
|
||||
|
|
@ -90,7 +91,7 @@ const AnalysisDataAdapter: React.FC<AnalysisDataAdapterProps> = ({ tradingResult
|
|||
'Based on comprehensive analysis, the stock shows strong fundamentals with reasonable valuation and positive technical momentum, warranting a buy recommendation with appropriate risk management.'
|
||||
};
|
||||
|
||||
return <AnalysisWidgets data={transformedData} rawData={tradingResult} />;
|
||||
return <AnalysisWidgets symbol={tradingResult.symbol} date={tradingResult.date} data={transformedData} rawData={tradingResult} />;
|
||||
};
|
||||
|
||||
export default AnalysisDataAdapter;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
// New interface for the transformed JSON structure
|
||||
interface TransformedAnalysisData {
|
||||
|
|
@ -458,12 +458,77 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
|||
const [activeReportTab, setActiveReportTab] = React.useState<string>(availableReports[0]?.key || 'market');
|
||||
React.useEffect(() => { setActiveReportTab(availableReports[0]?.key || 'market'); }, [availableReports]);
|
||||
|
||||
// Reflection functionality state
|
||||
const [showReflectionModal, setShowReflectionModal] = React.useState(false);
|
||||
const [lossResults, setLossResults] = React.useState('');
|
||||
const [isReflecting, setIsReflecting] = React.useState(false);
|
||||
const [reflectionResults, setReflectionResults] = React.useState(null);
|
||||
const [reflectionError, setReflectionError] = React.useState('');
|
||||
|
||||
// Handle reflection API call
|
||||
const handleReflection = async () => {
|
||||
const symbolToUse = data.metadata.company_ticker;
|
||||
const dateToUse = data.metadata.analysis_date;
|
||||
|
||||
console.log('Reflection attempt:', { symbolToUse, dateToUse, lossResults });
|
||||
|
||||
if (!lossResults.trim()) {
|
||||
setReflectionError('Please enter your loss results');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsReflecting(true);
|
||||
setReflectionError('');
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:8000/reflect-on-analysis/${symbolToUse}/${dateToUse}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
returns_losses: lossResults
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Failed to perform reflection');
|
||||
}
|
||||
|
||||
const reflectionData = await response.json();
|
||||
setReflectionResults(reflectionData);
|
||||
setReflectionError('');
|
||||
} catch (error) {
|
||||
console.error('Reflection error:', error);
|
||||
setReflectionError(error.message || 'Failed to perform reflection');
|
||||
} finally {
|
||||
setIsReflecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setShowReflectionModal(false);
|
||||
setLossResults('');
|
||||
setReflectionResults(null);
|
||||
setReflectionError('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="mx-auto max-w-6xl space-y-4">
|
||||
{/* Metadata - Full width */}
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-3">Company Information</h2>
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h2 className="text-lg font-semibold">Company Information</h2>
|
||||
<button
|
||||
onClick={() => setShowReflectionModal(true)}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm font-medium"
|
||||
title="Reflect on Analysis"
|
||||
>
|
||||
🤔 Reflect on Analysis
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Stat label="Company" value={`${md?.company_name ?? '-'} (${md?.company_ticker ?? '-'})`} />
|
||||
<Stat label="Analysis Date" value={md?.analysis_date ?? '-'} />
|
||||
|
|
@ -595,12 +660,85 @@ const TransformedDataAdapter: React.FC<TransformedDataAdapterProps> = ({ analysi
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reflection Modal */}
|
||||
{showReflectionModal && (
|
||||
<div className="fixed inset-0 bg-gray-900 bg-opacity-75 flex justify-center items-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-lg p-6 w-full max-w-md mx-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-lg font-semibold">Reflect on Analysis</h2>
|
||||
<button
|
||||
onClick={handleModalClose}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleReflection(); }}>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Loss Results:
|
||||
</label>
|
||||
<textarea
|
||||
value={lossResults}
|
||||
onChange={(e) => setLossResults(e.target.value)}
|
||||
placeholder="e.g., Lost 15% due to unexpected earnings miss, market volatility, etc."
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
rows={4}
|
||||
/>
|
||||
{reflectionError && (
|
||||
<p className="mt-2 text-sm text-red-500">{reflectionError}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleModalClose}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isReflecting}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{isReflecting ? 'Reflecting...' : 'Submit Reflection'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* Display reflection results */}
|
||||
{reflectionResults && (
|
||||
<div className="mt-4 p-4 bg-gray-50 rounded-md">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-2">Reflection Results:</h4>
|
||||
<div className="space-y-2">
|
||||
{Object.entries(reflectionResults.reflections || {}).map(([agentType, reflection]) => (
|
||||
<div key={agentType} className="text-xs">
|
||||
<span className="font-medium capitalize">{agentType.replace('_', ' ')}:</span>
|
||||
<p className="text-gray-600 ml-2">{typeof reflection === 'string' ? reflection : JSON.stringify(reflection)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Render minimalist dashboard with all keys from the transformed JSON
|
||||
return <MinimalTransformedDashboard data={transformedWithConfig} />;
|
||||
return (
|
||||
<div>
|
||||
<MinimalTransformedDashboard data={transformedWithConfig} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransformedDataAdapter;
|
||||
|
|
|
|||
|
|
@ -34,14 +34,24 @@ interface AnalysisWidgetsProps {
|
|||
rawData?: any;
|
||||
onBackWidget?: () => void;
|
||||
onRefreshWidget?: () => void;
|
||||
symbol?: string;
|
||||
date?: string;
|
||||
}
|
||||
|
||||
const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBackWidget, onRefreshWidget }) => {
|
||||
const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBackWidget, onRefreshWidget, symbol, date }) => {
|
||||
const [activeTab, setActiveTab] = useState('bull');
|
||||
const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set());
|
||||
const [positionSize, setPositionSize] = useState(10000);
|
||||
const [portfolioAllocation, setPortfolioAllocation] = useState(4);
|
||||
|
||||
const [showReflectionModal, setShowReflectionModal] = useState(false);
|
||||
const [lossResults, setLossResults] = useState('');
|
||||
const [isReflecting, setIsReflecting] = useState(false);
|
||||
const [reflectionResults, setReflectionResults] = useState(null);
|
||||
const [reflectionError, setReflectionError] = useState('');
|
||||
|
||||
console.log('AnalysisWidgets props:', { symbol, date, hasRawData: !!rawData });
|
||||
|
||||
const handleBack = () => {
|
||||
if (onBackWidget) return onBackWidget();
|
||||
if (typeof window !== 'undefined' && window.history) window.history.back();
|
||||
|
|
@ -51,6 +61,47 @@ const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBack
|
|||
if (typeof window !== 'undefined') window.location.reload();
|
||||
};
|
||||
|
||||
const handleReflection = async () => {
|
||||
const symbolToUse = symbol || data.symbol || 'UNKNOWN';
|
||||
const dateToUse = date || new Date().toISOString().split('T')[0];
|
||||
|
||||
console.log('Reflection attempt:', { symbolToUse, dateToUse, lossResults });
|
||||
|
||||
if (!lossResults.trim()) {
|
||||
setReflectionError('Please enter your loss results');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsReflecting(true);
|
||||
setReflectionError('');
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:8000/reflect-on-analysis/${symbolToUse}/${dateToUse}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
returns_losses: lossResults
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Failed to perform reflection');
|
||||
}
|
||||
|
||||
const reflectionData = await response.json();
|
||||
setReflectionResults(reflectionData);
|
||||
setReflectionError('');
|
||||
} catch (error) {
|
||||
console.error('Reflection error:', error);
|
||||
setReflectionError(error.message || 'Failed to perform reflection');
|
||||
} finally {
|
||||
setIsReflecting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const WidgetHeader: React.FC<{ title: string }> = ({ title }) => (
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<button
|
||||
|
|
@ -63,57 +114,33 @@ const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBack
|
|||
←
|
||||
</button>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRefresh}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
⟳
|
||||
</button>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowReflectionModal(true)}
|
||||
className="px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm"
|
||||
title={`Reflect on Analysis (Symbol: ${symbol || data.symbol || 'N/A'}, Date: ${date || 'N/A'})`}
|
||||
>
|
||||
🤔 Reflect
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRefresh}
|
||||
className="p-2 rounded hover:bg-gray-100 text-gray-600"
|
||||
aria-label="Refresh"
|
||||
title="Refresh"
|
||||
>
|
||||
⟳
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Mock price data for chart
|
||||
const priceData = [
|
||||
{ date: '2024-07-01', price: 4.20, volume: 1200000 },
|
||||
{ date: '2024-07-08', price: 4.45, volume: 1500000 },
|
||||
{ date: '2024-07-15', price: 4.80, volume: 1800000 },
|
||||
{ date: '2024-07-22', price: 5.20, volume: 2100000 },
|
||||
{ date: '2024-07-26', price: 5.81, volume: 2500000 },
|
||||
];
|
||||
|
||||
const ownershipData = [
|
||||
{ name: 'Insider', value: data.insiderOwnership, color: '#8884d8' },
|
||||
{ name: 'Institutional', value: data.institutionalOwnership, color: '#82ca9d' },
|
||||
{ name: 'Retail', value: data.retailOwnership, color: '#ffc658' },
|
||||
];
|
||||
|
||||
const toggleSection = (section: string) => {
|
||||
const newExpanded = new Set(expandedSections);
|
||||
if (newExpanded.has(section)) {
|
||||
newExpanded.delete(section);
|
||||
} else {
|
||||
newExpanded.add(section);
|
||||
}
|
||||
setExpandedSections(newExpanded);
|
||||
};
|
||||
|
||||
const calculatePosition = () => {
|
||||
const allocation = (portfolioAllocation / 100) * positionSize;
|
||||
const shares = Math.floor(allocation / data.currentPrice);
|
||||
return { allocation, shares };
|
||||
};
|
||||
|
||||
const getRiskColor = (level: number) => {
|
||||
if (level < 30) return 'text-green-600';
|
||||
if (level < 70) return 'text-yellow-600';
|
||||
return 'text-red-600';
|
||||
};
|
||||
|
||||
const getTrendColor = (current: number, reference: number) => {
|
||||
return current > reference ? 'text-green-600' : 'text-red-600';
|
||||
const handleModalClose = () => {
|
||||
setShowReflectionModal(false);
|
||||
setLossResults('');
|
||||
setReflectionResults(null);
|
||||
setReflectionError('');
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -554,6 +581,59 @@ const AnalysisWidgets: React.FC<AnalysisWidgetsProps> = ({ data, rawData, onBack
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Reflection Modal */}
|
||||
{showReflectionModal && (
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity">
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg className="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
|
||||
Reflect on Analysis
|
||||
</h3>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
Please enter your loss results to reflect on the analysis.
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
value={lossResults}
|
||||
onChange={(e) => setLossResults(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
{reflectionError && (
|
||||
<p className="text-sm text-red-500">{reflectionError}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleReflection}
|
||||
disabled={isReflecting}
|
||||
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
>
|
||||
{isReflecting ? 'Reflecting...' : 'Reflect'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleModalClose}
|
||||
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue