diff --git a/Entity/ConfigEntity.cs b/Entity/ConfigEntity.cs
new file mode 100644
index 0000000..bcbba1f
--- /dev/null
+++ b/Entity/ConfigEntity.cs
@@ -0,0 +1,31 @@
+namespace Entity;
+
+///
+/// 配置实体
+///
+[Table(Name = "Config")]
+public class ConfigEntity : BaseEntity
+{
+ ///
+ /// 配置Key
+ ///
+ public string Key { get; set; } = string.Empty;
+
+ ///
+ /// 配置Value
+ ///
+ public string Value { get; set; } = string.Empty;
+
+ ///
+ /// 配置类型
+ ///
+ public ConfigType Type { get; set; }
+}
+
+public enum ConfigType
+{
+ Boolean,
+ String,
+ Json,
+ Number
+}
diff --git a/Repository/ConfigRepository.cs b/Repository/ConfigRepository.cs
new file mode 100644
index 0000000..5fed37b
--- /dev/null
+++ b/Repository/ConfigRepository.cs
@@ -0,0 +1,19 @@
+namespace Repository;
+
+public interface IConfigRepository : IBaseRepository
+{
+ ///
+ /// 根据Key获取配置
+ ///
+ Task GetByKeyAsync(string key);
+}
+
+public class ConfigRepository(IFreeSql freeSql) : BaseRepository(freeSql), IConfigRepository
+{
+ public async Task GetByKeyAsync(string key)
+ {
+ return await FreeSql.Select()
+ .Where(c => c.Key == key)
+ .FirstAsync();
+ }
+}
diff --git a/Service/ConfigService.cs b/Service/ConfigService.cs
new file mode 100644
index 0000000..8550e27
--- /dev/null
+++ b/Service/ConfigService.cs
@@ -0,0 +1,77 @@
+namespace Service;
+
+public interface IConfigService
+{
+ ///
+ /// 根据Key获取配置值
+ ///
+ Task GetConfigByKeyAsync(string key);
+
+ ///
+ /// 设置配置值
+ ///
+ Task SetConfigByKeyAsync(string key, T value);
+}
+
+public class ConfigService(IConfigRepository configRepository) : IConfigService
+{
+ public async Task GetConfigByKeyAsync(string key)
+ {
+ var config = await configRepository.GetByKeyAsync(key);
+ if (config == null || string.IsNullOrEmpty(config.Value))
+ {
+ return default;
+ }
+
+ return config.Type switch
+ {
+ ConfigType.Boolean => (T)(object)bool.Parse(config.Value),
+ ConfigType.String => (T)(object)config.Value,
+ ConfigType.Number => (T)Convert.ChangeType(config.Value, typeof(T)),
+ ConfigType.Json => JsonSerializer.Deserialize(config.Value) ?? default,
+ _ => default
+ };
+ }
+
+ public async Task SetConfigByKeyAsync(string key, T value)
+ {
+ if (value == null)
+ {
+ return false;
+ }
+
+ var config = await configRepository.GetByKeyAsync(key);
+ var type = typeof(T) switch
+ {
+ Type t when t == typeof(bool) => ConfigType.Boolean,
+ Type t when t == typeof(int)
+ || t == typeof(double)
+ || t == typeof(float)
+ || t == typeof(decimal) => ConfigType.Number,
+ Type t when t == typeof(string) => ConfigType.String,
+ _ => ConfigType.Json
+ };
+ var valueStr = type switch
+ {
+ ConfigType.Boolean => value.ToString()!.ToLower(),
+ ConfigType.Number => value.ToString()!,
+ ConfigType.String => value as string ?? string.Empty,
+ ConfigType.Json => JsonSerializer.Serialize(value),
+ _ => throw new InvalidOperationException("Unsupported config type")
+ };
+ if (config == null)
+ {
+ config = new ConfigEntity
+ {
+ Key = key,
+ Type = type,
+
+ };
+ return await configRepository.AddAsync(config);
+ }
+
+ config.Value = valueStr;
+ config.Type = type;
+ return await configRepository.UpdateAsync(config);
+ }
+}
diff --git a/Service/SmartHandleService.cs b/Service/SmartHandleService.cs
index 5c60a43..e9d9e96 100644
--- a/Service/SmartHandleService.cs
+++ b/Service/SmartHandleService.cs
@@ -14,7 +14,8 @@ public class SmartHandleService(
ITextSegmentService textSegmentService,
ILogger logger,
ITransactionCategoryRepository categoryRepository,
- IOpenAiService openAiService
+ IOpenAiService openAiService,
+ IConfigService configService
) : ISmartHandleService
{
public async Task SmartClassifyAsync(long[] transactionIds, Action<(string, string)> chunkAction)
@@ -258,9 +259,9 @@ public class SmartHandleService(
{
// 第一步:使用AI生成聚合SQL查询
var now = DateTime.Now;
- var sqlPrompt = $"""
- 当前日期:{now:yyyy年M月d日}({now:yyyy-MM-dd})
- 用户问题:{userInput}
+ var sqlPrompt = $$"""
+ 当前日期:{{now:yyyy年M月d日}}({{now:yyyy-MM-dd}})
+ 用户问题:{{userInput}}
数据库类型:SQLite
数据库表名:TransactionRecord
@@ -291,22 +292,27 @@ public class SmartHandleService(
- 提取日期:strftime('%Y-%m-%d', OccurredAt)
- 不要使用 YEAR()、MONTH()、DAY() 函数,SQLite不支持
- 示例1(按分类统计):
- 用户:这三个月坐车花了多少钱?
- 返回:SELECT Classify, COUNT(*) as TransactionCount, SUM(ABS(Amount)) as TotalAmount, AVG(ABS(Amount)) as AvgAmount FROM TransactionRecord WHERE Type = 0 AND OccurredAt >= '2025-10-01' AND OccurredAt < '2026-01-01' AND (Classify LIKE '%交通%' OR Reason LIKE '%打车%' OR Reason LIKE '%公交%' OR Reason LIKE '%地铁%') GROUP BY Classify ORDER BY TotalAmount DESC
-
- 示例2(按月统计):
- 用户:最近半年每月支出情况
- 返回:SELECT strftime('%Y', OccurredAt) as Year, strftime('%m', OccurredAt) as Month, COUNT(*) as TransactionCount, SUM(ABS(Amount)) as TotalAmount FROM TransactionRecord WHERE Type = 0 AND OccurredAt >= '2025-06-01' GROUP BY strftime('%Y', OccurredAt), strftime('%m', OccurredAt) ORDER BY Year, Month
-
- 示例3(总体统计):
- 用户:本月花了多少钱?
- 返回:SELECT COUNT(*) as TransactionCount, SUM(ABS(Amount)) as TotalAmount, AVG(ABS(Amount)) as AvgAmount, MAX(ABS(Amount)) as MaxAmount FROM TransactionRecord WHERE Type = 0 AND OccurredAt >= '2025-12-01' AND OccurredAt < '2026-01-01'
-
- 示例4(详细记录 - 仅在用户明确要求详情时使用):
- 用户:单笔超过1000元的支出有哪些?
- 返回:SELECT OccurredAt, Classify, Reason, ABS(Amount) as Amount FROM TransactionRecord WHERE Type = 0 AND ABS(Amount) > 1000 ORDER BY Amount DESC LIMIT 50
-
+ 【重要】最终的SQL会被一下DOTNET代码执行, 请确保你生成的代码可执行,不报错
+ ```C#
+ public async Task> ExecuteDynamicSqlAsync(string completeSql)
+ {
+ var dt = await FreeSql.Ado.ExecuteDataTableAsync(completeSql);
+ var result = new List();
+
+ foreach (System.Data.DataRow row in dt.Rows)
+ {
+ var expando = new System.Dynamic.ExpandoObject() as IDictionary;
+ foreach (System.Data.DataColumn column in dt.Columns)
+ {
+ expando[column.ColumnName] = row[column];
+ }
+ result.Add(expando);
+ }
+
+ return result;
+ }
+ ```
+
只返回SQL语句。
""";
@@ -344,10 +350,15 @@ public class SmartHandleService(
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
+ var userPromptExtra = await configService.GetConfigByKeyAsync("BillAnalysisPrompt");
+
var dataPrompt = $"""
当前日期:{DateTime.Now:yyyy年M月d日}
用户问题:{userInput}
+ 【用户要求(重要)】
+ {userInput}
+
查询结果数据(JSON格式):
{dataJson}
@@ -375,6 +386,9 @@ public class SmartHandleService(
13. 提供洞察分析:根据数据给出有价值的发现和趋势分析
14. 给出实用建议:基于数据提供合理的财务建议
15. 语言专业、清晰、简洁
+
+ 【用户补充(重要)】
+ {userPromptExtra}
直接输出纯净的HTML内容,不要markdown代码块标记。
""";
diff --git a/Web/src/api/config.js b/Web/src/api/config.js
new file mode 100644
index 0000000..c9a4826
--- /dev/null
+++ b/Web/src/api/config.js
@@ -0,0 +1,28 @@
+import request from './request'
+
+/**
+ * 获取配置值
+ * @param {string} key - 配置的key
+ * @returns {Promise<{success: boolean, data: string}>}
+ */
+export const getConfig = (key) => {
+ return request({
+ url: '/Config/GetConfig',
+ method: 'get',
+ params: { key }
+ })
+}
+
+/**
+ * 设置配置值
+ * @param {string} key - 配置的key
+ * @param {string} value - 配置的值
+ * @returns {Promise<{success: boolean}>}
+ */
+export const setConfig = (key, value) => {
+ return request({
+ url: '/Config/SetConfig',
+ method: 'post',
+ params: { key, value }
+ })
+}
diff --git a/Web/src/views/BillAnalysisView.vue b/Web/src/views/BillAnalysisView.vue
index f52f2c0..8ecc1b4 100644
--- a/Web/src/views/BillAnalysisView.vue
+++ b/Web/src/views/BillAnalysisView.vue
@@ -6,7 +6,16 @@
left-arrow
placeholder
@click-left="onClickLeft"
- />
+ >
+
+
+
+
@@ -71,13 +80,32 @@
+
+
+
+
+