Skip to content

feat: 支持分钟级别定时#251

Open
zmdyy0318 wants to merge 2 commits into
MistEO:mainfrom
zmdyy0318:feat/2026062601
Open

feat: 支持分钟级别定时#251
zmdyy0318 wants to merge 2 commits into
MistEO:mainfrom
zmdyy0318:feat/2026062601

Conversation

@zmdyy0318

@zmdyy0318 zmdyy0318 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

close MaaEnd/MaaEnd#1247
opus4.8

Summary by Sourcery

通过将调度策略从按小时时间段切换为显式时间点,支持分钟级调度。

New Features:

  • 允许用户使用精确到分钟的时间点来配置日程,而不仅仅是按小时进行选择。

Enhancements:

  • 将日程评估更新为按分钟粒度的时间段执行,以实现更精细的轮询和补偿逻辑。
  • 在保持兼容性的前提下,将现有基于小时的日程配置迁移到新的时间点格式。
  • 更新日程 UI,使其通过 HH:mm 字符串来输入、列出和汇总选定时间,而不是使用按小时网格。
Original summary in English

Summary by Sourcery

Support minute-level scheduling by switching schedule policies from hourly slots to explicit time points.

New Features:

  • Allow users to configure schedules using specific minute-level time points instead of only hourly selections.

Enhancements:

  • Update schedule evaluation to run on per-minute slots with finer-grained polling and compensation logic.
  • Migrate existing hourly-based schedule configurations to the new time-point format while preserving compatibility.
  • Refresh schedule UI to input, list, and summarize selected times using HH:mm strings instead of an hour grid.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并提供了一些整体性的反馈:

  • 触发时段的存储键已经更新为 YYYY-MM-DD-HH-mm 格式,但 normalizeTriggeredSlotKeycleanTriggeredSlots 不再接受旧的 YYYY-MM-DD-HH 键;建议保留一条临时的兼容路径,或进行一次性迁移,以避免旧的条目遗留在 localStorage 中。
  • 默认时间值 08:00 在多个地方被硬编码(timeDraft 状态和新策略创建处);建议抽取一个共享常量,以便保持默认排程时间的一致性并更易于调整。
给 AI Agents 的提示词
Please address the comments from this code review:

## Overall Comments
- The storage key handling for triggered slots has been updated to a `YYYY-MM-DD-HH-mm` format, but `normalizeTriggeredSlotKey` and `cleanTriggeredSlots` no longer accept the old `YYYY-MM-DD-HH` keys; consider keeping a temporary compatibility path or a one-shot migration to avoid leaking old entries in localStorage.
- The default time value `08:00` is hardcoded in multiple places (`timeDraft` state and new policy creation); consider extracting a shared constant to keep the default schedule time consistent and easier to adjust.

## Individual Comments

### Comment 1
<location path="src/components/SchedulePanel.tsx" line_range="22-25" />
<code_context>
-// 小时选项 (0-23)
-const HOURS = Array.from({ length: 24 }, (_, i) => i);
+// 校验 "HH:mm" 格式
+const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

 interface SchedulePanelProps {
</code_context>
<issue_to_address>
**suggestion:** Consider normalizing the `<input type="time">` value instead of strictly rejecting values that include seconds.

Some browsers emit `HH:mm:ss` for `type="time"`, which will fail this pattern and block adding a schedule even though the value can be safely reduced to minutes. Consider normalizing `e.target.value` (e.g., `value.slice(0, 5)` or via `Date`/`Intl`) before storing it in `timeDraft`, and validate only the normalized `HH:mm` string to preserve the invariant used elsewhere.

Suggested implementation:

```typescript
 // 校验规范化后的 "HH:mm" 格式
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

// 规范化 <input type="time"> 的值,将 "HH:mm:ss" 等形式裁剪为 "HH:mm"
const normalizeTimeValue = (value: string): string => {
  if (!value) return value;
  // 绝大多数浏览器时间字符串形如 "HH:mm" 或 "HH:mm:ss"
  // 只要前 5 位是 "HH:mm" 形态,就裁剪前 5 位作为规范格式
  if (value.length >= 5 && value[2] === ':') {
    return value.slice(0, 5);
  }
  return value;
};

interface SchedulePanelProps {

```

To fully implement the behavior you described, you should also update the time input handling code (not visible in the snippet). Wherever you currently handle the time input (e.g. `onChange`, `onBlur`, or wherever `e.target.value` is stored into `timeDraft` or validated via `TIME_PATTERN`):

1. Normalize the raw value before using it:
   - Change something like:
     - `const value = e.target.value;`
   - To:
     - `const value = normalizeTimeValue(e.target.value);`
2. Use the normalized `value` for:
   - `setTimeDraft(value);`
   - Any `TIME_PATTERN.test(...)` calls.
   - Any other schedule creation/update logic that relies on time strings.
3. Ensure that any initial values coming from props or external sources that represent a time value are either already `HH:mm` or also passed through `normalizeTimeValue` before being stored/validated, to keep the invariant consistent.
</issue_to_address>

### Comment 2
<location path="src/stores/appStore.ts" line_range="69-77" />
<code_context>
 import type { AppState, LogEntry, TaskRunStatus } from './types';

+/** 向后兼容:将旧版整点小时 hours 迁移为 times ("HH:mm") */
+function migrateSchedulePolicies(inst: {
+  schedulePolicies?: SchedulePolicy[];
+}): SchedulePolicy[] | undefined {
+  if (!inst.schedulePolicies) return undefined;
+  return inst.schedulePolicies.map((policy) => {
+    if (policy.times) {
+      // 已是新格式,丢弃可能残留的 hours 字段
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Normalize existing `times` arrays during migration to ensure they satisfy the new invariant (sorted & deduped).

In the helper, when `policy.times` already exists you currently return it unchanged (aside from dropping `hours`). If any legacy or externally-produced `times` are unsorted or contain duplicates, they’ll violate the documented “已排序去重” contract that the UI and scheduler now assume. Consider normalizing here too, e.g. `Array.from(new Set(policy.times)).sort((a, b) => a.localeCompare(b))`, so both migrated and pre-existing configs share the same guarantees.

```suggestion
  return inst.schedulePolicies.map((policy) => {
    if (policy.times) {
      // 已是新格式:规范化 times(排序去重),并丢弃可能残留的 hours 字段
      const normalizedTimes = Array.from(new Set(policy.times)).sort((a, b) =>
        a.localeCompare(b),
      );
      const { hours: _legacyHours, ...rest } = policy;
      return { ...rest, times: normalizedTimes };
    }
    const times = (policy.hours ?? [])
      .map((h) => `${String(h).padStart(2, '0')}:00`)
      .sort((a, b) => a.localeCompare(b));
```
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The storage key handling for triggered slots has been updated to a YYYY-MM-DD-HH-mm format, but normalizeTriggeredSlotKey and cleanTriggeredSlots no longer accept the old YYYY-MM-DD-HH keys; consider keeping a temporary compatibility path or a one-shot migration to avoid leaking old entries in localStorage.
  • The default time value 08:00 is hardcoded in multiple places (timeDraft state and new policy creation); consider extracting a shared constant to keep the default schedule time consistent and easier to adjust.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The storage key handling for triggered slots has been updated to a `YYYY-MM-DD-HH-mm` format, but `normalizeTriggeredSlotKey` and `cleanTriggeredSlots` no longer accept the old `YYYY-MM-DD-HH` keys; consider keeping a temporary compatibility path or a one-shot migration to avoid leaking old entries in localStorage.
- The default time value `08:00` is hardcoded in multiple places (`timeDraft` state and new policy creation); consider extracting a shared constant to keep the default schedule time consistent and easier to adjust.

## Individual Comments

### Comment 1
<location path="src/components/SchedulePanel.tsx" line_range="22-25" />
<code_context>
-// 小时选项 (0-23)
-const HOURS = Array.from({ length: 24 }, (_, i) => i);
+// 校验 "HH:mm" 格式
+const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

 interface SchedulePanelProps {
</code_context>
<issue_to_address>
**suggestion:** Consider normalizing the `<input type="time">` value instead of strictly rejecting values that include seconds.

Some browsers emit `HH:mm:ss` for `type="time"`, which will fail this pattern and block adding a schedule even though the value can be safely reduced to minutes. Consider normalizing `e.target.value` (e.g., `value.slice(0, 5)` or via `Date`/`Intl`) before storing it in `timeDraft`, and validate only the normalized `HH:mm` string to preserve the invariant used elsewhere.

Suggested implementation:

```typescript
 // 校验规范化后的 "HH:mm" 格式
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

// 规范化 <input type="time"> 的值,将 "HH:mm:ss" 等形式裁剪为 "HH:mm"
const normalizeTimeValue = (value: string): string => {
  if (!value) return value;
  // 绝大多数浏览器时间字符串形如 "HH:mm" 或 "HH:mm:ss"
  // 只要前 5 位是 "HH:mm" 形态,就裁剪前 5 位作为规范格式
  if (value.length >= 5 && value[2] === ':') {
    return value.slice(0, 5);
  }
  return value;
};

interface SchedulePanelProps {

```

To fully implement the behavior you described, you should also update the time input handling code (not visible in the snippet). Wherever you currently handle the time input (e.g. `onChange`, `onBlur`, or wherever `e.target.value` is stored into `timeDraft` or validated via `TIME_PATTERN`):

1. Normalize the raw value before using it:
   - Change something like:
     - `const value = e.target.value;`
   - To:
     - `const value = normalizeTimeValue(e.target.value);`
2. Use the normalized `value` for:
   - `setTimeDraft(value);`
   - Any `TIME_PATTERN.test(...)` calls.
   - Any other schedule creation/update logic that relies on time strings.
3. Ensure that any initial values coming from props or external sources that represent a time value are either already `HH:mm` or also passed through `normalizeTimeValue` before being stored/validated, to keep the invariant consistent.
</issue_to_address>

### Comment 2
<location path="src/stores/appStore.ts" line_range="69-77" />
<code_context>
 import type { AppState, LogEntry, TaskRunStatus } from './types';

+/** 向后兼容:将旧版整点小时 hours 迁移为 times ("HH:mm") */
+function migrateSchedulePolicies(inst: {
+  schedulePolicies?: SchedulePolicy[];
+}): SchedulePolicy[] | undefined {
+  if (!inst.schedulePolicies) return undefined;
+  return inst.schedulePolicies.map((policy) => {
+    if (policy.times) {
+      // 已是新格式,丢弃可能残留的 hours 字段
+      const { hours: _legacyHours, ...rest } = policy;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Normalize existing `times` arrays during migration to ensure they satisfy the new invariant (sorted & deduped).

In the helper, when `policy.times` already exists you currently return it unchanged (aside from dropping `hours`). If any legacy or externally-produced `times` are unsorted or contain duplicates, they’ll violate the documented “已排序去重” contract that the UI and scheduler now assume. Consider normalizing here too, e.g. `Array.from(new Set(policy.times)).sort((a, b) => a.localeCompare(b))`, so both migrated and pre-existing configs share the same guarantees.

```suggestion
  return inst.schedulePolicies.map((policy) => {
    if (policy.times) {
      // 已是新格式:规范化 times(排序去重),并丢弃可能残留的 hours 字段
      const normalizedTimes = Array.from(new Set(policy.times)).sort((a, b) =>
        a.localeCompare(b),
      );
      const { hours: _legacyHours, ...rest } = policy;
      return { ...rest, times: normalizedTimes };
    }
    const times = (policy.hours ?? [])
      .map((h) => `${String(h).padStart(2, '0')}:00`)
      .sort((a, b) => a.localeCompare(b));
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +22 to 25
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

interface SchedulePanelProps {
instanceId: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 建议对 <input type="time"> 的值做规范化,而不是严格拒绝包含秒的值。

部分浏览器对 type="time" 会输出 HH:mm:ss,这会导致当前的正则匹配失败,从而阻止添加排程,但其实该值是可以安全地缩减到分钟粒度的。建议在将 e.target.value 存入 timeDraft 之前先做规范化处理(例如使用 value.slice(0, 5),或通过 Date/Intl 处理),并只对规范化后的 HH:mm 字符串做校验,以保持其它地方使用的时间格式不变。

建议的实现方式:

 // 校验规范化后的 "HH:mm" 格式
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

// 规范化 <input type="time"> 的值,将 "HH:mm:ss" 等形式裁剪为 "HH:mm"
const normalizeTimeValue = (value: string): string => {
  if (!value) return value;
  // 绝大多数浏览器时间字符串形如 "HH:mm" 或 "HH:mm:ss"
  // 只要前 5 位是 "HH:mm" 形态,就裁剪前 5 位作为规范格式
  if (value.length >= 5 && value[2] === ':') {
    return value.slice(0, 5);
  }
  return value;
};

interface SchedulePanelProps {

为了完整实现你在描述中希望的行为,你还需要更新时间输入的处理逻辑(在当前代码片段中不可见)。在所有处理时间输入的地方(例如 onChangeonBlur,或者任何将 e.target.value 写入 timeDraft 或用 TIME_PATTERN 校验的地方):

  1. 在使用原始值之前先做规范化:
    • 将类似下面的代码:
      • const value = e.target.value;
    • 替换为:
      • const value = normalizeTimeValue(e.target.value);
  2. 使用规范化后的 value 来:
    • 调用 setTimeDraft(value);
    • 进行任何 TIME_PATTERN.test(...) 调用。
    • 以及所有依赖时间字符串的排程创建/更新逻辑。
  3. 确保来自 props 或外部来源的初始时间值要么已经是 HH:mm 格式,要么同样先通过 normalizeTimeValue 再存储/校验,以保持该不变式的一致性。
Original comment in English

suggestion: Consider normalizing the <input type="time"> value instead of strictly rejecting values that include seconds.

Some browsers emit HH:mm:ss for type="time", which will fail this pattern and block adding a schedule even though the value can be safely reduced to minutes. Consider normalizing e.target.value (e.g., value.slice(0, 5) or via Date/Intl) before storing it in timeDraft, and validate only the normalized HH:mm string to preserve the invariant used elsewhere.

Suggested implementation:

 // 校验规范化后的 "HH:mm" 格式
const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;

// 规范化 <input type="time"> 的值,将 "HH:mm:ss" 等形式裁剪为 "HH:mm"
const normalizeTimeValue = (value: string): string => {
  if (!value) return value;
  // 绝大多数浏览器时间字符串形如 "HH:mm" 或 "HH:mm:ss"
  // 只要前 5 位是 "HH:mm" 形态,就裁剪前 5 位作为规范格式
  if (value.length >= 5 && value[2] === ':') {
    return value.slice(0, 5);
  }
  return value;
};

interface SchedulePanelProps {

To fully implement the behavior you described, you should also update the time input handling code (not visible in the snippet). Wherever you currently handle the time input (e.g. onChange, onBlur, or wherever e.target.value is stored into timeDraft or validated via TIME_PATTERN):

  1. Normalize the raw value before using it:
    • Change something like:
      • const value = e.target.value;
    • To:
      • const value = normalizeTimeValue(e.target.value);
  2. Use the normalized value for:
    • setTimeDraft(value);
    • Any TIME_PATTERN.test(...) calls.
    • Any other schedule creation/update logic that relies on time strings.
  3. Ensure that any initial values coming from props or external sources that represent a time value are either already HH:mm or also passed through normalizeTimeValue before being stored/validated, to keep the invariant consistent.

Comment thread src/stores/appStore.ts Outdated
@zmdyy0318

Copy link
Copy Markdown
Contributor Author

旧用户配置忽略,需要用户重新配置
自测通过

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

更细粒度(分钟)的定时策略

1 participant