feat(competitor-analysis): Fix 404, SDK compatibility, and update docs
Resolved multiple issues preventing the 'competitor-analysis' app from running and serving its frontend:
1. **Fixed Python SyntaxError in Prompts:** Corrected unterminated string literals and ensure proper multi-line string formatting (using .format() instead of f-strings for complex prompts) in .
2. **Addressed Python SDK Compatibility (google-generativeai==0.3.0):**
* Removed for and by adapting the orchestrator to pass JSON schemas as direct Python dictionaries, as required by the older SDK version.
* Updated with detailed guidance on handling / imports and dictionary-based schema definitions for older SDKs.
3. **Corrected Frontend Build Dependencies:** Moved critical build dependencies (like , , ) from to in .
* Updated to include this pitfall, ensuring frontend build tools are installed in Docker.
4. **Updated Documentation:**
* : Added comprehensive lessons learned regarding dependencies, Python SDK versioning (specifically and imports for ), and robust multi-line prompt handling.
* : Integrated specific details of the encountered errors and their solutions, making the migration report a more complete historical record and guide.
These changes collectively fix the 404 error by ensuring the Python backend starts correctly and serves the frontend assets after a successful build.
This commit is contained in:
97
competitor-analysis-app/components/Step4_Analysis.tsx
Normal file
97
competitor-analysis-app/components/Step4_Analysis.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import type { Analysis } from '../types';
|
||||
import EvidencePopover from './EvidencePopover';
|
||||
|
||||
interface Step4AnalysisProps {
|
||||
analyses: Analysis[];
|
||||
t: any;
|
||||
}
|
||||
|
||||
const DownloadIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>);
|
||||
|
||||
const downloadJSON = (data: any, filename: string) => {
|
||||
const jsonStr = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const OverlapBar: React.FC<{ score: number }> = ({ score }) => (
|
||||
<div className="w-full bg-light-accent dark:bg-brand-accent rounded-full h-2.5">
|
||||
<div className="bg-brand-highlight h-2.5 rounded-full" style={{ width: `${score}%` }}></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Step4Analysis: React.FC<Step4AnalysisProps> = ({ analyses, t }) => {
|
||||
const sortedAnalyses = [...analyses].sort((a, b) => b.overlap_score - a.overlap_score);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">{t.title}</h2>
|
||||
<p className="text-light-subtle dark:text-brand-light mb-6">
|
||||
{t.subtitle}
|
||||
</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
{sortedAnalyses.map((analysis, index) => (
|
||||
<div key={index} className="bg-light-secondary dark:bg-brand-secondary p-6 rounded-lg shadow-lg">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-light-text dark:text-white">{analysis.competitor.name}</h3>
|
||||
<a href={analysis.competitor.url} target="_blank" rel="noopener noreferrer" className="text-blue-500 dark:text-blue-400 hover:underline text-sm">
|
||||
{analysis.competitor.url}
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => downloadJSON(analysis, `analysis_${analysis.competitor.name.replace(/ /g, '_')}.json`)}
|
||||
title={t.downloadJson_title}
|
||||
className="text-light-subtle dark:text-brand-light hover:text-light-text dark:hover:text-white p-1 rounded-full"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</button>
|
||||
<EvidencePopover evidence={analysis.evidence} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">{t.portfolio}</h4>
|
||||
<ul className="list-disc list-inside text-light-subtle dark:text-brand-light text-sm space-y-1">
|
||||
{analysis.portfolio.map((p, i) => <li key={i}><strong>{p.product}:</strong> {p.purpose}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">{t.differentiators}</h4>
|
||||
<ul className="list-disc list-inside text-light-subtle dark:text-brand-light text-sm space-y-1">
|
||||
{analysis.differentiators.map((d, i) => <li key={i}>{d}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">{t.targetIndustries}</h4>
|
||||
<div className="flex flex-wrap gap-2 mb-2">
|
||||
{analysis.target_industries.map((ind, i) => (
|
||||
<span key={i} className="bg-light-accent dark:bg-brand-accent text-xs font-medium px-2.5 py-0.5 rounded-full">{ind}</span>
|
||||
))}
|
||||
</div>
|
||||
<span className="bg-gray-500 dark:bg-gray-600 text-white text-xs font-medium px-2.5 py-0.5 rounded-full">{analysis.delivery_model}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold mb-2">{t.overlapScore}: {analysis.overlap_score}%</h4>
|
||||
<OverlapBar score={analysis.overlap_score} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step4Analysis;
|
||||
Reference in New Issue
Block a user