competitor-analysis/services/geminiService.ts hinzugefügt
This commit is contained in:
628
competitor-analysis/services/geminiService.ts
Normal file
628
competitor-analysis/services/geminiService.ts
Normal file
@@ -0,0 +1,628 @@
|
||||
import { GoogleGenAI, Type } from "@google/genai";
|
||||
import type { Product, TargetIndustry, Keyword, CompetitorCandidate, Analysis, AppState, Conclusion, SilverBullet, Battlecard, ShortlistedCompetitor, ReferenceAnalysis } from '../types';
|
||||
|
||||
if (!process.env.API_KEY) {
|
||||
throw new Error("API_KEY environment variable not set");
|
||||
}
|
||||
|
||||
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
|
||||
const model = 'gemini-2.5-pro';
|
||||
|
||||
// Schemas for consistent JSON output
|
||||
const evidenceSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
url: { type: Type.STRING },
|
||||
snippet: { type: Type.STRING },
|
||||
},
|
||||
required: ['url', 'snippet']
|
||||
};
|
||||
|
||||
const productSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
name: { type: Type.STRING, description: "Product name" },
|
||||
purpose: { type: Type.STRING, description: "Purpose description (1-2 sentences)" },
|
||||
evidence: { type: Type.ARRAY, items: evidenceSchema },
|
||||
},
|
||||
required: ['name', 'purpose', 'evidence']
|
||||
};
|
||||
|
||||
const industrySchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
name: { type: Type.STRING, description: "Name of the target industry" },
|
||||
evidence: { type: Type.ARRAY, items: evidenceSchema },
|
||||
},
|
||||
required: ['name', 'evidence']
|
||||
};
|
||||
|
||||
const parseJsonResponse = <T,>(text: string): T => {
|
||||
try {
|
||||
const cleanedText = text.replace(/^```json\s*|```\s*$/g, '');
|
||||
return JSON.parse(cleanedText);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON:", text);
|
||||
throw new Error("Invalid JSON response from API");
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchProductDetails = async (name: string, url: string, language: 'de' | 'en'): Promise<Product> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Analysiere die Webseite ${url} und beschreibe den Zweck des Produkts "${name}" in 1-2 Sätzen. Gib auch die genaue URL und ein relevantes Snippet als Beleg an.
|
||||
Das "name" Feld im JSON soll der offizielle Produktname sein, wie er auf der Seite gefunden wird, oder "${name}" falls nicht eindeutig.
|
||||
Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Analyze the website ${url} and describe the purpose of the product "${name}" in 1-2 sentences. Also provide the exact URL and a relevant snippet as evidence.
|
||||
The "name" field in the JSON should be the official product name as found on the page, or "${name}" if not clearly stated.
|
||||
Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema: productSchema },
|
||||
});
|
||||
return parseJsonResponse<Product>(response.text);
|
||||
};
|
||||
|
||||
|
||||
export const fetchStep1Data = async (startUrl: string, language: 'de' | 'en'): Promise<{ products: Product[], target_industries: TargetIndustry[] }> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Research-Agent für B2B-Software-Wettbewerbsanalyse.
|
||||
Aufgabe: Analysiere die Website ${startUrl} und identifiziere die Hauptprodukte/Lösungen und deren Zielbranchen.
|
||||
Regeln:
|
||||
1. Konzentriere dich auf offizielle Produkt- und Lösungsseiten.
|
||||
2. Jede Information (Produkt, Branche) muss mit einer URL und einem kurzen Snippet belegt werden.
|
||||
3. Sei präzise und vermeide Spekulationen.
|
||||
Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Role: Research Agent for B2B Software Competitor Analysis.
|
||||
Task: Analyze the website ${startUrl} and identify the main products/solutions and their target industries.
|
||||
Rules:
|
||||
1. Focus on official product and solution pages.
|
||||
2. Every piece of information (product, industry) must be backed by a URL and a short snippet as evidence.
|
||||
3. Be precise and avoid speculation.
|
||||
Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
products: { type: Type.ARRAY, items: productSchema },
|
||||
target_industries: { type: Type.ARRAY, items: industrySchema },
|
||||
},
|
||||
required: ['products', 'target_industries']
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
return parseJsonResponse<{ products: Product[], target_industries: TargetIndustry[] }>(response.text);
|
||||
};
|
||||
|
||||
|
||||
export const fetchStep2Data = async (products: Product[], industries: TargetIndustry[], language: 'de' | 'en'): Promise<{ keywords: Keyword[] }> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Research-Agent.
|
||||
Aufgabe: Leite aus den folgenden Produkt- und Brancheninformationen 10-25 präzise deutsche und englische Keywords/Suchphrasen für die Wettbewerbsrecherche ab.
|
||||
Kontext:
|
||||
Produkte: ${products.map(p => `${p.name} (${p.purpose})`).join(', ')}
|
||||
Branchen: ${industries.map(i => i.name).join(', ')}
|
||||
Regeln:
|
||||
1. Erstelle Cluster: Produktkategorie, Funktionskern, Zielbranchen, Synonyme/Englischvarianten.
|
||||
2. Gib für jedes Keyword eine kurze Begründung ("rationale").
|
||||
3. Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Role: Research Agent.
|
||||
Task: From the following product and industry information, derive 10-25 precise English keywords/search phrases for competitor research.
|
||||
Context:
|
||||
Products: ${products.map(p => `${p.name} (${p.purpose})`).join(', ')}
|
||||
Industries: ${industries.map(i => i.name).join(', ')}
|
||||
Rules:
|
||||
1. Create clusters: Product category, core function, target industries, synonyms.
|
||||
2. Provide a brief rationale for each keyword.
|
||||
3. Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
keywords: {
|
||||
type: Type.ARRAY,
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
term: { type: Type.STRING },
|
||||
rationale: { type: Type.STRING }
|
||||
},
|
||||
required: ['term', 'rationale']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['keywords']
|
||||
};
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
return parseJsonResponse<{ keywords: Keyword[] }>(response.text);
|
||||
};
|
||||
|
||||
export const fetchStep3Data = async (keywords: Keyword[], marketScope: string, language: 'de' | 'en'): Promise<{ competitor_candidates: CompetitorCandidate[] }> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Research-Agent.
|
||||
Aufgabe: Finde relevante Wettbewerber basierend auf den folgenden Keywords. Fokussiere dich auf den Markt: ${marketScope}.
|
||||
Keywords: ${keywords.map(k => k.term).join(', ')}
|
||||
Regeln:
|
||||
1. Suche nach Software-Anbietern, nicht nach Resellern, Beratungen oder Implementierungspartnern.
|
||||
2. Gib für jeden Kandidaten an: Name, URL, Eignungsbegründung (why), Confidence-Score (0.0-1.0) und Belege (URL+Snippet).
|
||||
3. Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Role: Research Agent.
|
||||
Task: Find relevant competitors based on the following keywords. Focus on the market: ${marketScope}.
|
||||
Keywords: ${keywords.map(k => k.term).join(', ')}
|
||||
Rules:
|
||||
1. Search for software vendors, not resellers, consultants, or implementation partners.
|
||||
2. For each candidate, provide: Name, URL, justification for inclusion (why), confidence score (0.0-1.0), and evidence (URL+snippet).
|
||||
3. Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor_candidates: {
|
||||
type: Type.ARRAY,
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
name: { type: Type.STRING },
|
||||
url: { type: Type.STRING },
|
||||
confidence: { type: Type.NUMBER },
|
||||
why: { type: Type.STRING },
|
||||
evidence: { type: Type.ARRAY, items: evidenceSchema }
|
||||
},
|
||||
required: ['name', 'url', 'confidence', 'why', 'evidence']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['competitor_candidates']
|
||||
};
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
return parseJsonResponse<{ competitor_candidates: CompetitorCandidate[] }>(response.text);
|
||||
};
|
||||
|
||||
export const fetchStep4Data = async (company: AppState['company'], competitors: CompetitorCandidate[], language: 'de' | 'en'): Promise<{ analyses: Analysis[] }> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Research-Agent.
|
||||
Aufgabe: Führe eine detaillierte Portfolio- & Positionierungsanalyse für jeden der folgenden Wettbewerber durch. Vergleiche sie mit dem Ausgangsunternehmen ${company.name} (${company.start_url}).
|
||||
Wettbewerber:
|
||||
${competitors.map(c => `- ${c.name}: ${c.url}`).join('\n')}
|
||||
Analyse-Punkte pro Wettbewerber:
|
||||
1. portfolio: Kernprodukte (max. 5) mit kurzem Zweck.
|
||||
2. target_industries: Hauptzielbranchen.
|
||||
3. delivery_model: Geschäfts-/Bereitstellungsmodell (z.B. SaaS, On-Premise), falls ersichtlich.
|
||||
4. overlap_score: 0-100, basierend auf Produktfunktion, Zielbranche, Terminologie im Vergleich zum Ausgangsunternehmen.
|
||||
5. differentiators: 3-5 Bulletpoints zu Alleinstellungsmerkmalen oder Unterschieden.
|
||||
6. evidence: Wichtige Beleg-URLs mit Snippets.
|
||||
Regeln:
|
||||
1. Jede Behauptung mit Quellen belegen.
|
||||
2. Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Role: Research Agent.
|
||||
Task: Conduct a detailed portfolio & positioning analysis for each of the following competitors. Compare them with the initial company ${company.name} (${company.start_url}).
|
||||
Competitors:
|
||||
${competitors.map(c => `- ${c.name}: ${c.url}`).join('\n')}
|
||||
Analysis points per competitor:
|
||||
1. portfolio: Core products (max 5) with a brief purpose.
|
||||
2. target_industries: Main target industries.
|
||||
3. delivery_model: Business/delivery model (e.g., SaaS, On-Premise), if apparent.
|
||||
4. overlap_score: 0-100, based on product function, target industry, terminology compared to the initial company.
|
||||
5. differentiators: 3-5 bullet points on unique selling points or differences.
|
||||
6. evidence: Key supporting URLs with snippets.
|
||||
Rules:
|
||||
1. Back up every claim with sources.
|
||||
2. Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
analyses: {
|
||||
type: Type.ARRAY,
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor: { type: Type.OBJECT, properties: { name: { type: Type.STRING }, url: { type: Type.STRING } } },
|
||||
portfolio: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { product: { type: Type.STRING }, purpose: { type: Type.STRING } } } },
|
||||
target_industries: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||
delivery_model: { type: Type.STRING },
|
||||
overlap_score: { type: Type.INTEGER },
|
||||
differentiators: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||
evidence: { type: Type.ARRAY, items: evidenceSchema }
|
||||
},
|
||||
required: ['competitor', 'portfolio', 'target_industries', 'delivery_model', 'overlap_score', 'differentiators', 'evidence']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['analyses']
|
||||
};
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
return parseJsonResponse<{ analyses: Analysis[] }>(response.text);
|
||||
};
|
||||
|
||||
export const fetchStep5Data_SilverBullets = async (company: AppState['company'], analyses: Analysis[], language: 'de' | 'en'): Promise<{ silver_bullets: SilverBullet[] }> => {
|
||||
const competitorDataSummary = analyses.map(a => {
|
||||
return `
|
||||
- Competitor: ${a.competitor.name}
|
||||
- Portfolio Focus: ${a.portfolio.map(p => p.product).join(', ') || 'N/A'}
|
||||
- Differentiators: ${a.differentiators.join('; ')}
|
||||
`.trim();
|
||||
}).join('\n');
|
||||
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Strategieberater.
|
||||
Aufgabe: Erstelle für das Unternehmen "${company.name}" eine "Silver Bullet" für jeden Wettbewerber. Eine "Silver Bullet" ist ein prägnanter Satz (max. 25 Wörter), der im Vertriebsgespräch genutzt werden kann, um sich vom jeweiligen Wettbewerber abzugrenzen.
|
||||
Kontext: ${company.name} wird mit den folgenden Wettbewerbern verglichen:
|
||||
${competitorDataSummary.replace(/Competitor:|Portfolio Focus:|Differentiators:/g, (match) => ({'Competitor:': 'Wettbewerber:', 'Portfolio Focus:': 'Portfolio-Fokus:', 'Differentiators:': 'Alleinstellungsmerkmale:'}[match] || match))}
|
||||
|
||||
Regeln:
|
||||
1. Formuliere für JEDEN Wettbewerber einen einzigen, schlagkräftigen Satz.
|
||||
2. Der Satz soll eine Schwäche des Wettbewerbers oder eine Stärke von ${company.name} im direkten Vergleich hervorheben.
|
||||
3. Sei prägnant und überzeugend.
|
||||
4. Antworte ausschließlich im JSON-Format.
|
||||
`,
|
||||
en: `
|
||||
Role: Strategy Consultant.
|
||||
Task: Create a "Silver Bullet" for the company "${company.name}" for each competitor. A "Silver Bullet" is a concise sentence (max 25 words) that can be used in a sales pitch to differentiate from the respective competitor.
|
||||
Context: ${company.name} is being compared with the following competitors:
|
||||
${competitorDataSummary}
|
||||
|
||||
Rules:
|
||||
1. Formulate a single, powerful sentence for EACH competitor.
|
||||
2. The sentence should highlight a weakness of the competitor or a strength of ${company.name} in direct comparison.
|
||||
3. Be concise and persuasive.
|
||||
4. Respond exclusively in JSON format.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
silver_bullets: {
|
||||
type: Type.ARRAY,
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor_name: { type: Type.STRING },
|
||||
statement: { type: Type.STRING }
|
||||
},
|
||||
required: ['competitor_name', 'statement']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['silver_bullets']
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
|
||||
return parseJsonResponse<{ silver_bullets: SilverBullet[] }>(response.text);
|
||||
};
|
||||
|
||||
|
||||
export const fetchStep6Data_Conclusion = async (company: AppState['company'], products: Product[], industries: TargetIndustry[], analyses: Analysis[], silver_bullets: SilverBullet[], language: 'de' | 'en'): Promise<{ conclusion: Conclusion }> => {
|
||||
const competitorDataSummary = analyses.map(a => {
|
||||
return `
|
||||
- Competitor: ${a.competitor.name}
|
||||
- Portfolio: ${a.portfolio.map(p => p.product).join(', ') || 'N/A'}
|
||||
- Target Industries: ${a.target_industries.join(', ') || 'N/A'}
|
||||
- Overlap Score: ${a.overlap_score}
|
||||
- Differentiators: ${a.differentiators.join('; ')}
|
||||
`.trim();
|
||||
}).join('\n\n');
|
||||
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Research-Agent.
|
||||
Aufgabe: Erstelle ein Fazit der Wettbewerbsanalyse für ${company.name}.
|
||||
|
||||
Ausgangsunternehmen (${company.name}) Daten:
|
||||
- Produkte: ${products.map(p => p.name).join(', ')}
|
||||
- Branchen: ${industries.map(i => i.name).join(', ')}
|
||||
|
||||
Zusammengefasste Wettbewerber-Daten:
|
||||
${competitorDataSummary.replace(/Competitor:|Portfolio:|Target Industries:|Overlap Score:|Differentiators:/g, (match) => ({'Competitor:': 'Wettbewerber:', 'Portfolio:': 'Portfolio:', 'Target Industries:': 'Zielbranchen:', 'Overlap Score:': 'Overlap Score:', 'Differentiators:': 'Alleinstellungsmerkmale:'}[match] || match))}
|
||||
|
||||
Strategische Positionierung ("Silver Bullets"):
|
||||
${silver_bullets.map(sb => `- Gegen ${sb.competitor_name}: "${sb.statement}"`).join('\n')}
|
||||
|
||||
Erstelle:
|
||||
1. product_matrix: Analysiere ALLE Produkte von ${company.name} und den Wettbewerbern. Identifiziere 5-10 generische Produktkategorien oder Kernfunktionalitäten (z.B. "Mobile Lösung", "Disposition & Planung", "Asset Management"). Erstelle dann eine Matrix basierend auf diesen generischen Kategorien. Jedes Element im Array repräsentiert eine dieser Kategorien. Jedes Element hat ein "product" (string, der Name der generischen Kategorie) und ein "availability" (Array). Das "availability"-Array enthält Objekte mit "competitor" (string) und "has_offering" (boolean), für JEDEN Anbieter (inkl. ${company.name}), das anzeigt, ob der Anbieter eine Lösung in dieser Kategorie hat.
|
||||
2. industry_matrix: Ein Array. Jedes Element repräsentiert eine Branche (von ALLEN Anbietern, inkl. ${company.name}). Jedes Element hat eine "industry" (string) und ein "availability" (Array). Das "availability"-Array enthält Objekte mit "competitor" (string) und "has_offering" (boolean), für JEDEN Anbieter (inkl. ${company.name}).
|
||||
3. overlap_scores: Eine Liste der Wettbewerber und ihrer Overlap-Scores.
|
||||
4. summary: Eine knappe Einordnung (2-3 Sätze), wer sich worauf positioniert.
|
||||
5. opportunities: Wo liegen Lücken oder Chancen für ${company.name}?
|
||||
6. next_questions: Max. 5 offene Fragen oder nächste Schritte für den User.
|
||||
Regeln:
|
||||
1. Antworte ausschließlich im JSON-Format gemäß dem vorgegebenen Schema.
|
||||
`,
|
||||
en: `
|
||||
Role: Research Agent.
|
||||
Task: Create a conclusion for the competitive analysis for ${company.name}.
|
||||
|
||||
Initial Company (${company.name}) Data:
|
||||
- Products: ${products.map(p => p.name).join(', ')}
|
||||
- Industries: ${industries.map(i => i.name).join(', ')}
|
||||
|
||||
Summarized Competitor Data:
|
||||
${competitorDataSummary}
|
||||
|
||||
Strategic Positioning ("Silver Bullets"):
|
||||
${silver_bullets.map(sb => `- Against ${sb.competitor_name}: "${sb.statement}"`).join('\n')}
|
||||
|
||||
Create:
|
||||
1. product_matrix: Analyze ALL products from ${company.name} and the competitors. Identify 5-10 generic product categories or core functionalities (e.g., "Mobile Solution", "Dispatch & Planning", "Asset Management"). Then create a matrix based on these generic categories. Each element in the array represents one of these categories. Each element has a "product" (string, the name of the generic category) and an "availability" (array). The "availability" array contains objects with "competitor" (string) and "has_offering" (boolean), for EVERY provider (incl. ${company.name}), indicating if the provider has a solution in this category.
|
||||
2. industry_matrix: An array. Each element represents an industry (from ALL providers, incl. ${company.name}). Each element has an "industry" (string) and an "availability" (array). The "availability" array contains objects with "competitor" (string) and "has_offering" (boolean), for EVERY provider (incl. ${company.name}).
|
||||
3. overlap_scores: A list of competitors and their overlap scores.
|
||||
4. summary: A brief assessment (2-3 sentences) of who is positioned where.
|
||||
5. opportunities: Where are the gaps or opportunities for ${company.name}?
|
||||
6. next_questions: Max 5 open questions or next steps for the user.
|
||||
Rules:
|
||||
1. Respond exclusively in JSON format according to the provided schema.
|
||||
`
|
||||
};
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
conclusion: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
product_matrix: {
|
||||
type: Type.ARRAY,
|
||||
description: "Array representing a feature-based product comparison. Each item is a generic product category or core functionality.",
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
product: { type: Type.STRING, description: "Name of the generic product category/feature." },
|
||||
availability: {
|
||||
type: Type.ARRAY,
|
||||
description: "Which competitors offer this product.",
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor: { type: Type.STRING },
|
||||
has_offering: { type: Type.BOOLEAN, description: "True if the competitor has a similar offering." }
|
||||
},
|
||||
required: ['competitor', 'has_offering']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['product', 'availability']
|
||||
}
|
||||
},
|
||||
industry_matrix: {
|
||||
type: Type.ARRAY,
|
||||
description: "Array representing industry comparison. Each item is an industry.",
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
industry: { type: Type.STRING, description: "Name of the industry." },
|
||||
availability: {
|
||||
type: Type.ARRAY,
|
||||
description: "Which competitors serve this industry.",
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor: { type: Type.STRING },
|
||||
has_offering: { type: Type.BOOLEAN, description: "True if the competitor serves this industry." }
|
||||
},
|
||||
required: ['competitor', 'has_offering']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['industry', 'availability']
|
||||
}
|
||||
},
|
||||
overlap_scores: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { competitor: { type: Type.STRING }, score: { type: Type.NUMBER } } } },
|
||||
summary: { type: Type.STRING },
|
||||
opportunities: { type: Type.STRING },
|
||||
next_questions: { type: Type.ARRAY, items: { type: Type.STRING } }
|
||||
},
|
||||
required: ['product_matrix', 'industry_matrix', 'overlap_scores', 'summary', 'opportunities', 'next_questions']
|
||||
}
|
||||
},
|
||||
required: ['conclusion']
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
return parseJsonResponse<{ conclusion: Conclusion }>(response.text);
|
||||
};
|
||||
|
||||
export const fetchStep7Data_Battlecards = async (company: AppState['company'], analyses: Analysis[], silver_bullets: SilverBullet[], language: 'de' | 'en'): Promise<{ battlecards: Battlecard[] }> => {
|
||||
const competitorDataSummary = analyses.map(a => {
|
||||
const silverBullet = silver_bullets.find(sb => sb.competitor_name === a.competitor.name)?.statement || "Not found.";
|
||||
return `
|
||||
- Competitor: ${a.competitor.name}
|
||||
- Portfolio Focus: ${a.portfolio.map(p => p.product).join(', ') || 'N/A'}
|
||||
- Target Industries: ${a.target_industries.join(', ')}
|
||||
- Differentiators: ${a.differentiators.join('; ')}
|
||||
- Silver Bullet against this competitor: "${silverBullet}"
|
||||
`.trim();
|
||||
}).join('\n\n');
|
||||
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Vertriebsstratege und Coach für das B2B-Softwareunternehmen "${company.name}".
|
||||
Aufgabe: Erstelle für jeden der folgenden Wettbewerber eine detaillierte "Sales Battlecard". Diese Battlecard soll dem Vertriebsteam konkrete, handlungsorientierte Argumente für Kundengespräche an die Hand geben.
|
||||
|
||||
Informationen über das eigene Unternehmen: ${company.name}
|
||||
Detaillierte Analyse der Wettbewerber:
|
||||
${competitorDataSummary.replace(/Competitor:|Portfolio Focus:|Target Industries:|Differentiators:|Silver Bullet against this competitor:/g, (match) => ({'Competitor:': 'Wettbewerber:', 'Portfolio Focus:': 'Portfolio-Fokus:', 'Target Industries:': 'Zielbranchen:', 'Differentiators:': 'Alleinstellungsmerkmale:', 'Silver Bullet against this competitor:': 'Silver Bullet gegen diesen Wettbewerber:'}[match] || match))}
|
||||
|
||||
Für JEDEN Wettbewerber, erstelle die folgenden Sektionen einer Battlecard im JSON-Format:
|
||||
1. **competitor_name**: Der Name des Wettbewerbers.
|
||||
2. **competitor_profile**:
|
||||
* **focus**: Fasse den Kernfokus des Wettbewerbers (Produkte & Branchen) in einem Satz zusammen.
|
||||
* **positioning**: Beschreibe die Kernpositionierung des Wettbewerbers in 1-2 Sätzen.
|
||||
3. **strengths_vs_weaknesses**: Formuliere 3-4 prägnante Stichpunkte. Jeder Stichpunkt soll eine Stärke von "${company.name}" einer vermuteten Schwäche des Wettbewerbers gegenüberstellen. Beginne die Sätze z.B. mit "Während [Wettbewerber]..., bieten wir...".
|
||||
4. **landmine_questions**: Formuliere 3-5 intelligente, offene "Landminen"-Fragen, die ein Vertriebsmitarbeiter einem potenziellen Kunden stellen kann. Diese Fragen sollen die Schwächen des Wettbewerbers aufdecken oder die Stärken von "${company.name}" betonen, ohne den Wettbewerber direkt anzugreifen.
|
||||
5. **silver_bullet**: Übernimm die bereits formulierte "Silver Bullet" für diesen Wettbewerber.
|
||||
|
||||
Regeln:
|
||||
- Sei präzise, überzeugend und nutze eine aktive, vertriebsorientierte Sprache.
|
||||
- Die "landmine_questions" müssen so formuliert sein, dass sie den Kunden zum Nachdenken anregen und ihn in Richtung der Vorteile von "${company.name}" lenken.
|
||||
- Antworte ausschließlich im JSON-Format gemäß dem vorgegebenen Schema für ein Array von Battlecards.
|
||||
`,
|
||||
en: `
|
||||
Role: Sales Strategist and Coach for the B2B software company "${company.name}".
|
||||
Task: Create a detailed "Sales Battlecard" for each of the following competitors. This battlecard should provide the sales team with concrete, actionable arguments for customer conversations.
|
||||
|
||||
Information about our own company: ${company.name}
|
||||
Detailed analysis of competitors:
|
||||
${competitorDataSummary}
|
||||
|
||||
For EACH competitor, create the following sections of a battlecard in JSON format:
|
||||
1. **competitor_name**: The name of the competitor.
|
||||
2. **competitor_profile**:
|
||||
* **focus**: Summarize the competitor's core focus (products & industries) in one sentence.
|
||||
* **positioning**: Describe the competitor's core positioning in 1-2 sentences.
|
||||
3. **strengths_vs_weaknesses**: Formulate 3-4 concise bullet points. Each point should contrast a strength of "${company.name}" with a presumed weakness of the competitor. Start sentences with, for example, "While [Competitor]..., we offer...".
|
||||
4. **landmine_questions**: Formulate 3-5 intelligent, open-ended "landmine" questions that a sales representative can ask a potential customer. These questions should uncover the competitor's weaknesses or emphasize the strengths of "${company.name}" without attacking the competitor directly.
|
||||
5. **silver_bullet**: Use the "Silver Bullet" already formulated for this competitor.
|
||||
|
||||
Rules:
|
||||
- Be precise, persuasive, and use active, sales-oriented language.
|
||||
- The "landmine_questions" must be formulated to make the customer think and guide them towards the advantages of "${company.name}".
|
||||
- Respond exclusively in JSON format according to the specified schema for an array of battlecards.
|
||||
`
|
||||
};
|
||||
|
||||
|
||||
const responseSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
battlecards: {
|
||||
type: Type.ARRAY,
|
||||
items: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
competitor_name: { type: Type.STRING },
|
||||
competitor_profile: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
focus: { type: Type.STRING },
|
||||
positioning: { type: Type.STRING }
|
||||
},
|
||||
required: ['focus', 'positioning']
|
||||
},
|
||||
strengths_vs_weaknesses: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||
landmine_questions: { type: Type.ARRAY, items: { type: Type.STRING } },
|
||||
silver_bullet: { type: Type.STRING }
|
||||
},
|
||||
required: ['competitor_name', 'competitor_profile', 'strengths_vs_weaknesses', 'landmine_questions', 'silver_bullet']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['battlecards']
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: { responseMimeType: "application/json", responseSchema },
|
||||
});
|
||||
|
||||
return parseJsonResponse<{ battlecards: Battlecard[] }>(response.text);
|
||||
};
|
||||
|
||||
export const fetchStep8Data_ReferenceAnalysis = async (competitors: ShortlistedCompetitor[], language: 'de' | 'en'): Promise<{ reference_analysis: ReferenceAnalysis[], groundingMetadata: any[] }> => {
|
||||
const prompts = {
|
||||
de: `
|
||||
Rolle: Faktentreuer Research-Agent. Deine Antworten MÜSSEN ausschließlich auf den Ergebnissen der von dir durchgeführten Websuche basieren.
|
||||
Aufgabe: Führe für jeden der folgenden Wettbewerber eine Websuche durch, um deren offizielle Referenzkunden, Case Studies oder Success Stories zu finden.
|
||||
Wettbewerber:
|
||||
${competitors.map(c => `- ${c.name}: ${c.url}`).join('\n')}
|
||||
|
||||
ABLAUF FÜR JEDEN WETTBEWERBER:
|
||||
1. **SUCHE**: Führe eine gezielte Suche durch mit Phrasen wie "[Wettbewerber-Name] Referenzen", "[Wettbewerber-Name] Case Studies", "[Wettbewerber-Name] Kunden".
|
||||
2. **VALIDIERUNG**: Analysiere die Suchergebnisse. Konzentriere dich AUSSCHLIESSLICH auf Links, die zur offiziellen Domain des Wettbewerbers gehören (z.B. ${"`wettbewerber.com/referenzen`"}). Ignoriere Pressemitteilungen auf Drittseiten, Partnerlisten oder Nachrichtenartikel.
|
||||
3. **EXTRAKTION**: Extrahiere die geforderten Informationen NUR von diesen validierten, offiziellen Seiten.
|
||||
|
||||
SEHR WICHTIGE REGELN ZUR VERMEIDUNG VON FALSCHINFORMATIONEN:
|
||||
- **NUR GEFUNDENE DATEN**: Gib NUR Kunden an, für die du eine dedizierte Case-Study-Seite oder einen klaren Testimonial-Abschnitt auf der OFFIZIELLEN Website des Wettbewerbers gefunden hast.
|
||||
- **KEINE HALLUZINATION**: Erfinde KEINE Kunden, Branchen oder Zitate. Wenn du für einen Wettbewerber absolut nichts findest, gib ein leeres "references" Array zurück. Dies ist besser als falsche Informationen.
|
||||
- **DIREKTER LINK**: Die 'case_study_url' MUSS der exakte, funktionierende Link zur Seite sein, auf der die Informationen gefunden wurden.
|
||||
|
||||
Antworte AUSSCHLIESSLICH im JSON-Format, eingeschlossen in einem Markdown-Codeblock.
|
||||
Extrahiere für JEDEN GEFUNDENEN UND VERIFIZIERTEN Referenzkunden (max. 5 pro Wettbewerber) die geforderten Felder.
|
||||
`,
|
||||
en: `
|
||||
Role: Fact-based Research Agent. Your answers MUST be based solely on the results of the web search you perform.
|
||||
Task: For each of the following competitors, conduct a web search to find their official reference customers, case studies, or success stories.
|
||||
Competitors:
|
||||
${competitors.map(c => `- ${c.name}: ${c.url}`).join('\n')}
|
||||
|
||||
PROCESS FOR EACH COMPETITOR:
|
||||
1. **SEARCH**: Conduct a targeted search with phrases like "[Competitor Name] references", "[Competitor Name] case studies", "[Competitor Name] customers".
|
||||
2. **VALIDATION**: Analyze the search results. Focus EXCLUSIVELY on links that belong to the competitor's official domain (e.g., ${"`competitor.com/references`"}). Ignore press releases on third-party sites, partner lists, or news articles.
|
||||
3. **EXTRACTION**: Extract the required information ONLY from these validated, official pages.
|
||||
|
||||
VERY IMPORTANT RULES TO AVOID MISINFORMATION:
|
||||
- **ONLY FOUND DATA**: ONLY list customers for whom you have found a dedicated case study page or a clear testimonial section on the OFFICIAL website of the competitor.
|
||||
- **NO HALLUCINATION**: DO NOT invent customers, industries, or quotes. If you find absolutely nothing for a competitor, return an empty "references" array. This is better than false information.
|
||||
- **DIRECT LINK**: The 'case_study_url' MUST be the exact, working link to the page where the information was found.
|
||||
|
||||
Respond EXCLUSIVELY in JSON format, enclosed in a markdown code block.
|
||||
For EACH FOUND AND VERIFIED reference customer (max 5 per competitor), extract the required fields.
|
||||
`
|
||||
};
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompts[language],
|
||||
config: {
|
||||
tools: [{googleSearch: {}}],
|
||||
},
|
||||
});
|
||||
|
||||
const groundingMetadata = response.candidates?.[0]?.groundingMetadata?.groundingChunks || [];
|
||||
const parsedData = parseJsonResponse<{ reference_analysis: ReferenceAnalysis[] }>(response.text);
|
||||
|
||||
return { ...parsedData, groundingMetadata };
|
||||
};
|
||||
Reference in New Issue
Block a user