feat: add AstrBot Pages console, public API, and contextual scheduling#76
feat: add AstrBot Pages console, public API, and contextual scheduling#76Justice-ocr wants to merge 44 commits into
Conversation
There was a problem hiding this comment.
Sorry @Justice-ocr, your pull request is larger than the review limit of 150000 diff characters
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive contextual timing scheduler and a feature-rich Web administration console for the proactive chat plugin, including a lightweight public API, event text extraction, and platform conversation insights. The frontend is built using React and bundled via a custom Babel-based build tool. Key feedback includes addressing missing imports in web_admin_server.py that would cause runtime errors, fixing a parsing bug for composite time expressions and adding exception handling for integer conversions in task_scheduler.py, preventing premature returns on exceptions in llm_adapter.py's history extraction, and safeguarding the frontend against invalid timezone configurations in formatters.js to prevent rendering crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| from __future__ import annotations | ||
|
|
||
| import asyncio | ||
| import inspect | ||
| import json | ||
| import math | ||
| import os |
There was a problem hiding this comment.
在 _open_directory_payload 和 update_config 等方法中使用了 sys、subprocess、Path (来自 pathlib) 以及 JSONResponse (来自 fastapi.responses),但这些模块和类并未在文件顶部导入。这会在运行时触发 NameError。建议在文件顶部补齐这些导入。
| from __future__ import annotations | |
| import asyncio | |
| import inspect | |
| import json | |
| import math | |
| import os | |
| from __future__ import annotations | |
| import asyncio | |
| import inspect | |
| import json | |
| import math | |
| import os | |
| import sys | |
| import subprocess | |
| from pathlib import Path | |
| from fastapi.responses import JSONResponse |
| def _extract_explicit_delay_minutes(self, text: str) -> int | None: | ||
| if "半小时" in text or "半个小时" in text: | ||
| return 30 | ||
|
|
||
| minute_match = re.search(r"(\d{1,3})\s*(分钟|分|mins?|minutes?)\s*(后|later)?", text) | ||
| if minute_match: | ||
| value = int(minute_match.group(1)) | ||
| if 1 <= value <= 1440: | ||
| return value | ||
|
|
||
| hour_match = re.search(r"(\d{1,2})\s*(个)?\s*(小时|钟头|hours?|hrs?|h)\s*(后|later)?", text) | ||
| if hour_match: | ||
| value = int(hour_match.group(1)) | ||
| if 1 <= value <= 48: | ||
| return value * 60 | ||
|
|
||
| return None |
There was a problem hiding this comment.
_extract_explicit_delay_minutes 在解析复合时间表达式(例如 "1小时10分钟")时存在逻辑缺陷。由于它会优先匹配 "10分钟" 并直接返回,导致 "1小时" 的部分被完全忽略,从而返回 10 分钟而不是 70 分钟。建议将其改为累加解析,以同时支持单一和复合时间表达式。
def _extract_explicit_delay_minutes(self, text: str) -> int | None:
if "半小时" in text or "半个小时" in text:
return 30
total_minutes = 0
has_match = False
hour_match = re.search(r"(\\d{1,2})\\s*(个)?\\s*(小时|钟头|hours?|hrs?|h)", text)
if hour_match:
total_minutes += int(hour_match.group(1)) * 60
has_match = True
minute_match = re.search(r"(\\d{1,3})\\s*(分钟|分|mins?|minutes?)", text)
if minute_match:
total_minutes += int(minute_match.group(1))
has_match = True
if has_match and 1 <= total_minutes <= 2880:
return total_minutes
return None| def _get_schedule_bounds(self, schedule_conf: dict) -> tuple[int, int]: | ||
| min_interval = int(schedule_conf.get("min_interval_minutes", 30)) * 60 | ||
| max_interval = max( | ||
| min_interval, int(schedule_conf.get("max_interval_minutes", 900)) * 60 | ||
| ) | ||
| return min_interval, max_interval |
There was a problem hiding this comment.
_get_schedule_bounds 在从配置中读取 min_interval_minutes 和 max_interval_minutes 并转换为整数时,缺少异常处理。如果用户在配置中填写了非数字字符串或空值,会导致 ValueError 或 TypeError 从而使调度器崩溃。建议添加异常处理以提高健壮性。
def _get_schedule_bounds(self, schedule_conf: dict) -> tuple[int, int]:
try:
min_val = int(schedule_conf.get("min_interval_minutes", 30))
except (ValueError, TypeError):
min_val = 30
try:
max_val = int(schedule_conf.get("max_interval_minutes", 900))
except (ValueError, TypeError):
max_val = 900
min_interval = min_val * 60
max_interval = max(min_interval, max_val * 60)
return min_interval, max_interval| def _extract_history_message_text(self, message: Any) -> str: | ||
| if hasattr(message, "to_dict"): | ||
| try: | ||
| message = message.to_dict() | ||
| except Exception: | ||
| return "" |
There was a problem hiding this comment.
在 _extract_history_message_text 中,如果 message.to_dict() 抛出异常,当前代码会直接返回 ""。然而,该对象可能仍然拥有一个有效的 content 属性,可以通过后续的 getattr(message, "content", "") 兜底逻辑正常获取。建议在 except 块中使用 pass 而不是直接返回,以允许兜底逻辑执行,提高防御性编程的容错能力。
| def _extract_history_message_text(self, message: Any) -> str: | |
| if hasattr(message, "to_dict"): | |
| try: | |
| message = message.to_dict() | |
| except Exception: | |
| return "" | |
| def _extract_history_message_text(self, message: Any) -> str: | |
| if hasattr(message, "to_dict"): | |
| try: | |
| message = message.to_dict() | |
| except Exception: | |
| pass |
|
|
||
| // IANA 模式交给 Intl 处理,可正确覆盖夏令时等复杂时区规则。 | ||
| const formatter = new Intl.DateTimeFormat('zh-CN', { | ||
| year: includeYear ? 'numeric' : undefined, | ||
| month: '2-digit', | ||
| day: '2-digit', | ||
| hour: '2-digit', | ||
| minute: '2-digit', | ||
| second: includeSeconds ? '2-digit' : undefined, | ||
| hour12: false, |
There was a problem hiding this comment.
如果用户在配置中填写了无效的时区字符串(例如拼写错误),Intl.DateTimeFormat 会抛出 RangeError 并导致整个前端页面渲染崩溃。建议使用 try-catch 块包裹该实例化过程,并在捕获到错误时优雅地降级到默认时区(如 'Asia/Shanghai'),以防止页面白屏。
let formatter;
try {
formatter = new Intl.DateTimeFormat('zh-CN', {
year: includeYear ? 'numeric' : undefined,
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: includeSeconds ? '2-digit' : undefined,
hour12: false,
timeZone: tz.value,
});
} catch (e) {
formatter = new Intl.DateTimeFormat('zh-CN', {
year: includeYear ? 'numeric' : undefined,
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: includeSeconds ? '2-digit' : undefined,
hour12: false,
timeZone: 'Asia/Shanghai',
});
}…duling Codex/ai pages contextual scheduling
d0885ca to
073d9db
Compare
|
补充说明一下本 PR 后续根据 review 和实际运行反馈追加的修正。本 PR 仍然是 AI 辅助生成/迭代的改动,覆盖面比较大,仍希望上游维护者继续谨慎 review。 针对 review 中指出的问题,当前分支已经做了这些处理:
后续实际测试里还补了几类运行时问题:
最新提交
行为边界:这个逻辑只响应 AstrBot 的 本地已跑过: |
说明
这个 PR 由 AI 辅助生成并在本地做了基础验证。由于改动范围较大、实现仍比较粗糙,各方面都还有待完善:代码结构、UI 细节、兼容性、测试覆盖和功能取舍都需要上游维护者进一步审阅、调整或拆分。建议先按 draft PR 处理,不建议未经复核直接合并。
与上游 main 对比后的主要变化
相对上游
DBJD-CR:main,本分支包含 33 个提交,约 48 个文件变更,主要解决或推进了以下问题:1. AstrBot Pages 管理页面支持 / #74
pages/proactive-chat/下的 AstrBot Pages 管理页面。core/web_admin_server.py。save_config/get_config,保存后会回读校验,避免“保存后刷新又变回去”。2. 插件间公开 API / #22
core/public_api.py,并在main.py混入。get_proactive_chat_statuslist_proactive_chat_jobslist_proactive_chat_sessionstrigger_proactive_chatreschedule_proactive_chatcancel_proactive_chat_job3. 基于语境的触发时间预测 / #26
enable_contextual_timingcontextual_timing_history_countmin_interval_minutes和max_interval_minutes限制,不会突破用户配置边界。4. 上下文与平台历史增强
已做的本地验证
node --check pages/proactive-chat/page-app.jspython -m json.tool _conf_schema.jsonpython -m compileall -q .git diff --check风险与待完善点