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;