import 'dart:convert'; enum Intent { createEvent, searchEvent, unknown } /// 意图匹配规则(顺序敏感:searchEvent 优先级高于 createEvent) final _orderedPatterns = <(RegExp, Intent)>[ (RegExp(r'^查看|^有什么|^今天.*日程|^明天.*安排|^查询'), Intent.searchEvent), (RegExp(r'提醒|开会|预约|安排.*时间|创建.*日程'), Intent.createEvent), (RegExp(r'明天.*\d|今天.*\d|后天.*\d|\d{1,2}点|\d{1,2}:\d{2}'), Intent.createEvent), ]; /// 时区常量 const _defaultTimezone = 'Asia/Shanghai'; const _dayToday = '今天'; const _dayTomorrow = '明天'; const _dayAfterTomorrow = '后天'; const _tomorrowOffset = 1; const _dayAfterTomorrowOffset = 2; const _defaultMinute = 0; class AiDecisionEngine { 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(); } final title = args['title']; if (title == null || (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) ?? _dayToday; final hour = int.parse(timeMatch.group(2)!); final minute = int.parse(timeMatch.group(3) ?? '$_defaultMinute'); final now = DateTime.now(); final dayOffset = switch (dayStr) { _dayTomorrow => _tomorrowOffset, _dayAfterTomorrow => _dayAfterTomorrowOffset, _ => 0, }; final startAt = DateTime( now.year, now.month, now.day + dayOffset, hour, minute, ); args['startAt'] = startAt.toIso8601String(); args['timezone'] = _defaultTimezone; } 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:([A-Za-z0-9_.-]+)\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}); }