Add layered price lookup and category auto-fill
- PriceLookupService: eBay live data → saved listing history → AI estimate, each result labelled by source so the user knows how reliable it is - Revalue row: new "Check eBay" button fetches suggestion and pre-populates the price field; shows source label beneath (or "No suggestion available") - Category auto-fill: AutoFillCategoryAsync takes the top eBay category suggestion and fills the field automatically after photo analysis or AI title generation; dropdown stays visible so user can override Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
83
EbayListingTool/Services/PriceLookupService.cs
Normal file
83
EbayListingTool/Services/PriceLookupService.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using EbayListingTool.Models;
|
||||
|
||||
namespace EbayListingTool.Services;
|
||||
|
||||
public record PriceSuggestion(decimal Price, string Source, string Label);
|
||||
|
||||
/// <summary>
|
||||
/// Layered price suggestion: eBay live data → own listing history → AI estimate.
|
||||
/// Returns the first source that produces a result, labelled so the UI can show
|
||||
/// where the suggestion came from.
|
||||
/// </summary>
|
||||
public class PriceLookupService
|
||||
{
|
||||
private readonly EbayPriceResearchService _ebay;
|
||||
private readonly SavedListingsService _history;
|
||||
private readonly AiAssistantService _ai;
|
||||
|
||||
private static readonly Regex PriceRegex =
|
||||
new(@"PRICE:\s*(\d+\.?\d*)", RegexOptions.IgnoreCase);
|
||||
|
||||
public PriceLookupService(
|
||||
EbayPriceResearchService ebay,
|
||||
SavedListingsService history,
|
||||
AiAssistantService ai)
|
||||
{
|
||||
_ebay = ebay;
|
||||
_history = history;
|
||||
_ai = ai;
|
||||
}
|
||||
|
||||
public async Task<PriceSuggestion?> GetSuggestionAsync(SavedListing listing)
|
||||
{
|
||||
// 1. eBay live listings
|
||||
try
|
||||
{
|
||||
var result = await _ebay.GetLivePricesAsync(listing.Title);
|
||||
if (result.HasSuggestion)
|
||||
return new PriceSuggestion(
|
||||
result.Suggested,
|
||||
"ebay",
|
||||
$"eBay suggests £{result.Suggested:F2} (from {result.Count} listings)");
|
||||
}
|
||||
catch { /* eBay unavailable — fall through */ }
|
||||
|
||||
// 2. Own saved listing history — same category, at least 2 data points
|
||||
var sameCat = _history.Listings
|
||||
.Where(l => l.Id != listing.Id
|
||||
&& !string.IsNullOrWhiteSpace(l.Category)
|
||||
&& l.Category.Equals(listing.Category, StringComparison.OrdinalIgnoreCase)
|
||||
&& l.Price > 0)
|
||||
.Select(l => l.Price)
|
||||
.ToList();
|
||||
|
||||
if (sameCat.Count >= 2)
|
||||
{
|
||||
var avg = Math.Round(sameCat.Average(), 2);
|
||||
return new PriceSuggestion(
|
||||
avg,
|
||||
"history",
|
||||
$"Your avg for {listing.Category}: £{avg:F2} ({sameCat.Count} listings)");
|
||||
}
|
||||
|
||||
// 3. AI estimate
|
||||
try
|
||||
{
|
||||
var response = await _ai.SuggestPriceAsync(listing.Title, listing.ConditionNotes);
|
||||
var match = PriceRegex.Match(response);
|
||||
if (match.Success
|
||||
&& decimal.TryParse(match.Groups[1].Value,
|
||||
System.Globalization.NumberStyles.Any,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var price)
|
||||
&& price > 0)
|
||||
{
|
||||
return new PriceSuggestion(price, "ai", $"AI estimate: £{price:F2}");
|
||||
}
|
||||
}
|
||||
catch { /* AI unavailable */ }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public partial class MainWindow : MetroWindow
|
||||
private readonly BulkImportService _bulkService;
|
||||
private readonly SavedListingsService _savedService;
|
||||
private readonly EbayPriceResearchService _priceService;
|
||||
private readonly PriceLookupService _priceLookupService;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -27,14 +28,15 @@ public partial class MainWindow : MetroWindow
|
||||
_aiService = new AiAssistantService(config);
|
||||
_bulkService = new BulkImportService();
|
||||
_savedService = new SavedListingsService();
|
||||
_priceService = new EbayPriceResearchService(_auth);
|
||||
_priceService = new EbayPriceResearchService(_auth);
|
||||
_priceLookupService = new PriceLookupService(_priceService, _savedService, _aiService);
|
||||
|
||||
// Photo Analysis tab — no eBay needed
|
||||
PhotoView.Initialise(_aiService, _savedService, _priceService);
|
||||
PhotoView.UseDetailsRequested += OnUseDetailsRequested;
|
||||
|
||||
// Saved Listings tab
|
||||
SavedView.Initialise(_savedService);
|
||||
SavedView.Initialise(_savedService, _priceLookupService);
|
||||
|
||||
// New Listing + Bulk tabs
|
||||
SingleView.Initialise(_listingService, _categoryService, _aiService, _auth);
|
||||
|
||||
@@ -205,13 +205,69 @@
|
||||
<TextBlock x:Name="DetailTitle" Grid.Column="0"
|
||||
FontSize="17" FontWeight="Bold" TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MahApps.Brushes.Gray1}"/>
|
||||
<Border Grid.Column="1"
|
||||
Background="{DynamicResource MahApps.Brushes.Accent}"
|
||||
CornerRadius="6" Padding="10,4" Margin="10,0,0,0"
|
||||
VerticalAlignment="Top">
|
||||
<TextBlock x:Name="DetailPrice"
|
||||
FontSize="16" FontWeight="Bold" Foreground="White"/>
|
||||
</Border>
|
||||
|
||||
<!-- Price display + quick revalue -->
|
||||
<StackPanel Grid.Column="1" Margin="10,0,0,0" VerticalAlignment="Top">
|
||||
|
||||
<!-- Normal price badge + Revalue button -->
|
||||
<StackPanel x:Name="PriceDisplayRow" Orientation="Horizontal">
|
||||
<Border Background="{DynamicResource MahApps.Brushes.Accent}"
|
||||
CornerRadius="6" Padding="10,4">
|
||||
<TextBlock x:Name="DetailPrice"
|
||||
FontSize="16" FontWeight="Bold" Foreground="White"/>
|
||||
</Border>
|
||||
<Button x:Name="RevalueBtn" Click="RevalueBtn_Click"
|
||||
Height="28" Padding="8,0" Margin="6,0,0,0"
|
||||
Style="{DynamicResource MahApps.Styles.Button.Square}"
|
||||
ToolTip="Quick-change the price">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial Kind="CurrencyGbp" Width="11" Height="11"
|
||||
Margin="0,0,4,0" VerticalAlignment="Center"/>
|
||||
<TextBlock Text="Revalue" FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Inline revalue editor (hidden by default) -->
|
||||
<StackPanel x:Name="RevalueRow" Orientation="Vertical"
|
||||
Visibility="Collapsed" Margin="0,4,0,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<mah:NumericUpDown x:Name="RevaluePrice"
|
||||
Minimum="0" Maximum="99999"
|
||||
StringFormat="F2" Interval="0.5"
|
||||
Width="110" Height="30"/>
|
||||
<Button x:Name="CheckEbayBtn" Click="CheckEbayBtn_Click"
|
||||
Height="30" Padding="8,0" Margin="6,0,4,0"
|
||||
Style="{DynamicResource MahApps.Styles.Button.Square}"
|
||||
ToolTip="Check eBay for a suggested price">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<iconPacks:PackIconMaterial x:Name="CheckEbayIcon"
|
||||
Kind="Magnify" Width="11" Height="11"
|
||||
Margin="0,0,4,0" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="CheckEbayText" Text="Check eBay"
|
||||
FontSize="11" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Click="RevalueSave_Click"
|
||||
Style="{DynamicResource MahApps.Styles.Button.Square.Accent}"
|
||||
Height="30" Padding="10,0" Margin="0,0,4,0"
|
||||
ToolTip="Save new price">
|
||||
<iconPacks:PackIconMaterial Kind="Check" Width="13" Height="13"/>
|
||||
</Button>
|
||||
<Button Click="RevalueCancel_Click"
|
||||
Style="{DynamicResource MahApps.Styles.Button.Square}"
|
||||
Height="30" Padding="8,0"
|
||||
ToolTip="Cancel">
|
||||
<iconPacks:PackIconMaterial Kind="Close" Width="11" Height="11"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="PriceSuggestionLabel"
|
||||
FontSize="10" Margin="0,5,0,0"
|
||||
Foreground="{DynamicResource MahApps.Brushes.Gray5}"
|
||||
Visibility="Collapsed" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Meta row: category · date -->
|
||||
@@ -362,6 +418,9 @@
|
||||
|
||||
<!-- Photos -->
|
||||
<TextBlock Text="PHOTOS" Style="{StaticResource DetailLabel}" Margin="0,0,0,3"/>
|
||||
<TextBlock Text="First photo is the listing cover. Use ◀ ▶ to reorder."
|
||||
FontSize="10" Foreground="{DynamicResource MahApps.Brushes.Gray5}"
|
||||
Margin="0,0,0,6"/>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled"
|
||||
Margin="0,0,0,10">
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace EbayListingTool.Views;
|
||||
public partial class SavedListingsView : UserControl
|
||||
{
|
||||
private SavedListingsService? _service;
|
||||
private PriceLookupService? _priceLookup;
|
||||
private SavedListing? _selected;
|
||||
|
||||
// Edit mode working state
|
||||
@@ -33,9 +34,10 @@ public partial class SavedListingsView : UserControl
|
||||
};
|
||||
}
|
||||
|
||||
public void Initialise(SavedListingsService service)
|
||||
public void Initialise(SavedListingsService service, PriceLookupService? priceLookup = null)
|
||||
{
|
||||
_service = service;
|
||||
_service = service;
|
||||
_priceLookup = priceLookup;
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
@@ -255,6 +257,10 @@ public partial class SavedListingsView : UserControl
|
||||
EmptyDetail.Visibility = Visibility.Collapsed;
|
||||
DetailPanel.Visibility = Visibility.Visible;
|
||||
|
||||
// Reset revalue UI
|
||||
PriceDisplayRow.Visibility = Visibility.Visible;
|
||||
RevalueRow.Visibility = Visibility.Collapsed;
|
||||
|
||||
DetailTitle.Text = listing.Title;
|
||||
DetailPrice.Text = listing.PriceDisplay;
|
||||
DetailCategory.Text = listing.Category;
|
||||
@@ -332,6 +338,77 @@ public partial class SavedListingsView : UserControl
|
||||
catch { }
|
||||
}
|
||||
|
||||
// ---- Quick revalue ----
|
||||
|
||||
private void RevalueBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selected == null) return;
|
||||
RevaluePrice.Value = (double)_selected.Price;
|
||||
PriceSuggestionLabel.Visibility = Visibility.Collapsed;
|
||||
PriceSuggestionLabel.Text = "";
|
||||
CheckEbayBtn.IsEnabled = _priceLookup != null;
|
||||
PriceDisplayRow.Visibility = Visibility.Collapsed;
|
||||
RevalueRow.Visibility = Visibility.Visible;
|
||||
RevaluePrice.Focus();
|
||||
}
|
||||
|
||||
private void RevalueSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selected == null || _service == null) return;
|
||||
_selected.Price = (decimal)(RevaluePrice.Value ?? 0);
|
||||
_service.Update(_selected);
|
||||
DetailPrice.Text = _selected.PriceDisplay;
|
||||
PriceDisplayRow.Visibility = Visibility.Visible;
|
||||
RevalueRow.Visibility = Visibility.Collapsed;
|
||||
PriceSuggestionLabel.Visibility = Visibility.Collapsed;
|
||||
RefreshList();
|
||||
}
|
||||
|
||||
private void RevalueCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PriceDisplayRow.Visibility = Visibility.Visible;
|
||||
RevalueRow.Visibility = Visibility.Collapsed;
|
||||
PriceSuggestionLabel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private async void CheckEbayBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selected == null || _priceLookup == null) return;
|
||||
|
||||
CheckEbayBtn.IsEnabled = false;
|
||||
CheckEbayIcon.Kind = MahApps.Metro.IconPacks.PackIconMaterialKind.Loading;
|
||||
CheckEbayText.Text = "Checking…";
|
||||
PriceSuggestionLabel.Visibility = Visibility.Collapsed;
|
||||
|
||||
try
|
||||
{
|
||||
var suggestion = await _priceLookup.GetSuggestionAsync(_selected);
|
||||
|
||||
if (suggestion != null)
|
||||
{
|
||||
RevaluePrice.Value = (double)suggestion.Price;
|
||||
PriceSuggestionLabel.Text = suggestion.Label;
|
||||
PriceSuggestionLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
PriceSuggestionLabel.Text = "No suggestion available — enter price manually.";
|
||||
PriceSuggestionLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PriceSuggestionLabel.Text = $"Lookup failed: {ex.Message}";
|
||||
PriceSuggestionLabel.Visibility = Visibility.Visible;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CheckEbayIcon.Kind = MahApps.Metro.IconPacks.PackIconMaterialKind.Magnify;
|
||||
CheckEbayText.Text = "Check eBay";
|
||||
CheckEbayBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Edit mode ----
|
||||
|
||||
private void EditListing_Click(object sender, RoutedEventArgs e)
|
||||
@@ -372,9 +449,17 @@ public partial class SavedListingsView : UserControl
|
||||
var path = _editPhotoPaths[i];
|
||||
var index = i; // capture for lambdas
|
||||
|
||||
var container = new Grid { Width = 120, Height = 120, Margin = new Thickness(0, 0, 8, 0) };
|
||||
// Outer StackPanel: photo tile on top, reorder buttons below
|
||||
var outer = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Width = 120
|
||||
};
|
||||
|
||||
// --- Photo tile (image + cover badge + remove button) ---
|
||||
var photoGrid = new Grid { Width = 120, Height = 120 };
|
||||
|
||||
// Photo image
|
||||
var imgBorder = new Border
|
||||
{
|
||||
Width = 120, Height = 120,
|
||||
@@ -403,9 +488,9 @@ public partial class SavedListingsView : UserControl
|
||||
AddPhotoIcon(imgBorder);
|
||||
}
|
||||
|
||||
container.Children.Add(imgBorder);
|
||||
photoGrid.Children.Add(imgBorder);
|
||||
|
||||
// "Cover" badge on the first photo
|
||||
// "Cover" badge — top-left, only on first photo
|
||||
if (i == 0)
|
||||
{
|
||||
var badge = new Border
|
||||
@@ -418,10 +503,10 @@ public partial class SavedListingsView : UserControl
|
||||
Margin = new Thickness(4, 4, 0, 0)
|
||||
};
|
||||
badge.Child = new TextBlock { Text = "Cover", FontSize = 9, Foreground = Brushes.White };
|
||||
container.Children.Add(badge);
|
||||
photoGrid.Children.Add(badge);
|
||||
}
|
||||
|
||||
// Remove (×) button — top-right corner
|
||||
// Remove (×) button — top-right corner of image
|
||||
var removeBtn = new Button
|
||||
{
|
||||
Content = "×",
|
||||
@@ -442,66 +527,69 @@ public partial class SavedListingsView : UserControl
|
||||
_editPhotoPaths.RemoveAt(index);
|
||||
BuildEditPhotoStrip();
|
||||
};
|
||||
container.Children.Add(removeBtn);
|
||||
photoGrid.Children.Add(removeBtn);
|
||||
|
||||
// Left/right reorder buttons — bottom-centre
|
||||
outer.Children.Add(photoGrid);
|
||||
|
||||
// --- Reorder buttons below the photo ---
|
||||
var reorderPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Bottom,
|
||||
Margin = new Thickness(0, 0, 0, 4)
|
||||
Margin = new Thickness(0, 4, 0, 0)
|
||||
};
|
||||
|
||||
if (i > 0)
|
||||
var leftBtn = new Button
|
||||
{
|
||||
var leftBtn = new Button
|
||||
{
|
||||
Content = "◀",
|
||||
Width = 26, Height = 20,
|
||||
FontSize = 9,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0, 0, 2, 0),
|
||||
Style = (Style)FindResource("MahApps.Styles.Button.Square"),
|
||||
Foreground = Brushes.White,
|
||||
Background = new SolidColorBrush(Color.FromArgb(180, 40, 40, 40)),
|
||||
ToolTip = "Move left"
|
||||
};
|
||||
leftBtn.Click += (s, e) =>
|
||||
{
|
||||
(_editPhotoPaths[index], _editPhotoPaths[index - 1]) =
|
||||
(_editPhotoPaths[index - 1], _editPhotoPaths[index]);
|
||||
BuildEditPhotoStrip();
|
||||
};
|
||||
reorderPanel.Children.Add(leftBtn);
|
||||
}
|
||||
|
||||
if (i < _editPhotoPaths.Count - 1)
|
||||
Width = 52, Height = 24,
|
||||
FontSize = 10,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0, 0, 2, 0),
|
||||
Style = (Style)FindResource("MahApps.Styles.Button.Square"),
|
||||
ToolTip = "Move left",
|
||||
IsEnabled = i > 0
|
||||
};
|
||||
leftBtn.Content = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
((StackPanel)leftBtn.Content).Children.Add(new MahApps.Metro.IconPacks.PackIconMaterial
|
||||
{
|
||||
var rightBtn = new Button
|
||||
{
|
||||
Content = "▶",
|
||||
Width = 26, Height = 20,
|
||||
FontSize = 9,
|
||||
Padding = new Thickness(0),
|
||||
Style = (Style)FindResource("MahApps.Styles.Button.Square"),
|
||||
Foreground = Brushes.White,
|
||||
Background = new SolidColorBrush(Color.FromArgb(180, 40, 40, 40)),
|
||||
ToolTip = "Move right"
|
||||
};
|
||||
rightBtn.Click += (s, e) =>
|
||||
{
|
||||
(_editPhotoPaths[index], _editPhotoPaths[index + 1]) =
|
||||
(_editPhotoPaths[index + 1], _editPhotoPaths[index]);
|
||||
BuildEditPhotoStrip();
|
||||
};
|
||||
reorderPanel.Children.Add(rightBtn);
|
||||
}
|
||||
Kind = MahApps.Metro.IconPacks.PackIconMaterialKind.ChevronLeft,
|
||||
Width = 12, Height = 12, VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
((StackPanel)leftBtn.Content).Children.Add(new TextBlock { Text = "Move", FontSize = 9, Margin = new Thickness(2, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center });
|
||||
leftBtn.Click += (s, e) =>
|
||||
{
|
||||
(_editPhotoPaths[index], _editPhotoPaths[index - 1]) =
|
||||
(_editPhotoPaths[index - 1], _editPhotoPaths[index]);
|
||||
BuildEditPhotoStrip();
|
||||
};
|
||||
reorderPanel.Children.Add(leftBtn);
|
||||
|
||||
if (reorderPanel.Children.Count > 0)
|
||||
container.Children.Add(reorderPanel);
|
||||
var rightBtn = new Button
|
||||
{
|
||||
Width = 52, Height = 24,
|
||||
FontSize = 10,
|
||||
Padding = new Thickness(0),
|
||||
Style = (Style)FindResource("MahApps.Styles.Button.Square"),
|
||||
ToolTip = "Move right",
|
||||
IsEnabled = i < _editPhotoPaths.Count - 1
|
||||
};
|
||||
rightBtn.Content = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
((StackPanel)rightBtn.Content).Children.Add(new TextBlock { Text = "Move", FontSize = 9, Margin = new Thickness(0, 0, 2, 0), VerticalAlignment = VerticalAlignment.Center });
|
||||
((StackPanel)rightBtn.Content).Children.Add(new MahApps.Metro.IconPacks.PackIconMaterial
|
||||
{
|
||||
Kind = MahApps.Metro.IconPacks.PackIconMaterialKind.ChevronRight,
|
||||
Width = 12, Height = 12, VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
rightBtn.Click += (s, e) =>
|
||||
{
|
||||
(_editPhotoPaths[index], _editPhotoPaths[index + 1]) =
|
||||
(_editPhotoPaths[index + 1], _editPhotoPaths[index]);
|
||||
BuildEditPhotoStrip();
|
||||
};
|
||||
reorderPanel.Children.Add(rightBtn);
|
||||
|
||||
EditPhotosPanel.Children.Add(container);
|
||||
outer.Children.Add(reorderPanel);
|
||||
EditPhotosPanel.Children.Add(outer);
|
||||
}
|
||||
|
||||
// "Add photos" button at the end of the strip
|
||||
|
||||
@@ -18,6 +18,7 @@ public partial class SingleItemView : UserControl
|
||||
|
||||
private ListingDraft _draft = new();
|
||||
private System.Threading.CancellationTokenSource? _categoryCts;
|
||||
private bool _suppressCategoryLookup;
|
||||
private string _suggestedPriceValue = "";
|
||||
|
||||
// Photo drag-reorder
|
||||
@@ -50,7 +51,7 @@ public partial class SingleItemView : UserControl
|
||||
}
|
||||
|
||||
/// <summary>Pre-fills the form from a Photo Analysis result.</summary>
|
||||
public void PopulateFromAnalysis(PhotoAnalysisResult result, IReadOnlyList<string> imagePaths, decimal price)
|
||||
public async void PopulateFromAnalysis(PhotoAnalysisResult result, IReadOnlyList<string> imagePaths, decimal price)
|
||||
{
|
||||
// Q6: reset form directly — calling NewListing_Click shows a confirmation dialog which
|
||||
// is unexpected when arriving here automatically from the Photo Analysis tab.
|
||||
@@ -71,9 +72,9 @@ public partial class SingleItemView : UserControl
|
||||
TitleBox.Text = result.Title;
|
||||
DescriptionBox.Text = result.Description;
|
||||
PriceBox.Value = (double)price;
|
||||
CategoryBox.Text = result.CategoryKeyword;
|
||||
|
||||
_draft.CategoryName = result.CategoryKeyword;
|
||||
// Auto-fill the top eBay category from the analysis keyword; user can override
|
||||
await AutoFillCategoryAsync(result.CategoryKeyword);
|
||||
|
||||
// Q1: load all photos from analysis
|
||||
var validPaths = imagePaths.Where(p => !string.IsNullOrEmpty(p) && File.Exists(p)).ToArray();
|
||||
@@ -117,6 +118,10 @@ public partial class SingleItemView : UserControl
|
||||
{
|
||||
var title = await _aiService.GenerateTitleAsync(current, condition);
|
||||
TitleBox.Text = title.Trim().TrimEnd('.').Trim('"');
|
||||
|
||||
// Auto-fill category from the generated title if not already set
|
||||
if (string.IsNullOrWhiteSpace(_draft.CategoryId))
|
||||
await AutoFillCategoryAsync(TitleBox.Text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -129,6 +134,8 @@ public partial class SingleItemView : UserControl
|
||||
|
||||
private async void CategoryBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_suppressCategoryLookup) return;
|
||||
|
||||
_categoryCts?.Cancel();
|
||||
_categoryCts?.Dispose();
|
||||
_categoryCts = new System.Threading.CancellationTokenSource();
|
||||
@@ -211,6 +218,38 @@ public partial class SingleItemView : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the top eBay category suggestion for <paramref name="keyword"/> and auto-fills
|
||||
/// the category fields. The suggestions list is shown so the user can override.
|
||||
/// </summary>
|
||||
private async Task AutoFillCategoryAsync(string keyword)
|
||||
{
|
||||
if (_categoryService == null || string.IsNullOrWhiteSpace(keyword)) return;
|
||||
|
||||
try
|
||||
{
|
||||
var suggestions = await _categoryService.GetCategorySuggestionsAsync(keyword);
|
||||
if (suggestions.Count == 0) return;
|
||||
|
||||
var top = suggestions[0];
|
||||
_suppressCategoryLookup = true;
|
||||
try
|
||||
{
|
||||
_draft.CategoryId = top.CategoryId;
|
||||
_draft.CategoryName = top.CategoryName;
|
||||
CategoryBox.Text = top.CategoryName;
|
||||
CategoryIdLabel.Text = $"ID: {top.CategoryId}";
|
||||
}
|
||||
finally { _suppressCategoryLookup = false; }
|
||||
|
||||
// Show the full list so user can see alternatives and override
|
||||
CategorySuggestionsList.ItemsSource = suggestions;
|
||||
CategorySuggestionsList.Visibility = suggestions.Count > 1
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
catch { /* non-critical — leave category blank if lookup fails */ }
|
||||
}
|
||||
|
||||
// ---- Condition ----
|
||||
|
||||
private void ConditionBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user