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.
148 lines
6.2 KiB
TypeScript
148 lines
6.2 KiB
TypeScript
import React from 'react';
|
|
import type { AppState } from '../types';
|
|
|
|
interface Step6ConclusionProps {
|
|
appState: AppState | null;
|
|
t: any;
|
|
}
|
|
|
|
const MatrixTable: React.FC<{ data: { [row: string]: { [col: string]: string } }, title: string }> = ({ data, title }) => {
|
|
if (!data || Object.keys(data).length === 0) {
|
|
return <p>No data available for {title}.</p>;
|
|
}
|
|
|
|
// Robustly get all unique column headers from all rows
|
|
const columnSet = new Set<string>();
|
|
Object.values(data).forEach(rowData => {
|
|
if (rowData && typeof rowData === 'object') {
|
|
Object.keys(rowData).forEach(col => columnSet.add(col));
|
|
}
|
|
});
|
|
const columns = Array.from(columnSet).sort();
|
|
const rows = Object.keys(data);
|
|
|
|
return (
|
|
<div className="bg-light-secondary dark:bg-brand-secondary p-4 rounded-lg">
|
|
<h3 className="text-lg font-bold mb-3">{title}</h3>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm text-left">
|
|
<thead className="bg-light-accent dark:bg-brand-accent uppercase">
|
|
<tr>
|
|
<th scope="col" className="px-4 py-2"></th>
|
|
{columns.map(col => <th key={col} scope="col" className="px-4 py-2 text-center whitespace-nowrap">{col}</th>)}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{rows.map(row => (
|
|
<tr key={row} className="border-b border-light-accent dark:border-brand-accent">
|
|
<th scope="row" className="px-4 py-2 font-medium whitespace-nowrap">{row}</th>
|
|
{columns.map(col => {
|
|
// Safely access cell data to prevent crashes on malformed data
|
|
const cellData = data[row]?.[col] || ' ';
|
|
return (
|
|
<td key={col} className="px-4 py-2 text-center text-green-600 dark:text-green-400 font-bold">
|
|
{cellData}
|
|
</td>
|
|
);
|
|
})}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
type ProductMatrixItem = { product: string; availability: { competitor: string; has_offering: boolean }[] };
|
|
type IndustryMatrixItem = { industry: string; availability: { competitor: string; has_offering: boolean }[] };
|
|
|
|
const transformMatrixData = (matrixData: (ProductMatrixItem[] | IndustryMatrixItem[]) | undefined): { [row: string]: { [col: string]: string } } => {
|
|
const tableData: { [row: string]: { [col: string]: string } } = {};
|
|
if (!matrixData || !Array.isArray(matrixData) || matrixData.length === 0) {
|
|
return tableData;
|
|
}
|
|
|
|
const allCompetitors = new Set<string>();
|
|
matrixData.forEach(row => {
|
|
if (row && Array.isArray(row.availability)) {
|
|
row.availability.forEach(item => {
|
|
if (item && typeof item.competitor === 'string') {
|
|
allCompetitors.add(item.competitor);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
const competitorList = Array.from(allCompetitors).sort();
|
|
|
|
matrixData.forEach(row => {
|
|
if (!row) return;
|
|
|
|
const rowKey = 'product' in row ? row.product : ('industry' in row ? row.industry : undefined);
|
|
|
|
if (typeof rowKey !== 'string' || !rowKey) {
|
|
return;
|
|
}
|
|
|
|
const availabilityMap = new Map<string, boolean>();
|
|
if (Array.isArray(row.availability)) {
|
|
row.availability.forEach(item => {
|
|
if (item && typeof item.competitor === 'string') {
|
|
availabilityMap.set(item.competitor, item.has_offering);
|
|
}
|
|
});
|
|
}
|
|
|
|
const rowObject: { [col: string]: string } = {};
|
|
competitorList.forEach(competitor => {
|
|
rowObject[competitor] = availabilityMap.get(competitor) ? '✓' : ' ';
|
|
});
|
|
tableData[rowKey] = rowObject;
|
|
});
|
|
|
|
return tableData;
|
|
};
|
|
|
|
const Step6Conclusion: React.FC<Step6ConclusionProps> = ({ appState, t }) => {
|
|
if (!appState || !appState.conclusion) return <p>{t.generating}</p>;
|
|
const { conclusion, company } = appState;
|
|
|
|
const productMatrixForTable = transformMatrixData(conclusion.product_matrix);
|
|
const industryMatrixForTable = transformMatrixData(conclusion.industry_matrix);
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h2 className="text-2xl font-bold">{t.title}</h2>
|
|
</div>
|
|
|
|
<p className="text-light-subtle dark:text-brand-light mb-6">{t.subtitle}</p>
|
|
|
|
<div className="space-y-6">
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<MatrixTable data={productMatrixForTable} title={t.productMatrix} />
|
|
<MatrixTable data={industryMatrixForTable} title={t.industryMatrix} />
|
|
</div>
|
|
|
|
<div className="bg-light-secondary dark:bg-brand-secondary p-4 rounded-lg">
|
|
<h3 className="text-lg font-bold mb-3">{t.summary}</h3>
|
|
<p className="text-light-subtle dark:text-brand-light">{conclusion.summary}</p>
|
|
</div>
|
|
|
|
<div className="bg-light-secondary dark:bg-brand-secondary p-4 rounded-lg">
|
|
<h3 className="text-lg font-bold mb-3">{t.opportunities(company.name)}</h3>
|
|
<p className="text-light-subtle dark:text-brand-light">{conclusion.opportunities}</p>
|
|
</div>
|
|
|
|
<div className="bg-light-secondary dark:bg-brand-secondary p-4 rounded-lg">
|
|
<h3 className="text-lg font-bold mb-3">{t.nextQuestions}</h3>
|
|
<ul className="list-disc list-inside text-light-subtle dark:text-brand-light space-y-1">
|
|
{(conclusion.next_questions || []).map((q, i) => <li key={i}>{q}</li>)}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Step6Conclusion; |