fix
This commit is contained in:
@@ -41,7 +41,8 @@ public class SmartHandleService(
|
||||
ILogger<SmartHandleService> logger,
|
||||
ITransactionCategoryRepository categoryRepository,
|
||||
IOpenAiService openAiService,
|
||||
IConfigService configService
|
||||
IConfigService configService,
|
||||
IClassificationIconPromptProvider iconPromptProvider
|
||||
) : ISmartHandleService
|
||||
{
|
||||
public async Task SmartClassifyAsync(long[] transactionIds, Action<(string, string)> chunkAction)
|
||||
@@ -541,6 +542,32 @@ public class SmartHandleService(
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理 AI 响应中的 markdown 代码块标记
|
||||
/// </summary>
|
||||
private static string CleanMarkdownCodeBlock(string response)
|
||||
{
|
||||
var cleaned = response?.Trim() ?? string.Empty;
|
||||
if (cleaned.StartsWith("```"))
|
||||
{
|
||||
// 移除开头的 ```json 或 ```
|
||||
var firstNewLine = cleaned.IndexOf('\n');
|
||||
if (firstNewLine > 0)
|
||||
{
|
||||
cleaned = cleaned.Substring(firstNewLine + 1);
|
||||
}
|
||||
|
||||
// 移除结尾的 ```
|
||||
if (cleaned.EndsWith("```"))
|
||||
{
|
||||
cleaned = cleaned.Substring(0, cleaned.Length - 3);
|
||||
}
|
||||
|
||||
cleaned = cleaned.Trim();
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private async Task<string> GetCategoryInfoAsync()
|
||||
{
|
||||
// 获取所有分类
|
||||
@@ -649,46 +676,9 @@ public class SmartHandleService(
|
||||
{
|
||||
logger.LogInformation("正在为分类 {CategoryName} 生成 {IconCount} 个图标", categoryName, iconCount);
|
||||
|
||||
var typeText = categoryType == TransactionType.Expense ? "支出" : "收入";
|
||||
var systemPrompt = iconPromptProvider.GetPrompt(categoryName, categoryType);
|
||||
|
||||
var systemPrompt = """
|
||||
你是一个专业的 SVG 图标设计师,擅长创作精美、富有表现力的图标。
|
||||
请根据分类名称和类型,生成 5 个风格迥异、视觉效果突出的 SVG 图标。
|
||||
|
||||
设计要求:
|
||||
1. 尺寸:24x24,viewBox="0 0 24 24"
|
||||
2. 色彩:使用丰富的渐变色和多色搭配,让图标更有吸引力和辨识度
|
||||
- 可以使用 <linearGradient> 或 <radialGradient> 创建渐变效果
|
||||
- 不同元素使用不同颜色,增加层次感
|
||||
- 根据分类含义选择合适的配色方案(如餐饮用暖色系、交通用蓝色系等)
|
||||
3. 设计风格:5 个图标必须风格明显不同,避免雷同
|
||||
- 第1个:扁平化风格,色彩鲜明,使用渐变
|
||||
- 第2个:线性风格,多色描边,细节丰富
|
||||
- 第3个:3D立体风格,使用阴影和高光效果
|
||||
- 第4个:卡通可爱风格,圆润造型,活泼配色
|
||||
- 第5个:现代简约风格,几何与曲线结合,优雅配色
|
||||
4. 细节丰富:不要只用简单的几何图形,添加特征性的细节元素
|
||||
- 例如:餐饮可以加刀叉、蒸汽、食材纹理等
|
||||
- 交通可以加轮胎、车窗、尾气等
|
||||
- 每个图标要有独特的视觉记忆点
|
||||
5. 图标要直观表达分类含义,让人一眼就能识别
|
||||
6. 只返回 JSON 数组格式,包含 5 个完整的 SVG 字符串,不要有任何其他文字说明
|
||||
|
||||
重要:每个 SVG 必须是自包含的完整代码,包含所有必要的 gradient 定义。
|
||||
""";
|
||||
|
||||
var userPrompt = $"""
|
||||
分类名称:{categoryName}
|
||||
分类类型:{typeText}
|
||||
|
||||
请为这个分类生成 {iconCount} 个精美的、风格各异的彩色 SVG 图标。
|
||||
确保每个图标都有独特的视觉特征,不会与其他图标混淆。
|
||||
|
||||
返回格式(纯 JSON 数组,无其他内容):
|
||||
["<svg>...</svg>", "<svg>...</svg>", "<svg>...</svg>", "<svg>...</svg>", "<svg>...</svg>"]
|
||||
""";
|
||||
|
||||
var response = await openAiService.ChatAsync(systemPrompt, userPrompt, timeoutSeconds: 60 * 10);
|
||||
var response = await openAiService.ChatAsync(systemPrompt, "", timeoutSeconds: 60 * 10);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
@@ -696,6 +686,15 @@ public class SmartHandleService(
|
||||
return null;
|
||||
}
|
||||
|
||||
// 清理可能的 markdown 代码块标记
|
||||
response = CleanMarkdownCodeBlock(response);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
logger.LogWarning("清理后的 AI 响应为空,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 验证返回的是有效的 JSON 数组
|
||||
try
|
||||
{
|
||||
@@ -724,45 +723,66 @@ public class SmartHandleService(
|
||||
{
|
||||
logger.LogInformation("正在为分类 {CategoryName} 生成单个图标", categoryName);
|
||||
|
||||
var typeText = categoryType == TransactionType.Expense ? "支出" : "收入";
|
||||
// 使用单个图标生成的 Prompt(只生成 1 个图标,加快速度)
|
||||
var systemPrompt = iconPromptProvider.GetSingleIconPrompt(categoryName, categoryType);
|
||||
|
||||
var systemPrompt = """
|
||||
你是一个专业的SVG图标设计师。为预算分类生成极简风格的SVG图标。
|
||||
|
||||
设计要求:
|
||||
1. 尺寸:24x24,viewBox="0 0 24 24"
|
||||
2. 使用丰富的渐变色和多色搭配,让图标更有吸引力
|
||||
3. 图标要直观表达分类含义
|
||||
4. 只返回SVG代码,不要有任何其他文字说明
|
||||
""";
|
||||
|
||||
var userPrompt = $"""
|
||||
请为「{categoryName}」{typeText}分类生成一个精美的SVG图标。
|
||||
直接返回SVG代码,无需解释。
|
||||
""";
|
||||
|
||||
var svgContent = await openAiService.ChatAsync(systemPrompt, userPrompt, timeoutSeconds: 30);
|
||||
if (string.IsNullOrWhiteSpace(svgContent))
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
logger.LogWarning("AI 未返回有效的图标数据,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
// 增加超时时间到 180 秒(3 分钟)
|
||||
var response = await openAiService.ChatAsync(systemPrompt, "", timeoutSeconds: 180);
|
||||
|
||||
stopwatch.Stop();
|
||||
logger.LogInformation("AI 响应耗时: {ElapsedMs}ms,分类: {CategoryName}", stopwatch.ElapsedMilliseconds, categoryName);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
logger.LogWarning("AI 未返回有效的图标数据,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 清理可能的 markdown 代码块标记
|
||||
response = CleanMarkdownCodeBlock(response);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(response))
|
||||
{
|
||||
logger.LogWarning("清理后的 AI 响应为空,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解析返回的 JSON 数组,取第一个图标
|
||||
try
|
||||
{
|
||||
var icons = JsonSerializer.Deserialize<List<string>>(response);
|
||||
if (icons == null || icons.Count == 0)
|
||||
{
|
||||
logger.LogWarning("AI 未返回有效的图标数据,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
}
|
||||
|
||||
var svg = icons[0];
|
||||
logger.LogInformation("成功为分类 {CategoryName} 生成单个图标,总耗时: {ElapsedMs}ms", categoryName, stopwatch.ElapsedMilliseconds);
|
||||
return svg;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
logger.LogError(ex, "解析 AI 返回的图标数据失败,分类: {CategoryName},响应内容: {Response}",
|
||||
categoryName, response.Length > 500 ? response.Substring(0, 500) + "..." : response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 提取SVG标签
|
||||
var svgMatch = System.Text.RegularExpressions.Regex.Match(
|
||||
svgContent,
|
||||
@"<svg[^>]*>.*?</svg>",
|
||||
System.Text.RegularExpressions.RegexOptions.Singleline);
|
||||
|
||||
if (!svgMatch.Success)
|
||||
catch (TimeoutException)
|
||||
{
|
||||
logger.LogWarning("生成的内容不包含有效的SVG标签,分类: {CategoryName}", categoryName);
|
||||
return null;
|
||||
stopwatch.Stop();
|
||||
logger.LogError("AI 请求超时(>180秒),分类: {CategoryName},已等待: {ElapsedMs}ms", categoryName, stopwatch.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
stopwatch.Stop();
|
||||
logger.LogError(ex, "AI 调用失败,分类: {CategoryName},耗时: {ElapsedMs}ms", categoryName, stopwatch.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
|
||||
var svg = svgMatch.Value;
|
||||
logger.LogInformation("成功为分类 {CategoryName} 生成单个图标", categoryName);
|
||||
return svg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user