Files
Floke 6a7d56a9c9 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.
2026-01-10 09:10:00 +00:00

131 lines
6.1 KiB
TypeScript

import React, { useState } from 'react';
import type { Product, TargetIndustry } from '../types';
import { EditableCard } from './EditableCard';
import EvidencePopover from './EvidencePopover';
import { fetchProductDetails } from '../services/geminiService';
interface Step1ExtractionProps {
products: Product[];
industries: TargetIndustry[];
onProductsChange: (products: Product[]) => void;
onIndustriesChange: (industries: TargetIndustry[]) => void;
t: any;
lang: 'de' | 'en';
}
const Step1Extraction: React.FC<Step1ExtractionProps> = ({ products, industries, onProductsChange, onIndustriesChange, t, lang }) => {
const [newProductName, setNewProductName] = useState('');
const [newProductUrl, setNewProductUrl] = useState('');
const [isAdding, setIsAdding] = useState(false);
const [addError, setAddError] = useState<string | null>(null);
const handleAddProduct = async (e: React.FormEvent) => {
e.preventDefault();
if (!newProductName || !newProductUrl) return;
setIsAdding(true);
setAddError(null);
try {
const newProduct = await fetchProductDetails(newProductName, newProductUrl, lang);
onProductsChange([...products, newProduct]);
setNewProductName('');
setNewProductUrl('');
} catch (error) {
console.error("Failed to add product:", error);
setAddError(t.addProductError);
} finally {
setIsAdding(false);
}
};
const inputClasses = "w-full bg-light-primary dark:bg-brand-primary text-light-text dark:text-brand-text border border-light-accent dark:border-brand-accent rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-brand-highlight placeholder-light-subtle dark:placeholder-brand-light";
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="bg-light-secondary dark:bg-brand-secondary p-6 rounded-lg shadow-lg mb-6 border border-light-accent dark:border-brand-accent">
<h3 className="text-xl font-bold mb-4">{t.addProductTitle}</h3>
<form onSubmit={handleAddProduct} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="product_name" className="block text-sm font-medium text-light-subtle dark:text-brand-light mb-1">{t.productNameLabel}</label>
<input
id="product_name"
type="text"
value={newProductName}
onChange={(e) => setNewProductName(e.target.value)}
className={inputClasses}
placeholder={t.productNamePlaceholder}
required
/>
</div>
<div>
<label htmlFor="product_url" className="block text-sm font-medium text-light-subtle dark:text-brand-light mb-1">{t.productUrlLabel}</label>
<input
id="product_url"
type="url"
value={newProductUrl}
onChange={(e) => setNewProductUrl(e.target.value)}
className={inputClasses}
placeholder="https://..."
required
/>
</div>
</div>
{addError && <p className="text-red-500 text-sm">{addError}</p>}
<div className="text-right">
<button
type="submit"
disabled={isAdding}
className="bg-brand-accent hover:bg-brand-light text-white font-bold py-2 px-4 rounded-md disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isAdding ? t.addingButton : t.addButton}
</button>
</div>
</form>
</div>
<EditableCard<Product>
title={t.productsTitle}
items={products}
onItemsChange={onProductsChange}
showAddButton={false}
fieldConfigs={[
{ key: 'name', label: t.productNameLabel, type: 'text' },
{ key: 'purpose', label: t.purposeLabel, type: 'textarea' },
]}
newItemTemplate={{ name: '', purpose: '', evidence: [] }}
renderDisplay={(item) => (
<div>
<div className="flex items-center">
<strong className="text-light-text dark:text-white">{item.name}</strong>
<EvidencePopover evidence={item.evidence} />
</div>
<p className="text-light-subtle dark:text-brand-light text-sm mt-1">{item.purpose}</p>
</div>
)}
t={t.editableCard}
/>
<EditableCard<TargetIndustry>
title={t.industriesTitle}
items={industries}
onItemsChange={onIndustriesChange}
fieldConfigs={[{ key: 'name', label: t.industryNameLabel, type: 'text' }]}
newItemTemplate={{ name: '', evidence: [] }}
renderDisplay={(item) => (
<div className="flex items-center">
<strong className="text-light-text dark:text-white">{item.name}</strong>
<EvidencePopover evidence={item.evidence} />
</div>
)}
t={t.editableCard}
/>
</div>
);
};
export default Step1Extraction;