- Migrated 4 components from ECharts to Chart.js: * MonthlyExpenseCard.vue (折线图) * DailyTrendChart.vue (双系列折线图) * ExpenseCategoryCard.vue (环形图) * BudgetChartAnalysis.vue (仪表盘 + 多种图表) - Removed all ECharts imports and environment variable switches - Unified all charts to use BaseChart.vue component - Build verified: pnpm build success ✓ - No echarts imports remaining ✓ Refs: openspec/changes/migrate-remaining-echarts-to-chartjs
95 lines
3.2 KiB
C#
95 lines
3.2 KiB
C#
using Service.AI;
|
||
|
||
namespace Service.IconSearch;
|
||
|
||
public record SearchKeywordSettings
|
||
{
|
||
public string KeywordPromptTemplate { get; init; } =
|
||
"为以下中文分类名称生成3-5个相关的英文搜索关键字,用于搜索图标:{categoryName}。" +
|
||
"输出格式为JSON数组,例如:[\"food\", \"restaurant\", \"dining\"]。";
|
||
}
|
||
|
||
public class SearchKeywordGeneratorService(
|
||
IOpenAiService openAiService,
|
||
IOptions<SearchKeywordSettings> settings,
|
||
ILogger<SearchKeywordGeneratorService> logger
|
||
) : ISearchKeywordGeneratorService
|
||
{
|
||
private readonly SearchKeywordSettings _settings = settings.Value;
|
||
|
||
public async Task<List<string>> GenerateKeywordsAsync(string categoryName)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(categoryName))
|
||
{
|
||
logger.LogWarning("分类名称为空,无法生成搜索关键字");
|
||
return [];
|
||
}
|
||
|
||
try
|
||
{
|
||
var prompt = _settings.KeywordPromptTemplate.Replace("{categoryName}", categoryName);
|
||
var response = await openAiService.ChatAsync(prompt, timeoutSeconds: 15);
|
||
|
||
if (string.IsNullOrEmpty(response))
|
||
{
|
||
logger.LogWarning("AI未返回搜索关键字,分类:{CategoryName}", categoryName);
|
||
return [];
|
||
}
|
||
|
||
var keywords = ParseKeywordsFromResponse(response);
|
||
logger.LogInformation("为分类 {CategoryName} 生成了 {Count} 个搜索关键字:{Keywords}",
|
||
categoryName, keywords.Count, string.Join(", ", keywords));
|
||
|
||
return keywords;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "生成搜索关键字失败,分类:{CategoryName}", categoryName);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
private List<string> ParseKeywordsFromResponse(string response)
|
||
{
|
||
try
|
||
{
|
||
var jsonNode = JsonNode.Parse(response);
|
||
if (jsonNode is JsonArray arrayNode)
|
||
{
|
||
var keywords = new List<string>();
|
||
foreach (var item in arrayNode)
|
||
{
|
||
if (item is JsonValue value && value.TryGetValue(out string keyword))
|
||
{
|
||
keywords.Add(keyword);
|
||
}
|
||
}
|
||
return keywords;
|
||
}
|
||
else if (jsonNode is JsonObject jsonObject)
|
||
{
|
||
if (jsonObject.TryGetPropertyValue("keywords", out var keywordsNode) && keywordsNode is JsonArray arrayNode2)
|
||
{
|
||
var keywords = new List<string>();
|
||
foreach (var item in arrayNode2)
|
||
{
|
||
if (item is JsonValue value && value.TryGetValue(out string keyword))
|
||
{
|
||
keywords.Add(keyword);
|
||
}
|
||
}
|
||
return keywords;
|
||
}
|
||
}
|
||
|
||
logger.LogWarning("无法解析AI响应为关键字数组:{Response}", response);
|
||
return [];
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "解析AI响应失败:{Response}", response);
|
||
return [];
|
||
}
|
||
}
|
||
}
|