Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/aminss99_sideby.json
T

1 line
85 KiB
JSON

{"repo": "aminss99/sideby", "files": {"backend/src/main/resources/application.properties": {"1fd6a0219f17452feceac8cd603909381f1abe2e": {"t": 1785745390.8156295, "n": 630, "h": "05e6cdd81ba47133", "c": "spring.application.name=comparison-app\nserver.port=${SERVER_PORT:8080}\n\n# Gemini AI Configuration (Primary)\ngemini.api.key=${GEMINI_API_KEY:}\ngemini.api.url=${GEMINI_API_URL:https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent}\n\n# DeepSeek AI Configuration (Fallback)\ndeepseek.api.key=${DEEPSEEK_API_KEY:}\n\n# Firecrawl extraction (optional for local dev; Jsoup fallback is used when empty)\nfirecrawl.api.key=${FIRECRAWL_API_KEY:}\nfirecrawl.api.url=${FIRECRAWL_API_URL:https://api.firecrawl.dev/v2/scrape}\n\n# CORS Configuration\ncors.allowed-origins=${CORS_ALLOWED_ORIGINS:https://snapsolve.ink}\n"}, "3381b4eeac533ff295478c422eea18320e3dd03b": {"t": 1785750990.4920824, "n": 630, "h": "05e6cdd81ba47133", "c": "spring.application.name=comparison-app\nserver.port=${SERVER_PORT:8080}\n\n# Gemini AI Configuration (Primary)\ngemini.api.key=${GEMINI_API_KEY:}\ngemini.api.url=${GEMINI_API_URL:https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent}\n\n# DeepSeek AI Configuration (Fallback)\ndeepseek.api.key=${DEEPSEEK_API_KEY:}\n\n# Firecrawl extraction (optional for local dev; Jsoup fallback is used when empty)\nfirecrawl.api.key=${FIRECRAWL_API_KEY:}\nfirecrawl.api.url=${FIRECRAWL_API_URL:https://api.firecrawl.dev/v2/scrape}\n\n# CORS Configuration\ncors.allowed-origins=${CORS_ALLOWED_ORIGINS:https://snapsolve.ink}\n"}, "4fc9e4e57b88fef94594b2eb46c179e4a49dfa1e": {"t": 1785750991.5767448, "n": 541, "h": "07318ef092539a5d", "c": "spring.application.name=comparison-app\nserver.port=${SERVER_PORT:8080}\n\n# Gemini AI Configuration (Primary)\ngemini.api.key=${GEMINI_API_KEY:}\ngemini.api.url=${GEMINI_API_URL:https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent}\n\n# DeepSeek AI Configuration (Fallback)\ndeepseek.api.key=${DEEPSEEK_API_KEY:}\n\n# Firecrawl extraction (optional for local dev; Jsoup fallback is used when empty)\nfirecrawl.api.key=${FIRECRAWL_API_KEY:}\nfirecrawl.api.url=${FIRECRAWL_API_URL:https://api.firecrawl.dev/v2/scrape}\n"}, "3071c044265266ee75d9bf7321b3bf6274069399": {"t": 1785750992.6724465, "n": 342, "h": "1af42f2f40a61ed0", "c": "spring.application.name=comparison-app\nserver.port=${SERVER_PORT:8080}\n\n# Gemini AI Configuration (Primary)\ngemini.api.key=${GEMINI_API_KEY:}\ngemini.api.url=${GEMINI_API_URL:https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent}\n\n# DeepSeek AI Configuration (Fallback)\ndeepseek.api.key=${DEEPSEEK_API_KEY:}\n"}, "bbaa9b0c38a2e91386d9f41a876a27642690e2d7": {"t": 1785750993.756608, "n": 345, "h": "885c314684722682", "c": "spring.application.name=comparison-app\nserver.port=8080\n\n# Gemini AI Configuration (Primary)\ngemini.api.key=AIzaSyBAwV3bJEaP2Phz3vKMQ9dCsoUi6qlunNU\ngemini.api.url=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent\n\n# DeepSeek AI Configuration (Fallback)\ndeepseek.api.key=sk-9a50a43252dd4c5ebed3dcc992af842e\n"}, "30fadae27e5c0df2954180814ae115e60947d473": {"t": 1785750994.8524797, "n": 242, "h": "39da4bbb03cbbe17", "c": "spring.application.name=comparison-app\nserver.port=8080\n\n# Gemini AI Configuration\ngemini.api.key=AIzaSyBAwV3bJEaP2Phz3vKMQ9dCsoUi6qlunNU\ngemini.api.url=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent\n"}}, "backend/src/main/java/com/comparison/app/service/AISummaryService.java": {"1fd6a0219f17452feceac8cd603909381f1abe2e": {"t": 1785746125.2785773, "n": 26862, "h": "c8874e99144f82a6", "c": "package com.comparison.app.service;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.MediaType;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.RestClient;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n@Service\npublic class AISummaryService {\n\n @Value(\"${gemini.api.key}\")\n private String geminiApiKey;\n\n @Value(\"${gemini.api.url}\")\n private String geminiApiUrl;\n\n @Value(\"${deepseek.api.key}\")\n private String deepseekApiKey;\n\n private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/chat/completions\";\n private static final String DEEPSEEK_MODEL = \"deepseek-chat\";\n\n private final RestClient restClient;\n\n private static final Set<String> SPORTS_KEYWORDS = Set.of(\n \"bayern\", \"munich\", \"psg\", \"barcelona\", \"real madrid\", \"manchester\", \"liverpool\",\n \"chelsea\", \"arsenal\", \"juventus\", \"inter\", \"milan\", \"dortmund\", \"atletico\",\n \"nba\", \"lakers\", \"celtics\", \"warriors\", \"bulls\", \"heat\", \"knicks\",\n \"nfl\", \"patriots\", \"cowboys\", \"packers\", \"chiefs\", \"eagles\",\n \"team\", \"club\", \"fc\", \"united\", \"city\", \"athletic\", \"sporting\", \"football\");\n\n private static final Set<String> TRAVEL_KEYWORDS = Set.of(\n \"paris\", \"london\", \"tokyo\", \"new york\", \"rome\", \"amsterdam\", \"berlin\",\n \"dubai\", \"singapore\", \"bangkok\", \"sydney\", \"los angeles\", \"istanbul\",\n \"prague\", \"vienna\", \"budapest\", \"lisbon\", \"moscow\", \"beijing\", \"vacation\",\n \"baku\", \"azerbaijan\", \"venezuela\", \"caracas\", \"brazil\", \"rio\", \"mexico\",\n \"cairo\", \"egypt\", \"morocco\", \"marrakech\", \"athens\", \"greece\", \"spain\",\n \"barcelona\", \"madrid\", \"milan\", \"florence\", \"venice\", \"naples\", \"munich\",\n \"zurich\", \"geneva\", \"oslo\", \"stockholm\", \"copenhagen\", \"helsinki\",\n \"seoul\", \"taipei\", \"hong kong\", \"manila\", \"jakarta\", \"mumbai\", \"delhi\",\n \"city\", \"country\", \"travel\", \"trip\", \"destination\", \"tourism\", \"tourist\");\n\n private static final Set<String> GAMING_KEYWORDS = Set.of(\n \"ps5\", \"playstation\", \"xbox\", \"nintendo\", \"switch\", \"fortnite\", \"minecraft\",\n \"gta\", \"call of duty\", \"cod\", \"fifa\", \"nba2k\", \"zelda\", \"mario\", \"halo\",\n \"god of war\", \"spider-man\", \"elden ring\", \"game\", \"gaming\", \"console\");\n\n private static final Set<String> AUTO_KEYWORDS = Set.of(\n \"tesla\", \"bmw\", \"mercedes\", \"audi\", \"porsche\", \"toyota\", \"honda\", \"ford\",\n \"chevrolet\", \"volkswagen\", \"hyundai\", \"kia\", \"nissan\", \"mazda\",\n \"car\", \"vehicle\", \"suv\", \"sedan\", \"truck\", \"electric\", \"hybrid\");\n\n private static final Set<String> FOOD_KEYWORDS = Set.of(\n \"pizza\", \"burger\", \"sushi\", \"pasta\", \"tacos\", \"ramen\", \"steak\", \"salad\",\n \"sandwich\", \"wings\", \"fries\", \"noodles\", \"curry\", \"kebab\", \"shawarma\",\n \"mcdonald\", \"burger king\", \"kfc\", \"pizza hut\", \"domino\", \"subway\",\n \"starbucks\", \"chipotle\", \"taco bell\", \"wendy\", \"restaurant\", \"food\", \"cuisine\");\n\n public AISummaryService() {\n this.restClient = RestClient.create();\n }\n\n public AIResponse compareTwoItems(String itemA, String itemB, CompareRequestOptions options) {\n CompareRequestOptions safeOptions = options == null ? CompareRequestOptions.defaults() : options;\n String category = StringUtils.hasText(safeOptions.categoryOverride())\n ? safeOptions.categoryOverride()\n : detectCategory(itemA, itemB);\n String prompt = buildCategorySpecificPrompt(itemA, itemB, category, safeOptions);\n\n try {\n if (isGeminiConfigured()) {\n System.out.println(\"Trying Gemini API... Category: \" + category);\n return callGeminiAPI(prompt, category);\n }\n } catch (Exception geminiError) {\n System.err.println(\"Gemini API error: \" + geminiError.getMessage());\n }\n\n try {\n if (isDeepSeekConfigured()) {\n System.out.println(\"Falling back to DeepSeek API...\");\n return callDeepSeekAPI(prompt, category);\n }\n } catch (Exception deepSeekError) {\n System.err.println(\"DeepSeek API error: \" + deepSeekError.getMessage());\n }\n\n return new AIResponse(\n generateFallbackComparison(itemA, itemB, category),\n \"fallback\",\n \"template-fallback\",\n category,\n true,\n null,\n null,\n null);\n }\n\n private String detectCategory(String itemA, String itemB) {\n String combined = (itemA + \" \" + itemB).toLowerCase();\n\n if (FOOD_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"food\";\n }\n if (SPORTS_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"sports\";\n }\n if (TRAVEL_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"travel\";\n }\n if (GAMING_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"gaming\";\n }\n if (AUTO_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"auto\";\n }\n\n return \"tech\";\n }\n\n private String buildCategorySpecificPrompt(\n String itemA,\n String itemB,\n String category,\n CompareRequestOptions options) {\n String metricsSection = getCategoryMetrics(category);\n String tipsSection = getCategoryTips(category);\n String compareMode = normalizeCompareMode(options.compareMode());\n String depth = normalizeDepth(options.depth());\n String decisionContext = StringUtils.hasText(options.decisionContext())\n ? options.decisionContext().trim()\n : \"No extra context provided.\";\n String priorities = options.priorities().isEmpty()\n ? \"None selected. Use the category metrics above.\"\n : String.join(\", \", options.priorities());\n String mustHaves = options.mustHaves().isEmpty()\n ? \"None specified.\"\n : String.join(\", \", options.mustHaves());\n String redFlags = options.redFlags().isEmpty()\n ? \"None specified.\"\n : String.join(\", \", options.redFlags());\n String depthInstruction = switch (depth) {\n case \"quick\" -> \"Keep the answer tight and decisive in under 220 words.\";\n case \"extreme\" ->\n \"Go deeper than usual: expose tradeoffs, failure modes, edge cases, and practical decision boundaries while staying under 550 words.\";\n default -> \"Keep the answer practical and structured in under 400 words.\";\n };\n\n return \"\"\"\n You are an expert %s comparison analyst. Compare \"%s\" vs \"%s\".\n\n ## Category: %s\n\n IMPORTANT: Use ONLY these domain-specific metrics for comparison:\n %s\n\n ## Engine Settings\n - Compare mode: %s\n - Analysis depth: %s\n - Decision context: %s\n - Priority metrics: %s\n - Must-have requirements: %s\n - Red flags to avoid: %s\n\n Treat the engine settings above as hard guidance. Weigh the chosen priorities more heavily, explicitly call out whether must-have requirements are satisfied, and highlight any red flags that appear.\n\n ## Quick Verdict\n [One clear sentence declaring the winner and why. Name the winner explicitly.]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## Head-to-Head Scores (rate 1-100)\n | Metric | %s | %s | Winner |\n |--------|------|------|--------|\n %s\n\n ## Contextual Tip\n %s\n\n ## Final Recommendation\n Choose %s if [specific condition]. Choose %s if [specific condition].\n\n %s\n Be specific, practical, and domain-appropriate. Always declare a clear winner.\n \"\"\"\n .formatted(\n category,\n itemA,\n itemB,\n category.toUpperCase(),\n metricsSection,\n compareMode,\n depth,\n decisionContext,\n priorities,\n mustHaves,\n redFlags,\n itemA,\n category,\n category,\n category,\n category,\n itemB,\n category,\n category,\n category,\n category,\n itemA,\n itemB,\n getMetricsTableRows(category),\n tipsSection,\n itemA,\n itemB,\n depthInstruction);\n }\n\n private String getCategoryMetrics(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n - Trophies Won (total major titles and recent success)\n - Squad Value (market value of players)\n - Fan Base (global supporters and stadium atmosphere)\n - History & Legacy (tradition, founding, legends)\n - Youth Academy (player development quality)\n - Manager Quality (tactical ability, experience)\n - Recent Form (last season performance)\n - European Success (Champions League/Europa performance)\n - Domestic Dominance (league titles, consistency)\n - Star Players (key talent and marquee names)\n - Financial Power (revenue, spending ability)\n - Stadium (capacity, atmosphere, facilities)\n \"\"\";\n case \"travel\" -> \"\"\"\n - Cost of Living (daily expenses, accommodation, food)\n - Safety (crime rate, tourist safety)\n - Weather (climate, best seasons to visit)\n - Culture (museums, history, art scene)\n - Food Scene (cuisine variety, restaurants)\n - Nightlife (bars, clubs, entertainment)\n - Public Transport (metro, buses, walkability)\n - Tourist Attractions (landmarks, must-see spots)\n - Language (English friendliness)\n - Accommodation (hotel quality, Airbnb options)\n - Shopping (markets, stores, souvenirs)\n - Local Experience (authenticity, local immersion)\n \"\"\";\n case \"gaming\" -> \"\"\"\n - Graphics (visual quality, art style)\n - Gameplay (fun factor, mechanics)\n - Story (narrative, characters)\n - Multiplayer (online experience, modes)\n - Exclusives (unique titles, franchises)\n - Price (value for money)\n - Player Base (active community)\n - Updates (content support, DLC)\n - Controls (responsiveness, layout)\n - Replay Value (longevity)\n - Esports Scene (competitive potential)\n - Hardware (specs, performance)\n \"\"\";\n case \"auto\" -> \"\"\"\n - Performance (horsepower, acceleration, top speed)\n - Fuel Economy (MPG or electric range)\n - Safety (crash ratings, ADAS features)\n - Comfort (ride quality, noise, seats)\n - Price (MSRP, value proposition)\n - Reliability (dependability, maintenance)\n - Resale Value (depreciation rate)\n - Features (tech, infotainment, connectivity)\n - Interior Quality (materials, design)\n - Cargo Space (trunk, practicality)\n - Handling (driving dynamics, steering)\n - Brand Prestige (reputation, status)\n \"\"\";\n case \"food\" -> \"\"\"\n - Taste (flavor, freshness, quality)\n - Price (cost per serving, value)\n - Portion Size (amount of food)\n - Ingredients (quality, freshness)\n - Preparation Time (how long to make/serve)\n - Nutritional Value (calories, health factor)\n - Availability (ease of finding)\n - Variety (menu options, customization)\n - Popularity (how widely loved)\n - Convenience (ease of eating)\n - Ambiance (dining experience)\n - Satisfaction (overall fulfillment)\n \"\"\";\n default -> \"\"\"\n - Performance (speed, efficiency)\n - Ease of Use (learning curve)\n - Community (support, ecosystem)\n - Documentation (guides, tutorials)\n - Features (functionality, capabilities)\n - Price/Value (cost effectiveness)\n - Scalability (growth potential)\n - Reliability (stability, uptime)\n - Integration (compatibility)\n - Future Outlook (roadmap, updates)\n \"\"\";\n };\n }\n\n private String getCategoryTips(String category) {\n return switch (category) {\n case \"sports\" -> \"Consider recent Champions League and domestic league performance when choosing\";\n case \"travel\" -> \"Book accommodations 3 months in advance and visit attractions early morning\";\n case \"gaming\" -> \"Check if your friends play on the same platform before deciding\";\n case \"auto\" -> \"Always test drive both vehicles and consider total cost of ownership\";\n case \"food\" -> \"Consider taste preferences, dietary needs, and value for money when choosing\";\n default -> \"Check community activity and long-term support before committing\";\n };\n }\n\n private String getMetricsTableRows(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n | Trophies | [score] | [score] | [winner] |\n | Squad Value | [score] | [score] | [winner] |\n | Fan Base | [score] | [score] | [winner] |\n | Recent Form | [score] | [score] | [winner] |\n | European Success | [score] | [score] | [winner] |\n | Youth Academy | [score] | [score] | [winner] |\n \"\"\";\n case \"travel\" -> \"\"\"\n | Cost of Living | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Culture | [score] | [score] | [winner] |\n | Food Scene | [score] | [score] | [winner] |\n | Public Transport | [score] | [score] | [winner] |\n | Nightlife | [score] | [score] | [winner] |\n \"\"\";\n case \"gaming\" -> \"\"\"\n | Graphics | [score] | [score] | [winner] |\n | Gameplay | [score] | [score] | [winner] |\n | Exclusives | [score] | [score] | [winner] |\n | Multiplayer | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Player Base | [score] | [score] | [winner] |\n \"\"\";\n case \"auto\" -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Fuel Economy | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Comfort | [score] | [score] | [winner] |\n | Reliability | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n \"\"\";\n case \"food\" -> \"\"\"\n | Taste | [score] | [score] | [winner] |\n | Price | [score] | [score] | [winner] |\n | Portion Size | [score] | [score] | [winner] |\n | Convenience | [score] | [score] | [winner] |\n | Nutrition | [score] | [score] | [winner] |\n | Satisfaction | [score] | [score] | [winner] |\n \"\"\";\n default -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Ease of Use | [score] | [score] | [winner] |\n | Community | [score] | [score] | [winner] |\n | Features | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Future Outlook | [score] | [score] | [winner] |\n \"\"\";\n };\n }\n\n public String analyzeSingle(String query) {\n String prompt = buildSingleAnalysisPrompt(query);\n\n try {\n if (isGeminiConfigured()) {\n return callGeminiAPI(prompt, \"general\").summary();\n }\n } catch (Exception geminiError) {\n System.err.println(\"Gemini API error: \" + geminiError.getMessage());\n }\n\n try {\n if (isDeepSeekConfigured()) {\n return callDeepSeekAPI(prompt, \"general\").summary();\n }\n } catch (Exception deepSeekError) {\n System.err.println(\"DeepSeek API error: \" + deepSeekError.getMessage());\n }\n\n return \"Unable to analyze '\" + query + \"'. Please try again later.\";\n }\n\n private String buildSingleAnalysisPrompt(String query) {\n return \"\"\"\n You are an expert analyst. Analyze \"%s\".\n Provide:\n 1. Quick overview (1-2 sentences)\n 2. Key strengths (3 bullet points)\n 3. Potential drawbacks (2 bullet points)\n 4. Best for: [target audience]\n 5. Pro tip for getting the most value\n Keep response under 150 words.\n \"\"\".formatted(query);\n }\n\n private String normalizeCompareMode(String compareMode) {\n if (!StringUtils.hasText(compareMode)) {\n return \"balanced\";\n }\n\n return compareMode.trim().toLowerCase();\n }\n\n private String normalizeDepth(String depth) {\n if (!StringUtils.hasText(depth)) {\n return \"standard\";\n }\n\n return depth.trim().toLowerCase();\n }\n\n private AIResponse callGeminiAPI(String prompt, String category) {\n String fullUrl = geminiApiUrl + \"?key=\" + geminiApiKey;\n Map<String, Object> requestBody = Map.of(\n \"contents\", List.of(Map.of(\"parts\", List.of(Map.of(\"text\", prompt)))),\n \"generationConfig\", Map.of(\"temperature\", 0.7, \"maxOutputTokens\", 1200));\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(fullUrl)\n .contentType(MediaType.APPLICATION_JSON)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"candidates\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> candidates = (List<Map<String, Object>>) response.get(\"candidates\");\n if (!candidates.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> content = (Map<String, Object>) candidates.get(0).get(\"content\");\n if (content != null && content.containsKey(\"parts\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> parts = (List<Map<String, Object>>) content.get(\"parts\");\n if (!parts.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> usageMetadata = (Map<String, Object>) response.get(\"usageMetadata\");\n\n System.out.println(\"Gemini API success\");\n return new AIResponse(\n Objects.toString(parts.get(0).get(\"text\"), \"\"),\n \"google-gemini\",\n resolveGeminiModel(response),\n category,\n false,\n readInt(usageMetadata, \"promptTokenCount\"),\n readInt(usageMetadata, \"candidatesTokenCount\"),\n readInt(usageMetadata, \"totalTokenCount\"));\n }\n }\n }\n }\n\n throw new RuntimeException(\"Invalid response from Gemini API\");\n }\n\n private AIResponse callDeepSeekAPI(String prompt, String category) {\n Map<String, Object> requestBody = Map.of(\n \"model\", DEEPSEEK_MODEL,\n \"messages\", List.of(\n Map.of(\"role\", \"system\", \"content\", \"You are a helpful comparison expert.\"),\n Map.of(\"role\", \"user\", \"content\", prompt)),\n \"temperature\", 0.7,\n \"max_tokens\", 1200);\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(DEEPSEEK_API_URL)\n .contentType(MediaType.APPLICATION_JSON)\n .header(\"Authorization\", \"Bearer \" + deepseekApiKey)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"choices\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get(\"choices\");\n if (!choices.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> message = (Map<String, Object>) choices.get(0).get(\"message\");\n if (message != null && message.containsKey(\"content\")) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> usage = (Map<String, Object>) response.get(\"usage\");\n\n System.out.println(\"DeepSeek API success (fallback)\");\n return new AIResponse(\n Objects.toString(message.get(\"content\"), \"\"),\n \"deepseek\",\n resolveDeepSeekModel(response),\n category,\n false,\n readInt(usage, \"prompt_tokens\"),\n readInt(usage, \"completion_tokens\"),\n readInt(usage, \"total_tokens\"));\n }\n }\n }\n\n throw new RuntimeException(\"Invalid response from DeepSeek API\");\n }\n\n private boolean isGeminiConfigured() {\n return StringUtils.hasText(geminiApiKey) && StringUtils.hasText(geminiApiUrl);\n }\n\n private boolean isDeepSeekConfigured() {\n return StringUtils.hasText(deepseekApiKey);\n }\n\n private String generateFallbackComparison(String itemA, String itemB, String category) {\n String categoryLabel = category.toUpperCase();\n return \"\"\"\n ## Category: %s\n\n ## Quick Verdict\n Both %s and %s are excellent choices in the %s category. The winner depends on your specific needs.\n\n ## %s\n - Strength 1: Strong reputation and proven track record\n - Strength 2: Wide community support\n - Strength 3: Consistent performance\n - Best for: Those who value reliability\n\n ## %s\n - Strength 1: Modern approach and innovation\n - Strength 2: Growing popularity\n - Strength 3: Fresh perspective\n - Best for: Those seeking something new\n\n ## Head-to-Head\n | Metric | %s | %s |\n |--------|------|------|\n | Overall | 85 | 82 |\n | Value | 80 | 85 |\n | Popularity | 88 | 78 |\n\n ## Final Recommendation\n Choose %s for stability. Choose %s for innovation.\n\n AI service is temporarily unavailable. Try again for detailed analysis.\n \"\"\".formatted(categoryLabel, itemA, itemB, category, itemA, itemB, itemA, itemB, itemA, itemB);\n }\n\n private String resolveGeminiModel(Map<String, Object> response) {\n String modelVersion = readString(response, \"modelVersion\");\n if (StringUtils.hasText(modelVersion)) {\n return modelVersion;\n }\n\n int modelsIndex = geminiApiUrl.indexOf(\"/models/\");\n if (modelsIndex == -1) {\n return \"gemini-unknown\";\n }\n\n String modelPath = geminiApiUrl.substring(modelsIndex + \"/models/\".length());\n int separatorIndex = modelPath.indexOf(':');\n return separatorIndex == -1 ? modelPath : modelPath.substring(0, separatorIndex);\n }\n\n private String resolveDeepSeekModel(Map<String, Object> response) {\n String model = readString(response, \"model\");\n return StringUtils.hasText(model) ? model : DEEPSEEK_MODEL;\n }\n\n private String readString(Map<String, Object> source, String key) {\n if (source == null) {\n return null;\n }\n\n Object value = source.get(key);\n return value instanceof String stringValue ? stringValue : null;\n }\n\n private Integer readInt(Map<String, Object> source, String key) {\n if (source == null) {\n return null;\n }\n\n Object value = source.get(key);\n return value instanceof Number numberValue ? numberValue.intValue() : null;\n }\n}\n"}, "3071c044265266ee75d9bf7321b3bf6274069399": {"t": 1785750991.6911688, "n": 26862, "h": "c8874e99144f82a6", "c": "package com.comparison.app.service;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.MediaType;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.client.RestClient;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n@Service\npublic class AISummaryService {\n\n @Value(\"${gemini.api.key}\")\n private String geminiApiKey;\n\n @Value(\"${gemini.api.url}\")\n private String geminiApiUrl;\n\n @Value(\"${deepseek.api.key}\")\n private String deepseekApiKey;\n\n private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/chat/completions\";\n private static final String DEEPSEEK_MODEL = \"deepseek-chat\";\n\n private final RestClient restClient;\n\n private static final Set<String> SPORTS_KEYWORDS = Set.of(\n \"bayern\", \"munich\", \"psg\", \"barcelona\", \"real madrid\", \"manchester\", \"liverpool\",\n \"chelsea\", \"arsenal\", \"juventus\", \"inter\", \"milan\", \"dortmund\", \"atletico\",\n \"nba\", \"lakers\", \"celtics\", \"warriors\", \"bulls\", \"heat\", \"knicks\",\n \"nfl\", \"patriots\", \"cowboys\", \"packers\", \"chiefs\", \"eagles\",\n \"team\", \"club\", \"fc\", \"united\", \"city\", \"athletic\", \"sporting\", \"football\");\n\n private static final Set<String> TRAVEL_KEYWORDS = Set.of(\n \"paris\", \"london\", \"tokyo\", \"new york\", \"rome\", \"amsterdam\", \"berlin\",\n \"dubai\", \"singapore\", \"bangkok\", \"sydney\", \"los angeles\", \"istanbul\",\n \"prague\", \"vienna\", \"budapest\", \"lisbon\", \"moscow\", \"beijing\", \"vacation\",\n \"baku\", \"azerbaijan\", \"venezuela\", \"caracas\", \"brazil\", \"rio\", \"mexico\",\n \"cairo\", \"egypt\", \"morocco\", \"marrakech\", \"athens\", \"greece\", \"spain\",\n \"barcelona\", \"madrid\", \"milan\", \"florence\", \"venice\", \"naples\", \"munich\",\n \"zurich\", \"geneva\", \"oslo\", \"stockholm\", \"copenhagen\", \"helsinki\",\n \"seoul\", \"taipei\", \"hong kong\", \"manila\", \"jakarta\", \"mumbai\", \"delhi\",\n \"city\", \"country\", \"travel\", \"trip\", \"destination\", \"tourism\", \"tourist\");\n\n private static final Set<String> GAMING_KEYWORDS = Set.of(\n \"ps5\", \"playstation\", \"xbox\", \"nintendo\", \"switch\", \"fortnite\", \"minecraft\",\n \"gta\", \"call of duty\", \"cod\", \"fifa\", \"nba2k\", \"zelda\", \"mario\", \"halo\",\n \"god of war\", \"spider-man\", \"elden ring\", \"game\", \"gaming\", \"console\");\n\n private static final Set<String> AUTO_KEYWORDS = Set.of(\n \"tesla\", \"bmw\", \"mercedes\", \"audi\", \"porsche\", \"toyota\", \"honda\", \"ford\",\n \"chevrolet\", \"volkswagen\", \"hyundai\", \"kia\", \"nissan\", \"mazda\",\n \"car\", \"vehicle\", \"suv\", \"sedan\", \"truck\", \"electric\", \"hybrid\");\n\n private static final Set<String> FOOD_KEYWORDS = Set.of(\n \"pizza\", \"burger\", \"sushi\", \"pasta\", \"tacos\", \"ramen\", \"steak\", \"salad\",\n \"sandwich\", \"wings\", \"fries\", \"noodles\", \"curry\", \"kebab\", \"shawarma\",\n \"mcdonald\", \"burger king\", \"kfc\", \"pizza hut\", \"domino\", \"subway\",\n \"starbucks\", \"chipotle\", \"taco bell\", \"wendy\", \"restaurant\", \"food\", \"cuisine\");\n\n public AISummaryService() {\n this.restClient = RestClient.create();\n }\n\n public AIResponse compareTwoItems(String itemA, String itemB, CompareRequestOptions options) {\n CompareRequestOptions safeOptions = options == null ? CompareRequestOptions.defaults() : options;\n String category = StringUtils.hasText(safeOptions.categoryOverride())\n ? safeOptions.categoryOverride()\n : detectCategory(itemA, itemB);\n String prompt = buildCategorySpecificPrompt(itemA, itemB, category, safeOptions);\n\n try {\n if (isGeminiConfigured()) {\n System.out.println(\"Trying Gemini API... Category: \" + category);\n return callGeminiAPI(prompt, category);\n }\n } catch (Exception geminiError) {\n System.err.println(\"Gemini API error: \" + geminiError.getMessage());\n }\n\n try {\n if (isDeepSeekConfigured()) {\n System.out.println(\"Falling back to DeepSeek API...\");\n return callDeepSeekAPI(prompt, category);\n }\n } catch (Exception deepSeekError) {\n System.err.println(\"DeepSeek API error: \" + deepSeekError.getMessage());\n }\n\n return new AIResponse(\n generateFallbackComparison(itemA, itemB, category),\n \"fallback\",\n \"template-fallback\",\n category,\n true,\n null,\n null,\n null);\n }\n\n private String detectCategory(String itemA, String itemB) {\n String combined = (itemA + \" \" + itemB).toLowerCase();\n\n if (FOOD_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"food\";\n }\n if (SPORTS_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"sports\";\n }\n if (TRAVEL_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"travel\";\n }\n if (GAMING_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"gaming\";\n }\n if (AUTO_KEYWORDS.stream().anyMatch(combined::contains)) {\n return \"auto\";\n }\n\n return \"tech\";\n }\n\n private String buildCategorySpecificPrompt(\n String itemA,\n String itemB,\n String category,\n CompareRequestOptions options) {\n String metricsSection = getCategoryMetrics(category);\n String tipsSection = getCategoryTips(category);\n String compareMode = normalizeCompareMode(options.compareMode());\n String depth = normalizeDepth(options.depth());\n String decisionContext = StringUtils.hasText(options.decisionContext())\n ? options.decisionContext().trim()\n : \"No extra context provided.\";\n String priorities = options.priorities().isEmpty()\n ? \"None selected. Use the category metrics above.\"\n : String.join(\", \", options.priorities());\n String mustHaves = options.mustHaves().isEmpty()\n ? \"None specified.\"\n : String.join(\", \", options.mustHaves());\n String redFlags = options.redFlags().isEmpty()\n ? \"None specified.\"\n : String.join(\", \", options.redFlags());\n String depthInstruction = switch (depth) {\n case \"quick\" -> \"Keep the answer tight and decisive in under 220 words.\";\n case \"extreme\" ->\n \"Go deeper than usual: expose tradeoffs, failure modes, edge cases, and practical decision boundaries while staying under 550 words.\";\n default -> \"Keep the answer practical and structured in under 400 words.\";\n };\n\n return \"\"\"\n You are an expert %s comparison analyst. Compare \"%s\" vs \"%s\".\n\n ## Category: %s\n\n IMPORTANT: Use ONLY these domain-specific metrics for comparison:\n %s\n\n ## Engine Settings\n - Compare mode: %s\n - Analysis depth: %s\n - Decision context: %s\n - Priority metrics: %s\n - Must-have requirements: %s\n - Red flags to avoid: %s\n\n Treat the engine settings above as hard guidance. Weigh the chosen priorities more heavily, explicitly call out whether must-have requirements are satisfied, and highlight any red flags that appear.\n\n ## Quick Verdict\n [One clear sentence declaring the winner and why. Name the winner explicitly.]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## Head-to-Head Scores (rate 1-100)\n | Metric | %s | %s | Winner |\n |--------|------|------|--------|\n %s\n\n ## Contextual Tip\n %s\n\n ## Final Recommendation\n Choose %s if [specific condition]. Choose %s if [specific condition].\n\n %s\n Be specific, practical, and domain-appropriate. Always declare a clear winner.\n \"\"\"\n .formatted(\n category,\n itemA,\n itemB,\n category.toUpperCase(),\n metricsSection,\n compareMode,\n depth,\n decisionContext,\n priorities,\n mustHaves,\n redFlags,\n itemA,\n category,\n category,\n category,\n category,\n itemB,\n category,\n category,\n category,\n category,\n itemA,\n itemB,\n getMetricsTableRows(category),\n tipsSection,\n itemA,\n itemB,\n depthInstruction);\n }\n\n private String getCategoryMetrics(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n - Trophies Won (total major titles and recent success)\n - Squad Value (market value of players)\n - Fan Base (global supporters and stadium atmosphere)\n - History & Legacy (tradition, founding, legends)\n - Youth Academy (player development quality)\n - Manager Quality (tactical ability, experience)\n - Recent Form (last season performance)\n - European Success (Champions League/Europa performance)\n - Domestic Dominance (league titles, consistency)\n - Star Players (key talent and marquee names)\n - Financial Power (revenue, spending ability)\n - Stadium (capacity, atmosphere, facilities)\n \"\"\";\n case \"travel\" -> \"\"\"\n - Cost of Living (daily expenses, accommodation, food)\n - Safety (crime rate, tourist safety)\n - Weather (climate, best seasons to visit)\n - Culture (museums, history, art scene)\n - Food Scene (cuisine variety, restaurants)\n - Nightlife (bars, clubs, entertainment)\n - Public Transport (metro, buses, walkability)\n - Tourist Attractions (landmarks, must-see spots)\n - Language (English friendliness)\n - Accommodation (hotel quality, Airbnb options)\n - Shopping (markets, stores, souvenirs)\n - Local Experience (authenticity, local immersion)\n \"\"\";\n case \"gaming\" -> \"\"\"\n - Graphics (visual quality, art style)\n - Gameplay (fun factor, mechanics)\n - Story (narrative, characters)\n - Multiplayer (online experience, modes)\n - Exclusives (unique titles, franchises)\n - Price (value for money)\n - Player Base (active community)\n - Updates (content support, DLC)\n - Controls (responsiveness, layout)\n - Replay Value (longevity)\n - Esports Scene (competitive potential)\n - Hardware (specs, performance)\n \"\"\";\n case \"auto\" -> \"\"\"\n - Performance (horsepower, acceleration, top speed)\n - Fuel Economy (MPG or electric range)\n - Safety (crash ratings, ADAS features)\n - Comfort (ride quality, noise, seats)\n - Price (MSRP, value proposition)\n - Reliability (dependability, maintenance)\n - Resale Value (depreciation rate)\n - Features (tech, infotainment, connectivity)\n - Interior Quality (materials, design)\n - Cargo Space (trunk, practicality)\n - Handling (driving dynamics, steering)\n - Brand Prestige (reputation, status)\n \"\"\";\n case \"food\" -> \"\"\"\n - Taste (flavor, freshness, quality)\n - Price (cost per serving, value)\n - Portion Size (amount of food)\n - Ingredients (quality, freshness)\n - Preparation Time (how long to make/serve)\n - Nutritional Value (calories, health factor)\n - Availability (ease of finding)\n - Variety (menu options, customization)\n - Popularity (how widely loved)\n - Convenience (ease of eating)\n - Ambiance (dining experience)\n - Satisfaction (overall fulfillment)\n \"\"\";\n default -> \"\"\"\n - Performance (speed, efficiency)\n - Ease of Use (learning curve)\n - Community (support, ecosystem)\n - Documentation (guides, tutorials)\n - Features (functionality, capabilities)\n - Price/Value (cost effectiveness)\n - Scalability (growth potential)\n - Reliability (stability, uptime)\n - Integration (compatibility)\n - Future Outlook (roadmap, updates)\n \"\"\";\n };\n }\n\n private String getCategoryTips(String category) {\n return switch (category) {\n case \"sports\" -> \"Consider recent Champions League and domestic league performance when choosing\";\n case \"travel\" -> \"Book accommodations 3 months in advance and visit attractions early morning\";\n case \"gaming\" -> \"Check if your friends play on the same platform before deciding\";\n case \"auto\" -> \"Always test drive both vehicles and consider total cost of ownership\";\n case \"food\" -> \"Consider taste preferences, dietary needs, and value for money when choosing\";\n default -> \"Check community activity and long-term support before committing\";\n };\n }\n\n private String getMetricsTableRows(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n | Trophies | [score] | [score] | [winner] |\n | Squad Value | [score] | [score] | [winner] |\n | Fan Base | [score] | [score] | [winner] |\n | Recent Form | [score] | [score] | [winner] |\n | European Success | [score] | [score] | [winner] |\n | Youth Academy | [score] | [score] | [winner] |\n \"\"\";\n case \"travel\" -> \"\"\"\n | Cost of Living | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Culture | [score] | [score] | [winner] |\n | Food Scene | [score] | [score] | [winner] |\n | Public Transport | [score] | [score] | [winner] |\n | Nightlife | [score] | [score] | [winner] |\n \"\"\";\n case \"gaming\" -> \"\"\"\n | Graphics | [score] | [score] | [winner] |\n | Gameplay | [score] | [score] | [winner] |\n | Exclusives | [score] | [score] | [winner] |\n | Multiplayer | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Player Base | [score] | [score] | [winner] |\n \"\"\";\n case \"auto\" -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Fuel Economy | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Comfort | [score] | [score] | [winner] |\n | Reliability | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n \"\"\";\n case \"food\" -> \"\"\"\n | Taste | [score] | [score] | [winner] |\n | Price | [score] | [score] | [winner] |\n | Portion Size | [score] | [score] | [winner] |\n | Convenience | [score] | [score] | [winner] |\n | Nutrition | [score] | [score] | [winner] |\n | Satisfaction | [score] | [score] | [winner] |\n \"\"\";\n default -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Ease of Use | [score] | [score] | [winner] |\n | Community | [score] | [score] | [winner] |\n | Features | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Future Outlook | [score] | [score] | [winner] |\n \"\"\";\n };\n }\n\n public String analyzeSingle(String query) {\n String prompt = buildSingleAnalysisPrompt(query);\n\n try {\n if (isGeminiConfigured()) {\n return callGeminiAPI(prompt, \"general\").summary();\n }\n } catch (Exception geminiError) {\n System.err.println(\"Gemini API error: \" + geminiError.getMessage());\n }\n\n try {\n if (isDeepSeekConfigured()) {\n return callDeepSeekAPI(prompt, \"general\").summary();\n }\n } catch (Exception deepSeekError) {\n System.err.println(\"DeepSeek API error: \" + deepSeekError.getMessage());\n }\n\n return \"Unable to analyze '\" + query + \"'. Please try again later.\";\n }\n\n private String buildSingleAnalysisPrompt(String query) {\n return \"\"\"\n You are an expert analyst. Analyze \"%s\".\n Provide:\n 1. Quick overview (1-2 sentences)\n 2. Key strengths (3 bullet points)\n 3. Potential drawbacks (2 bullet points)\n 4. Best for: [target audience]\n 5. Pro tip for getting the most value\n Keep response under 150 words.\n \"\"\".formatted(query);\n }\n\n private String normalizeCompareMode(String compareMode) {\n if (!StringUtils.hasText(compareMode)) {\n return \"balanced\";\n }\n\n return compareMode.trim().toLowerCase();\n }\n\n private String normalizeDepth(String depth) {\n if (!StringUtils.hasText(depth)) {\n return \"standard\";\n }\n\n return depth.trim().toLowerCase();\n }\n\n private AIResponse callGeminiAPI(String prompt, String category) {\n String fullUrl = geminiApiUrl + \"?key=\" + geminiApiKey;\n Map<String, Object> requestBody = Map.of(\n \"contents\", List.of(Map.of(\"parts\", List.of(Map.of(\"text\", prompt)))),\n \"generationConfig\", Map.of(\"temperature\", 0.7, \"maxOutputTokens\", 1200));\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(fullUrl)\n .contentType(MediaType.APPLICATION_JSON)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"candidates\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> candidates = (List<Map<String, Object>>) response.get(\"candidates\");\n if (!candidates.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> content = (Map<String, Object>) candidates.get(0).get(\"content\");\n if (content != null && content.containsKey(\"parts\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> parts = (List<Map<String, Object>>) content.get(\"parts\");\n if (!parts.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> usageMetadata = (Map<String, Object>) response.get(\"usageMetadata\");\n\n System.out.println(\"Gemini API success\");\n return new AIResponse(\n Objects.toString(parts.get(0).get(\"text\"), \"\"),\n \"google-gemini\",\n resolveGeminiModel(response),\n category,\n false,\n readInt(usageMetadata, \"promptTokenCount\"),\n readInt(usageMetadata, \"candidatesTokenCount\"),\n readInt(usageMetadata, \"totalTokenCount\"));\n }\n }\n }\n }\n\n throw new RuntimeException(\"Invalid response from Gemini API\");\n }\n\n private AIResponse callDeepSeekAPI(String prompt, String category) {\n Map<String, Object> requestBody = Map.of(\n \"model\", DEEPSEEK_MODEL,\n \"messages\", List.of(\n Map.of(\"role\", \"system\", \"content\", \"You are a helpful comparison expert.\"),\n Map.of(\"role\", \"user\", \"content\", prompt)),\n \"temperature\", 0.7,\n \"max_tokens\", 1200);\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(DEEPSEEK_API_URL)\n .contentType(MediaType.APPLICATION_JSON)\n .header(\"Authorization\", \"Bearer \" + deepseekApiKey)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"choices\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get(\"choices\");\n if (!choices.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> message = (Map<String, Object>) choices.get(0).get(\"message\");\n if (message != null && message.containsKey(\"content\")) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> usage = (Map<String, Object>) response.get(\"usage\");\n\n System.out.println(\"DeepSeek API success (fallback)\");\n return new AIResponse(\n Objects.toString(message.get(\"content\"), \"\"),\n \"deepseek\",\n resolveDeepSeekModel(response),\n category,\n false,\n readInt(usage, \"prompt_tokens\"),\n readInt(usage, \"completion_tokens\"),\n readInt(usage, \"total_tokens\"));\n }\n }\n }\n\n throw new RuntimeException(\"Invalid response from DeepSeek API\");\n }\n\n private boolean isGeminiConfigured() {\n return StringUtils.hasText(geminiApiKey) && StringUtils.hasText(geminiApiUrl);\n }\n\n private boolean isDeepSeekConfigured() {\n return StringUtils.hasText(deepseekApiKey);\n }\n\n private String generateFallbackComparison(String itemA, String itemB, String category) {\n String categoryLabel = category.toUpperCase();\n return \"\"\"\n ## Category: %s\n\n ## Quick Verdict\n Both %s and %s are excellent choices in the %s category. The winner depends on your specific needs.\n\n ## %s\n - Strength 1: Strong reputation and proven track record\n - Strength 2: Wide community support\n - Strength 3: Consistent performance\n - Best for: Those who value reliability\n\n ## %s\n - Strength 1: Modern approach and innovation\n - Strength 2: Growing popularity\n - Strength 3: Fresh perspective\n - Best for: Those seeking something new\n\n ## Head-to-Head\n | Metric | %s | %s |\n |--------|------|------|\n | Overall | 85 | 82 |\n | Value | 80 | 85 |\n | Popularity | 88 | 78 |\n\n ## Final Recommendation\n Choose %s for stability. Choose %s for innovation.\n\n AI service is temporarily unavailable. Try again for detailed analysis.\n \"\"\".formatted(categoryLabel, itemA, itemB, category, itemA, itemB, itemA, itemB, itemA, itemB);\n }\n\n private String resolveGeminiModel(Map<String, Object> response) {\n String modelVersion = readString(response, \"modelVersion\");\n if (StringUtils.hasText(modelVersion)) {\n return modelVersion;\n }\n\n int modelsIndex = geminiApiUrl.indexOf(\"/models/\");\n if (modelsIndex == -1) {\n return \"gemini-unknown\";\n }\n\n String modelPath = geminiApiUrl.substring(modelsIndex + \"/models/\".length());\n int separatorIndex = modelPath.indexOf(':');\n return separatorIndex == -1 ? modelPath : modelPath.substring(0, separatorIndex);\n }\n\n private String resolveDeepSeekModel(Map<String, Object> response) {\n String model = readString(response, \"model\");\n return StringUtils.hasText(model) ? model : DEEPSEEK_MODEL;\n }\n\n private String readString(Map<String, Object> source, String key) {\n if (source == null) {\n return null;\n }\n\n Object value = source.get(key);\n return value instanceof String stringValue ? stringValue : null;\n }\n\n private Integer readInt(Map<String, Object> source, String key) {\n if (source == null) {\n return null;\n }\n\n Object value = source.get(key);\n return value instanceof Number numberValue ? numberValue.intValue() : null;\n }\n}\n"}, "bbaa9b0c38a2e91386d9f41a876a27642690e2d7": {"t": 1785750992.8253462, "n": 20141, "h": "1326a4785f1de1cb", "c": "package com.comparison.app.service;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.client.RestClient;\nimport org.springframework.http.MediaType;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n@Service\npublic class AISummaryService {\n\n @Value(\"${gemini.api.key}\")\n private String geminiApiKey;\n\n @Value(\"${gemini.api.url}\")\n private String geminiApiUrl;\n\n @Value(\"${deepseek.api.key}\")\n private String deepseekApiKey;\n\n private static final String DEEPSEEK_API_URL = \"https://api.deepseek.com/v1/chat/completions\";\n\n private final RestClient restClient;\n\n // Category keywords for detection\n private static final Set<String> SPORTS_KEYWORDS = Set.of(\n \"bayern\", \"munich\", \"psg\", \"barcelona\", \"real madrid\", \"manchester\", \"liverpool\",\n \"chelsea\", \"arsenal\", \"juventus\", \"inter\", \"milan\", \"dortmund\", \"atletico\",\n \"nba\", \"lakers\", \"celtics\", \"warriors\", \"bulls\", \"heat\", \"knicks\",\n \"nfl\", \"patriots\", \"cowboys\", \"packers\", \"chiefs\", \"eagles\",\n \"team\", \"club\", \"fc\", \"united\", \"city\", \"athletic\", \"sporting\", \"football\");\n\n private static final Set<String> TRAVEL_KEYWORDS = Set.of(\n \"paris\", \"london\", \"tokyo\", \"new york\", \"rome\", \"amsterdam\", \"berlin\",\n \"dubai\", \"singapore\", \"bangkok\", \"sydney\", \"los angeles\", \"istanbul\",\n \"prague\", \"vienna\", \"budapest\", \"lisbon\", \"moscow\", \"beijing\", \"vacation\");\n\n private static final Set<String> GAMING_KEYWORDS = Set.of(\n \"ps5\", \"playstation\", \"xbox\", \"nintendo\", \"switch\", \"fortnite\", \"minecraft\",\n \"gta\", \"call of duty\", \"cod\", \"fifa\", \"nba2k\", \"zelda\", \"mario\", \"halo\",\n \"god of war\", \"spider-man\", \"elden ring\", \"game\", \"gaming\", \"console\");\n\n private static final Set<String> AUTO_KEYWORDS = Set.of(\n \"tesla\", \"bmw\", \"mercedes\", \"audi\", \"porsche\", \"toyota\", \"honda\", \"ford\",\n \"chevrolet\", \"volkswagen\", \"hyundai\", \"kia\", \"nissan\", \"mazda\",\n \"car\", \"vehicle\", \"suv\", \"sedan\", \"truck\", \"electric\", \"hybrid\");\n\n private static final Set<String> FOOD_KEYWORDS = Set.of(\n \"pizza\", \"burger\", \"sushi\", \"pasta\", \"tacos\", \"ramen\", \"steak\", \"salad\",\n \"sandwich\", \"wings\", \"fries\", \"noodles\", \"curry\", \"kebab\", \"shawarma\",\n \"mcdonald\", \"burger king\", \"kfc\", \"pizza hut\", \"domino\", \"subway\",\n \"starbucks\", \"chipotle\", \"taco bell\", \"wendy\", \"restaurant\", \"food\", \"cuisine\");\n\n public AISummaryService() {\n this.restClient = RestClient.create();\n }\n\n /**\n * Compare two items side-by-side with smart category detection\n */\n public String compareTwoItems(String itemA, String itemB) {\n String category = detectCategory(itemA, itemB);\n String prompt = buildCategorySpecificPrompt(itemA, itemB, category);\n\n try {\n System.out.println(\"Trying Gemini API... Category: \" + category);\n return callGeminiAPI(prompt);\n } catch (Exception e) {\n System.err.println(\"Gemini API error: \" + e.getMessage());\n try {\n System.out.println(\"Falling back to DeepSeek API...\");\n return callDeepSeekAPI(prompt);\n } catch (Exception e2) {\n System.err.println(\"DeepSeek API error: \" + e2.getMessage());\n return generateFallbackComparison(itemA, itemB, category);\n }\n }\n }\n\n /**\n * Detect category based on keywords\n */\n private String detectCategory(String itemA, String itemB) {\n String combined = (itemA + \" \" + itemB).toLowerCase();\n\n if (FOOD_KEYWORDS.stream().anyMatch(combined::contains))\n return \"food\";\n if (SPORTS_KEYWORDS.stream().anyMatch(combined::contains))\n return \"sports\";\n if (TRAVEL_KEYWORDS.stream().anyMatch(combined::contains))\n return \"travel\";\n if (GAMING_KEYWORDS.stream().anyMatch(combined::contains))\n return \"gaming\";\n if (AUTO_KEYWORDS.stream().anyMatch(combined::contains))\n return \"auto\";\n\n return \"tech\"; // Default\n }\n\n /**\n * Build category-specific comparison prompt\n */\n private String buildCategorySpecificPrompt(String itemA, String itemB, String category) {\n String metricsSection = getCategoryMetrics(category);\n String tipsSection = getCategoryTips(category);\n\n return \"\"\"\n You are an expert %s comparison analyst. Compare \"%s\" vs \"%s\".\n\n ## Category: %s\n\n IMPORTANT: Use ONLY these domain-specific metrics for comparison:\n %s\n\n ## 🏆 Quick Verdict\n [One clear sentence declaring the winner and why. Name the winner explicitly.]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## %s\n - Strength 1: [specific to this %s context]\n - Strength 2: [specific to this %s context]\n - Strength 3: [specific to this %s context]\n - Best for: [target audience for %s]\n\n ## 📊 Head-to-Head Scores (rate 1-100)\n | Metric | %s | %s | Winner |\n |--------|------|------|--------|\n %s\n\n ## 💡 Contextual Tip\n %s\n\n ## 📝 Final Recommendation\n Choose %s if [specific condition]. Choose %s if [specific condition].\n\n Keep response under 400 words. Be specific, practical, and domain-appropriate. Always declare a clear winner.\n \"\"\"\n .formatted(\n category, itemA, itemB,\n category.toUpperCase(),\n metricsSection,\n itemA, category, category, category, category,\n itemB, category, category, category, category,\n itemA, itemB,\n getMetricsTableRows(category),\n tipsSection,\n itemA, itemB);\n }\n\n /**\n * Get category-specific metrics\n */\n private String getCategoryMetrics(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n - Trophies Won (total major titles and recent success)\n - Squad Value (market value of players)\n - Fan Base (global supporters and stadium atmosphere)\n - History & Legacy (tradition, founding, legends)\n - Youth Academy (player development quality)\n - Manager Quality (tactical ability, experience)\n - Recent Form (last season performance)\n - European Success (Champions League/Europa performance)\n - Domestic Dominance (league titles, consistency)\n - Star Players (key talent and marquee names)\n - Financial Power (revenue, spending ability)\n - Stadium (capacity, atmosphere, facilities)\n \"\"\";\n case \"travel\" -> \"\"\"\n - Cost of Living (daily expenses, accommodation, food)\n - Safety (crime rate, tourist safety)\n - Weather (climate, best seasons to visit)\n - Culture (museums, history, art scene)\n - Food Scene (cuisine variety, restaurants)\n - Nightlife (bars, clubs, entertainment)\n - Public Transport (metro, buses, walkability)\n - Tourist Attractions (landmarks, must-see spots)\n - Language (English friendliness)\n - Accommodation (hotel quality, Airbnb options)\n - Shopping (markets, stores, souvenirs)\n - Local Experience (authenticity, local immersion)\n \"\"\";\n case \"gaming\" -> \"\"\"\n - Graphics (visual quality, art style)\n - Gameplay (fun factor, mechanics)\n - Story (narrative, characters)\n - Multiplayer (online experience, modes)\n - Exclusives (unique titles, franchises)\n - Price (value for money)\n - Player Base (active community)\n - Updates (content support, DLC)\n - Controls (responsiveness, layout)\n - Replay Value (longevity)\n - Esports Scene (competitive potential)\n - Hardware (specs, performance)\n \"\"\";\n case \"auto\" -> \"\"\"\n - Performance (horsepower, acceleration, top speed)\n - Fuel Economy (MPG or electric range)\n - Safety (crash ratings, ADAS features)\n - Comfort (ride quality, noise, seats)\n - Price (MSRP, value proposition)\n - Reliability (dependability, maintenance)\n - Resale Value (depreciation rate)\n - Features (tech, infotainment, connectivity)\n - Interior Quality (materials, design)\n - Cargo Space (trunk, practicality)\n - Handling (driving dynamics, steering)\n - Brand Prestige (reputation, status)\n \"\"\";\n case \"food\" -> \"\"\"\n - Taste (flavor, freshness, quality)\n - Price (cost per serving, value)\n - Portion Size (amount of food)\n - Ingredients (quality, freshness)\n - Preparation Time (how long to make/serve)\n - Nutritional Value (calories, health factor)\n - Availability (ease of finding)\n - Variety (menu options, customization)\n - Popularity (how widely loved)\n - Convenience (ease of eating)\n - Ambiance (dining experience)\n - Satisfaction (overall fulfillment)\n \"\"\";\n default -> \"\"\"\n - Performance (speed, efficiency)\n - Ease of Use (learning curve)\n - Community (support, ecosystem)\n - Documentation (guides, tutorials)\n - Features (functionality, capabilities)\n - Price/Value (cost effectiveness)\n - Scalability (growth potential)\n - Reliability (stability, uptime)\n - Integration (compatibility)\n - Future Outlook (roadmap, updates)\n \"\"\";\n };\n }\n\n /**\n * Get contextual tips for category\n */\n private String getCategoryTips(String category) {\n return switch (category) {\n case \"sports\" -> \"Consider recent Champions League and domestic league performance when choosing\";\n case \"travel\" -> \"Book accommodations 3 months in advance and visit attractions early morning\";\n case \"gaming\" -> \"Check if your friends play on the same platform before deciding\";\n case \"auto\" -> \"Always test drive both vehicles and consider total cost of ownership\";\n case \"food\" -> \"Consider taste preferences, dietary needs, and value for money when choosing\";\n default -> \"Check community activity and long-term support before committing\";\n };\n }\n\n /**\n * Get metrics table rows for category\n */\n private String getMetricsTableRows(String category) {\n return switch (category) {\n case \"sports\" -> \"\"\"\n | Trophies | [score] | [score] | [winner] |\n | Squad Value | [score] | [score] | [winner] |\n | Fan Base | [score] | [score] | [winner] |\n | Recent Form | [score] | [score] | [winner] |\n | European Success | [score] | [score] | [winner] |\n | Youth Academy | [score] | [score] | [winner] |\n \"\"\";\n case \"travel\" -> \"\"\"\n | Cost of Living | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Culture | [score] | [score] | [winner] |\n | Food Scene | [score] | [score] | [winner] |\n | Public Transport | [score] | [score] | [winner] |\n | Nightlife | [score] | [score] | [winner] |\n \"\"\";\n case \"gaming\" -> \"\"\"\n | Graphics | [score] | [score] | [winner] |\n | Gameplay | [score] | [score] | [winner] |\n | Exclusives | [score] | [score] | [winner] |\n | Multiplayer | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Player Base | [score] | [score] | [winner] |\n \"\"\";\n case \"auto\" -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Fuel Economy | [score] | [score] | [winner] |\n | Safety | [score] | [score] | [winner] |\n | Comfort | [score] | [score] | [winner] |\n | Reliability | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n \"\"\";\n case \"food\" -> \"\"\"\n | Taste | [score] | [score] | [winner] |\n | Price | [score] | [score] | [winner] |\n | Portion Size | [score] | [score] | [winner] |\n | Convenience | [score] | [score] | [winner] |\n | Nutrition | [score] | [score] | [winner] |\n | Satisfaction | [score] | [score] | [winner] |\n \"\"\";\n default -> \"\"\"\n | Performance | [score] | [score] | [winner] |\n | Ease of Use | [score] | [score] | [winner] |\n | Community | [score] | [score] | [winner] |\n | Features | [score] | [score] | [winner] |\n | Value | [score] | [score] | [winner] |\n | Future Outlook | [score] | [score] | [winner] |\n \"\"\";\n };\n }\n\n /**\n * Analyze a single item\n */\n public String analyzeSingle(String query) {\n String prompt = buildSingleAnalysisPrompt(query);\n try {\n return callGeminiAPI(prompt);\n } catch (Exception e) {\n try {\n return callDeepSeekAPI(prompt);\n } catch (Exception e2) {\n return \"Unable to analyze '\" + query + \"'. Please try again later.\";\n }\n }\n }\n\n private String buildSingleAnalysisPrompt(String query) {\n return \"\"\"\n You are an expert analyst. Analyze \"%s\".\n Provide:\n 1. Quick overview (1-2 sentences)\n 2. Key strengths (3 bullet points)\n 3. Potential drawbacks (2 bullet points)\n 4. Best for: [target audience]\n 5. Pro tip for getting the most value\n Keep response under 150 words.\n \"\"\".formatted(query);\n }\n\n private String callGeminiAPI(String prompt) {\n String fullUrl = geminiApiUrl + \"?key=\" + geminiApiKey;\n\n Map<String, Object> requestBody = Map.of(\n \"contents\", List.of(Map.of(\"parts\", List.of(Map.of(\"text\", prompt)))),\n \"generationConfig\", Map.of(\"temperature\", 0.7, \"maxOutputTokens\", 1200));\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(fullUrl)\n .contentType(MediaType.APPLICATION_JSON)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"candidates\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> candidates = (List<Map<String, Object>>) response.get(\"candidates\");\n if (!candidates.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> content = (Map<String, Object>) candidates.get(0).get(\"content\");\n if (content != null && content.containsKey(\"parts\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> parts = (List<Map<String, Object>>) content.get(\"parts\");\n if (!parts.isEmpty()) {\n System.out.println(\"✅ Gemini API success\");\n return (String) parts.get(0).get(\"text\");\n }\n }\n }\n }\n throw new RuntimeException(\"Invalid response from Gemini API\");\n }\n\n private String callDeepSeekAPI(String prompt) {\n Map<String, Object> requestBody = Map.of(\n \"model\", \"deepseek-chat\",\n \"messages\", List.of(\n Map.of(\"role\", \"system\", \"content\", \"You are a helpful comparison expert.\"),\n Map.of(\"role\", \"user\", \"content\", prompt)),\n \"temperature\", 0.7,\n \"max_tokens\", 1200);\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(DEEPSEEK_API_URL)\n .contentType(MediaType.APPLICATION_JSON)\n .header(\"Authorization\", \"Bearer \" + deepseekApiKey)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"choices\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> choices = (List<Map<String, Object>>) response.get(\"choices\");\n if (!choices.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> message = (Map<String, Object>) choices.get(0).get(\"message\");\n if (message != null && message.containsKey(\"content\")) {\n System.out.println(\"✅ DeepSeek API success (fallback)\");\n return (String) message.get(\"content\");\n }\n }\n }\n throw new RuntimeException(\"Invalid response from DeepSeek API\");\n }\n\n private String generateFallbackComparison(String itemA, String itemB, String category) {\n String categoryLabel = category.toUpperCase();\n return \"\"\"\n ## Category: %s\n\n ## 🏆 Quick Verdict\n Both %s and %s are excellent choices in the %s category - the winner depends on your specific needs.\n\n ## %s\n - Strength 1: Strong reputation and proven track record\n - Strength 2: Wide community support\n - Strength 3: Consistent performance\n - Best for: Those who value reliability\n\n ## %s\n - Strength 1: Modern approach and innovation\n - Strength 2: Growing popularity\n - Strength 3: Fresh perspective\n - Best for: Those seeking something new\n\n ## 📊 Head-to-Head\n | Metric | %s | %s |\n |--------|------|------|\n | Overall | 85 | 82 |\n | Value | 80 | 85 |\n | Popularity | 88 | 78 |\n\n ## 💡 Final Recommendation\n Choose %s for stability. Choose %s for innovation.\n\n ⚠️ AI service temporarily unavailable. Try again for detailed analysis.\n \"\"\".formatted(categoryLabel, itemA, itemB, category, itemA, itemB, itemA, itemB, itemA, itemB);\n }\n}\n"}, "30fadae27e5c0df2954180814ae115e60947d473": {"t": 1785750994.0679302, "n": 5735, "h": "11fc7f5fe64398f6", "c": "package com.comparison.app.service;\n\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.web.client.RestClient;\nimport org.springframework.http.MediaType;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Service\npublic class AISummaryService {\n\n @Value(\"${gemini.api.key}\")\n private String apiKey;\n\n @Value(\"${gemini.api.url}\")\n private String apiUrl;\n\n private final RestClient restClient;\n\n public AISummaryService() {\n this.restClient = RestClient.create();\n }\n\n /**\n * Compare two items side-by-side with smart category detection\n */\n public String compareTwoItems(String itemA, String itemB) {\n String prompt = buildComparisonPrompt(itemA, itemB);\n\n try {\n return callGeminiAPI(prompt);\n } catch (Exception e) {\n System.err.println(\"Gemini API error: \" + e.getMessage());\n return generateFallbackComparison(itemA, itemB);\n }\n }\n\n /**\n * Analyze a single item\n */\n public String analyzeSingle(String query) {\n String prompt = buildSingleAnalysisPrompt(query);\n\n try {\n return callGeminiAPI(prompt);\n } catch (Exception e) {\n System.err.println(\"Gemini API error: \" + e.getMessage());\n return \"Unable to analyze '\" + query + \"'. Please try again later.\";\n }\n }\n\n private String buildComparisonPrompt(String itemA, String itemB) {\n return \"\"\"\n You are an expert comparison AI. Compare \"%s\" vs \"%s\".\n\n FIRST: Detect what category this comparison belongs to:\n - Travel/Cities (e.g., Paris vs London)\n - Technology (e.g., React vs Vue, iPhone vs Samsung)\n - Gaming (e.g., PS5 vs Xbox, Fortnite vs Apex)\n - Services (e.g., Vercel vs Netlify, Spotify vs Apple Music)\n - Food/Restaurants\n - Products/Shopping\n - Other\n\n THEN: Provide a structured side-by-side comparison:\n\n ## 🏆 Quick Verdict\n One sentence declaring which is better and why.\n\n ## %s\n • Key strengths (2-3 bullet points)\n • Best for: [target audience]\n\n ## %s\n • Key strengths (2-3 bullet points)\n • Best for: [target audience]\n\n ## 📊 Head-to-Head\n | Aspect | %s | %s |\n |--------|-------|-------|\n | [Key factor 1] | Rating/info | Rating/info |\n | [Key factor 2] | Rating/info | Rating/info |\n | [Key factor 3] | Rating/info | Rating/info |\n\n ## 💡 Final Recommendation\n Who should choose which, and why.\n\n Keep response under 300 words. Be specific, practical, and helpful.\n \"\"\".formatted(itemA, itemB, itemA, itemB, itemA, itemB);\n }\n\n private String buildSingleAnalysisPrompt(String query) {\n return \"\"\"\n You are an expert analyst. Analyze \"%s\".\n\n FIRST: Detect what category this belongs to and adjust your analysis accordingly.\n\n Provide:\n 1. Quick overview (1-2 sentences)\n 2. Key strengths (3 bullet points)\n 3. Potential drawbacks (2 bullet points)\n 4. Best for: [target audience]\n 5. Pro tip for getting the most value\n\n Keep response under 150 words. Be specific and actionable.\n \"\"\".formatted(query);\n }\n\n private String callGeminiAPI(String prompt) {\n String fullUrl = apiUrl + \"?key=\" + apiKey;\n\n Map<String, Object> requestBody = Map.of(\n \"contents\", List.of(\n Map.of(\"parts\", List.of(\n Map.of(\"text\", prompt)))),\n \"generationConfig\", Map.of(\n \"temperature\", 0.7,\n \"maxOutputTokens\", 800));\n\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> response = restClient.post()\n .uri(fullUrl)\n .contentType(MediaType.APPLICATION_JSON)\n .body(requestBody)\n .retrieve()\n .body(Map.class);\n\n if (response != null && response.containsKey(\"candidates\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> candidates = (List<Map<String, Object>>) response.get(\"candidates\");\n if (!candidates.isEmpty()) {\n @SuppressWarnings(\"unchecked\")\n Map<String, Object> content = (Map<String, Object>) candidates.get(0).get(\"content\");\n if (content != null && content.containsKey(\"parts\")) {\n @SuppressWarnings(\"unchecked\")\n List<Map<String, Object>> parts = (List<Map<String, Object>>) content.get(\"parts\");\n if (!parts.isEmpty()) {\n return (String) parts.get(0).get(\"text\");\n }\n }\n }\n }\n\n throw new RuntimeException(\"Invalid response from Gemini API\");\n }\n\n private String generateFallbackComparison(String itemA, String itemB) {\n return \"\"\"\n ## %s vs %s\n\n Unable to fetch live AI analysis. Here's a basic comparison:\n\n **%s**: A popular choice with its own unique strengths.\n\n **%s**: Another solid option worth considering.\n\n 💡 Tip: Try the comparison again for detailed AI-powered insights.\n \"\"\".formatted(itemA, itemB, itemA, itemB);\n }\n}\n"}}}}