From 7e0e6457d70c7236b2e2c0e288c43027d67c792a Mon Sep 17 00:00:00 2001 From: qzl Date: Sat, 28 Feb 2026 13:36:59 +0800 Subject: [PATCH] feat(chat): add AiDecisionEngine with force trigger support --- .../chat/data/ai/ai_decision_engine.dart | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 apps/lib/features/chat/data/ai/ai_decision_engine.dart diff --git a/apps/lib/features/chat/data/ai/ai_decision_engine.dart b/apps/lib/features/chat/data/ai/ai_decision_engine.dart new file mode 100644 index 0000000..bd27a98 --- /dev/null +++ b/apps/lib/features/chat/data/ai/ai_decision_engine.dart @@ -0,0 +1,100 @@ +import 'dart:convert'; + +enum Intent { createEvent, searchEvent, unknown } + +class AiDecisionEngine { + /// 意图匹配规则(顺序敏感:searchEvent 优先级高于 createEvent) + static final List<(RegExp, Intent)> _orderedPatterns = [ + (RegExp(r'^查看|^有什么|^今天.*日程|^明天.*安排|^查询'), Intent.searchEvent), + (RegExp(r'提醒|开会|预约|安排.*时间|创建.*日程'), Intent.createEvent), + ( + RegExp(r'明天.*\d|今天.*\d|后天.*\d|\d{1,2}点|\d{1,2}:\d{2}'), + Intent.createEvent, + ), + ]; + + Intent matchIntent(String text) { + for (final (pattern, intent) in _orderedPatterns) { + if (pattern.hasMatch(text)) return intent; + } + return Intent.unknown; + } + + Map? tryExtractEventArgs(String text) { + if (matchIntent(text) != Intent.createEvent) return null; + + final args = {}; + + final titleMatch = RegExp(r'提醒(.+?)(?:明天|今天|几点|$)').firstMatch(text); + if (titleMatch != null) { + args['title'] = titleMatch.group(1)?.trim() ?? text; + } else if (RegExp(r'\d{1,2}[:点]|\d{1,2}点').hasMatch(text)) { + args['title'] = text + .replaceAll(RegExp(r'\d{1,2}[:点]\d{0,2}|明天|今天|后天'), '') + .trim(); + } + + if (args['title'] == null || (args['title'] as String).isEmpty) return null; + + final timeMatch = RegExp( + r'(明天|今天|后天)?\s*(\d{1,2})[:点](\d{2})?', + ).firstMatch(text); + if (timeMatch != null) { + final dayStr = timeMatch.group(1) ?? '今天'; + final hour = int.parse(timeMatch.group(2)!); + final minute = int.parse(timeMatch.group(3) ?? '0'); + + final now = DateTime.now(); + DateTime startAt; + switch (dayStr) { + case '明天': + startAt = DateTime(now.year, now.month, now.day + 1, hour, minute); + break; + case '后天': + startAt = DateTime(now.year, now.month, now.day + 2, hour, minute); + break; + default: + startAt = DateTime(now.year, now.month, now.day, hour, minute); + } + + args['startAt'] = startAt.toIso8601String(); + args['timezone'] = 'Asia/Shanghai'; + } + + if (!args.containsKey('startAt')) return null; + return args; + } + + bool shouldTriggerToolCall(String text) => + matchIntent(text) == Intent.createEvent; + + Map? getToolCallArgs(String text) { + if (!shouldTriggerToolCall(text)) return null; + return tryExtractEventArgs(text); + } + + ForceTriggerResult? tryForceTrigger(String text) { + final match = RegExp(r'#tool:(\w+)\s*(\{.*\})?').firstMatch(text); + if (match == null) return null; + + final toolName = match.group(1)!; + final argsJson = match.group(2); + + Map? args; + if (argsJson != null) { + try { + args = jsonDecode(argsJson) as Map; + } catch (_) { + args = {}; + } + } + + return ForceTriggerResult(toolName: toolName, args: args ?? {}); + } +} + +class ForceTriggerResult { + final String toolName; + final Map args; + ForceTriggerResult({required this.toolName, required this.args}); +}