From c02c69aaa6e971d3a320558bacd8398f98917908 Mon Sep 17 00:00:00 2001 From: Peter Foster Date: Mon, 13 Apr 2026 17:53:06 +0100 Subject: [PATCH] Add AI corrections/refinement to Photo Analysis view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Corrections box and "Refine with AI" button below the description. User types what the AI got wrong (e.g. "earrings are white gold with diamonds, not silver and zirconium") and clicks Refine — the AI rewrites the title, description and price while keeping all other analysis fields intact. AiAssistantService.RefineWithCorrectionsAsync() sends the current listing text + corrections as a single text prompt and parses the JSON response back into the title/description/price fields. Co-Authored-By: Claude Sonnet 4.6 --- .../Services/AiAssistantService.cs | 48 ++++++++++++++++ EbayListingTool/Views/PhotoAnalysisView.xaml | 41 +++++++++++++ .../Views/PhotoAnalysisView.xaml.cs | 57 +++++++++++++++++++ 3 files changed, 146 insertions(+) diff --git a/EbayListingTool/Services/AiAssistantService.cs b/EbayListingTool/Services/AiAssistantService.cs index aa0466d..f697734 100644 --- a/EbayListingTool/Services/AiAssistantService.cs +++ b/EbayListingTool/Services/AiAssistantService.cs @@ -134,6 +134,54 @@ public class AiAssistantService } } + /// + /// Takes an existing AI-generated listing and rewrites title, description and price + /// to incorporate user corrections (e.g. "earrings are white gold, not silver"). + /// Returns the updated fields; other PhotoAnalysisResult fields are unchanged. + /// + public async Task<(string Title, string Description, decimal Price, string PriceReasoning)> + RefineWithCorrectionsAsync(string title, string description, decimal price, string corrections) + { + var prompt = + "You are an expert eBay UK seller. I have a listing with incorrect details that need fixing.\n\n" + + $"Current title: {title}\n" + + $"Current description:\n{description}\n\n" + + $"Current price: £{price:F2}\n\n" + + $"CORRECTIONS FROM SELLER: {corrections}\n\n" + + "Rewrite the listing incorporating these corrections. " + + "Return ONLY valid JSON — no markdown, no explanation:\n" + + "{\n" + + " \"title\": \"corrected eBay UK title, max 80 chars, keyword-rich\",\n" + + " \"description\": \"corrected full plain-text eBay UK description\",\n" + + " \"price_suggested\": 0.00,\n" + + " \"price_reasoning\": \"one sentence why this price\"\n" + + "}"; + + var json = await CallAsync(prompt); + + try + { + JObject obj; + try { obj = JObject.Parse(json.Trim()); } + catch (JsonReaderException) + { + var m = System.Text.RegularExpressions.Regex.Match(json, @"```(?:json)?\s*(\{[\s\S]*?\})\s*```"); + obj = JObject.Parse(m.Success ? m.Groups[1].Value : json.Trim()); + } + + return ( + obj["title"]?.ToString() ?? title, + obj["description"]?.ToString() ?? description, + obj["price_suggested"]?.Value() ?? price, + obj["price_reasoning"]?.ToString() ?? "" + ); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Could not parse AI refinement response: {ex.Message}"); + } + } + /// Convenience wrapper — analyses a single photo. public Task AnalyseItemFromPhotoAsync(string imagePath) => AnalyseItemFromPhotosAsync(new[] { imagePath }); diff --git a/EbayListingTool/Views/PhotoAnalysisView.xaml b/EbayListingTool/Views/PhotoAnalysisView.xaml index 6fb16d2..af741cc 100644 --- a/EbayListingTool/Views/PhotoAnalysisView.xaml +++ b/EbayListingTool/Views/PhotoAnalysisView.xaml @@ -554,6 +554,47 @@ + + + + + + + + + + + + + diff --git a/EbayListingTool/Views/PhotoAnalysisView.xaml.cs b/EbayListingTool/Views/PhotoAnalysisView.xaml.cs index f55a34b..ee947a4 100644 --- a/EbayListingTool/Views/PhotoAnalysisView.xaml.cs +++ b/EbayListingTool/Views/PhotoAnalysisView.xaml.cs @@ -220,6 +220,63 @@ public partial class PhotoAnalysisView : UserControl private void ReAnalyse_Click(object sender, RoutedEventArgs e) => Analyse_Click(sender, e); + private async void Refine_Click(object sender, RoutedEventArgs e) + { + if (_aiService == null || _lastResult == null) return; + + var corrections = CorrectionsBox.Text.Trim(); + if (string.IsNullOrEmpty(corrections)) + { + CorrectionsBox.Focus(); + return; + } + + var title = TitleBox.Text; + var description = DescriptionBox.Text; + var price = (decimal)(PriceOverride.Value ?? (double)_lastResult.PriceSuggested); + + SetRefining(true); + try + { + var (newTitle, newDesc, newPrice, newReasoning) = + await _aiService.RefineWithCorrectionsAsync(title, description, price, corrections); + + TitleBox.Text = newTitle; + DescriptionBox.Text = newDesc; + PriceOverride.Value = (double)newPrice; + + _lastResult.Title = newTitle; + _lastResult.Description = newDesc; + _lastResult.PriceSuggested = newPrice; + + if (!string.IsNullOrWhiteSpace(newReasoning)) + { + PriceReasoningText.Text = newReasoning; + PriceReasoningText.Visibility = Visibility.Visible; + } + + // Clear the corrections box now they're applied + CorrectionsBox.Text = ""; + } + catch (Exception ex) + { + MessageBox.Show($"Refinement failed:\n\n{ex.Message}", "AI Error", + MessageBoxButton.OK, MessageBoxImage.Warning); + } + finally + { + SetRefining(false); + } + } + + private void SetRefining(bool busy) + { + RefineBtn.IsEnabled = !busy; + RefineIcon.Visibility = busy ? Visibility.Collapsed : Visibility.Visible; + RefineSpinner.Visibility = busy ? Visibility.Visible : Visibility.Collapsed; + RefineBtnText.Text = busy ? "Refining…" : "Refine with AI"; + } + private void ShowResults(PhotoAnalysisResult r) { IdlePanel.Visibility = Visibility.Collapsed;