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.
91 lines
4.9 KiB
TypeScript
91 lines
4.9 KiB
TypeScript
import React, { useState } from 'react';
|
|
import type { AppState, Battlecard } from '../types';
|
|
|
|
interface Step7BattlecardsProps {
|
|
appState: AppState | null;
|
|
t: any;
|
|
}
|
|
|
|
const ProfileIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 inline-block" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" /></svg>);
|
|
const StrengthsIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 inline-block" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L9 9.414V13a1 1 0 102 0V9.414l1.293 1.293a1 1 0 001.414-1.414z" clipRule="evenodd" /></svg>);
|
|
const LandmineIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 inline-block" viewBox="0 0 20 20" fill="currentColor"><path d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" /></svg>);
|
|
const BulletIcon = () => (<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2 inline-block" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM7 9a1 1 0 000 2h6a1 1 0 100-2H7z" clipRule="evenodd" /></svg>);
|
|
|
|
const BattlecardComponent: React.FC<{ battlecard: Battlecard, t: any }> = ({ battlecard, t }) => {
|
|
|
|
return (
|
|
<div className="bg-light-secondary dark:bg-brand-secondary p-6 rounded-lg shadow-lg relative">
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h3 className="text-xl font-bold flex items-center mb-2"><ProfileIcon /> {t.profile}</h3>
|
|
<p className="text-light-subtle dark:text-brand-light pl-7">{battlecard.competitor_profile.focus}</p>
|
|
<p className="text-light-subtle dark:text-brand-light pl-7 mt-1">{battlecard.competitor_profile.positioning}</p>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-bold flex items-center mb-2"><StrengthsIcon /> {t.strengths}</h3>
|
|
<ul className="list-disc list-inside space-y-1 text-light-subtle dark:text-brand-light pl-7">
|
|
{(battlecard.strengths_vs_weaknesses || []).map((item, i) => <li key={i}>{item}</li>)}
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-bold flex items-center mb-2"><LandmineIcon /> {t.landmines}</h3>
|
|
<ul className="list-disc list-inside space-y-1 text-light-subtle dark:text-brand-light pl-7">
|
|
{(battlecard.landmine_questions || []).map((item, i) => <li key={i}>{item}</li>)}
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-bold flex items-center mb-2"><BulletIcon /> {t.silverBullet}</h3>
|
|
<blockquote className="border-l-4 border-brand-highlight pl-4 ml-7">
|
|
<p className="text-lg italic text-light-subtle dark:text-brand-light">"{battlecard.silver_bullet}"</p>
|
|
</blockquote>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Step7_Battlecards: React.FC<Step7BattlecardsProps> = ({ appState, t }) => {
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
|
|
if (!appState || !appState.battlecards || appState.battlecards.length === 0) {
|
|
return (
|
|
<div>
|
|
<h2 className="text-2xl font-bold mb-4">{t.title}</h2>
|
|
<p className="text-light-subtle dark:text-brand-light">{t.generating}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const { battlecards } = appState;
|
|
const activeBattlecard = battlecards[activeTab];
|
|
|
|
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="flex border-b border-light-accent dark:border-brand-accent mb-4 overflow-x-auto">
|
|
{battlecards.map((card, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => setActiveTab(index)}
|
|
className={`py-2 px-4 font-semibold text-sm focus:outline-none whitespace-nowrap ${
|
|
activeTab === index
|
|
? 'border-b-2 border-brand-highlight text-light-text dark:text-white'
|
|
: 'text-light-subtle dark:text-brand-light hover:bg-light-accent dark:hover:bg-brand-accent'
|
|
}`}
|
|
>
|
|
{card.competitor_name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{activeBattlecard && <BattlecardComponent battlecard={activeBattlecard} t={t.card} />}
|
|
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Step7_Battlecards; |