Apply second code review fixes: robustness and correctness

- AiAssistantService: 90s HTTP timeout; RefineWithCorrections uses 29.99 example
  price and explicit "Never return 0" instruction; treat returned 0 as missing and
  keep original price instead
- EbayAuthService: shared static HttpClient for all auth/token calls; SemaphoreSlim
  double-checked locking on GetAppTokenAsync to prevent concurrent token fetches
- EbayPriceResearchService: 15s timeout; require ≥5 samples before suggesting;
  trim top/bottom 10% outliers; BEST_MATCH sort avoids spam/broken listing bias
- PhotoAnalysisView: spinner-show inside try (Issue 1); decimal→double casts use
  Math.Round to avoid 19.99→19.989... drift (Issue 6); Dispatcher.CheckAccess guard
  for off-thread callers (Issue 7); save toast always restarts instead of silently
  dropping rapid saves, eliminating any stuck-flag path (Issue 8)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Foster
2026-04-13 18:14:00 +01:00
parent 551bed6814
commit 426089fb3e
4 changed files with 122 additions and 70 deletions

View File

@@ -16,7 +16,7 @@ public class AiAssistantService
{
private readonly string _apiKey;
private readonly string _model;
private static readonly HttpClient _http = new();
private static readonly HttpClient _http = new() { Timeout = TimeSpan.FromSeconds(90) };
private const string ApiUrl = "https://openrouter.ai/api/v1/chat/completions";
@@ -142,20 +142,25 @@ public class AiAssistantService
public async Task<(string Title, string Description, decimal Price, string PriceReasoning)>
RefineWithCorrectionsAsync(string title, string description, decimal price, string corrections)
{
var priceContext = price > 0
? $"Current price: £{price:F2}\n\n"
: "";
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" +
priceContext +
$"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_suggested\": 29.99,\n" +
" \"price_reasoning\": \"one sentence why this price\"\n" +
"}";
"}\n\n" +
"price_suggested MUST be a realistic GBP amount greater than 0. Never return 0.";
var json = await CallAsync(prompt);
@@ -169,10 +174,11 @@ public class AiAssistantService
obj = JObject.Parse(m.Success ? m.Groups[1].Value : json.Trim());
}
var parsedPrice = obj["price_suggested"]?.Value<decimal>() ?? 0;
return (
obj["title"]?.ToString() ?? title,
obj["description"]?.ToString() ?? description,
obj["price_suggested"]?.Value<decimal>() ?? price,
obj["title"]?.ToString() ?? title,
obj["description"]?.ToString() ?? description,
parsedPrice > 0 ? parsedPrice : price, // treat 0 as "not provided", keep original
obj["price_reasoning"]?.ToString() ?? ""
);
}