2026-02-28 13:36:59 +08:00
|
|
|
|
import 'dart:convert';
|
|
|
|
|
|
|
|
|
|
|
|
enum Intent { createEvent, searchEvent, unknown }
|
|
|
|
|
|
|
2026-02-28 14:41:21 +08:00
|
|
|
|
/// 意图匹配规则(顺序敏感: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;
|
2026-02-28 13:36:59 +08:00
|
|
|
|
|
2026-02-28 14:41:21 +08:00
|
|
|
|
class AiDecisionEngine {
|
2026-02-28 13:36:59 +08:00
|
|
|
|
Intent matchIntent(String text) {
|
|
|
|
|
|
for (final (pattern, intent) in _orderedPatterns) {
|
|
|
|
|
|
if (pattern.hasMatch(text)) return intent;
|
|
|
|
|
|
}
|
|
|
|
|
|
return Intent.unknown;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic>? tryExtractEventArgs(String text) {
|
|
|
|
|
|
if (matchIntent(text) != Intent.createEvent) return null;
|
|
|
|
|
|
|
|
|
|
|
|
final args = <String, dynamic>{};
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 14:41:21 +08:00
|
|
|
|
final title = args['title'];
|
|
|
|
|
|
if (title == null || (title as String).isEmpty) return null;
|
2026-02-28 13:36:59 +08:00
|
|
|
|
|
|
|
|
|
|
final timeMatch = RegExp(
|
|
|
|
|
|
r'(明天|今天|后天)?\s*(\d{1,2})[:点](\d{2})?',
|
|
|
|
|
|
).firstMatch(text);
|
|
|
|
|
|
if (timeMatch != null) {
|
2026-02-28 14:41:21 +08:00
|
|
|
|
final dayStr = timeMatch.group(1) ?? _dayToday;
|
2026-02-28 13:36:59 +08:00
|
|
|
|
final hour = int.parse(timeMatch.group(2)!);
|
2026-02-28 14:41:21 +08:00
|
|
|
|
final minute = int.parse(timeMatch.group(3) ?? '$_defaultMinute');
|
2026-02-28 13:36:59 +08:00
|
|
|
|
|
|
|
|
|
|
final now = DateTime.now();
|
2026-02-28 14:41:21 +08:00
|
|
|
|
final dayOffset = switch (dayStr) {
|
|
|
|
|
|
_dayTomorrow => _tomorrowOffset,
|
|
|
|
|
|
_dayAfterTomorrow => _dayAfterTomorrowOffset,
|
|
|
|
|
|
_ => 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
final startAt = DateTime(
|
|
|
|
|
|
now.year,
|
|
|
|
|
|
now.month,
|
|
|
|
|
|
now.day + dayOffset,
|
|
|
|
|
|
hour,
|
|
|
|
|
|
minute,
|
|
|
|
|
|
);
|
2026-02-28 13:36:59 +08:00
|
|
|
|
|
|
|
|
|
|
args['startAt'] = startAt.toIso8601String();
|
2026-02-28 14:41:21 +08:00
|
|
|
|
args['timezone'] = _defaultTimezone;
|
2026-02-28 13:36:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!args.containsKey('startAt')) return null;
|
|
|
|
|
|
return args;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool shouldTriggerToolCall(String text) =>
|
|
|
|
|
|
matchIntent(text) == Intent.createEvent;
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic>? getToolCallArgs(String text) {
|
|
|
|
|
|
if (!shouldTriggerToolCall(text)) return null;
|
|
|
|
|
|
return tryExtractEventArgs(text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ForceTriggerResult? tryForceTrigger(String text) {
|
2026-03-09 00:10:09 +08:00
|
|
|
|
final match = RegExp(
|
|
|
|
|
|
r'#tool:([A-Za-z0-9_.-]+)\s*(\{.*\})?',
|
|
|
|
|
|
).firstMatch(text);
|
2026-02-28 13:36:59 +08:00
|
|
|
|
if (match == null) return null;
|
|
|
|
|
|
|
|
|
|
|
|
final toolName = match.group(1)!;
|
|
|
|
|
|
final argsJson = match.group(2);
|
|
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic>? args;
|
|
|
|
|
|
if (argsJson != null) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
args = jsonDecode(argsJson) as Map<String, dynamic>;
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
args = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ForceTriggerResult(toolName: toolName, args: args ?? {});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class ForceTriggerResult {
|
|
|
|
|
|
final String toolName;
|
|
|
|
|
|
final Map<String, dynamic> args;
|
|
|
|
|
|
ForceTriggerResult({required this.toolName, required this.args});
|
|
|
|
|
|
}
|