From 0ae47e9427392035c670bd0e0e3d454ec9345ec7 Mon Sep 17 00:00:00 2001 From: Peter Foster Date: Wed, 15 Apr 2026 09:27:43 +0100 Subject: [PATCH] feat: add SuggestAspectsAsync to AiAssistantService --- .../Services/AiAssistantService.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/EbayListingTool/Services/AiAssistantService.cs b/EbayListingTool/Services/AiAssistantService.cs index 86e4cb0..519ffee 100644 --- a/EbayListingTool/Services/AiAssistantService.cs +++ b/EbayListingTool/Services/AiAssistantService.cs @@ -284,6 +284,59 @@ public class AiAssistantService } } + public async Task> SuggestAspectsAsync( + string title, string description, List aspects) + { + if (aspects.Count == 0) return new(); + + var aspectLines = aspects.Select(a => + { + var line = $"- {a.Name} (required: {a.IsRequired})"; + if (!a.IsFreeText && a.AllowedValues.Count > 0) + line += $" — allowed values: {string.Join(", ", a.AllowedValues.Take(10))}"; + return line; + }); + + var prompt = + "You are an eBay UK listing expert. Based on this listing, suggest values for the " + + "eBay item specifics (aspects) listed below.\n\n" + + $"Title: {title}\n" + + $"Description: {description}\n\n" + + "Aspects needed:\n" + + string.Join("\n", aspectLines) + "\n\n" + + "Rules:\n" + + "- For aspects with 'allowed values', you MUST use one of those exact values.\n" + + "- If unsure about an aspect, omit it from the response rather than guess.\n" + + "- Return ONLY valid JSON — no markdown, no explanation:\n" + + "{\n" + + " \"Brand\": \"Apple\",\n" + + " \"Model\": \"iPhone 14\"\n" + + "}\n" + + "Use the exact aspect name as the key."; + + 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.Properties() + .Where(p => !string.IsNullOrEmpty(p.Value?.ToString())) + .ToDictionary(p => p.Name, p => p.Value!.ToString()); + } + catch + { + return new(); + } + } + private async Task CallWithVisionAsync(IEnumerable imageDataUrls, string textPrompt) { if (string.IsNullOrEmpty(_apiKey))