fixed reminder

This commit is contained in:
2026-06-11 12:34:35 +03:00
parent 41cbef61a9
commit 66e1b0e29e
7 changed files with 104 additions and 19 deletions
+2 -1
View File
@@ -27,7 +27,8 @@ TOOLS_INSTRUCTIONS = """
- Покупки: list_shopping_lists, create_shopping_list, add_shopping_items, check_shopping_item, remove_shopping_item, delete_shopping_list. - Покупки: list_shopping_lists, create_shopping_list, add_shopping_items, check_shopping_item, remove_shopping_item, delete_shopping_list.
- «Добавь в список покупок» → add_shopping_items (list_name + товары). «Что купить» → list_shopping_lists. Не выдумывай списки. - «Добавь в список покупок» → add_shopping_items (list_name + товары). «Что купить» → list_shopping_lists. Не выдумывай списки.
- Напоминания: list_reminders, create_reminder, update_reminder, delete_reminder, complete_reminder. - Напоминания: list_reminders, create_reminder, update_reminder, delete_reminder, complete_reminder.
- «Напомни через 15 минут», «завтра утром», «12 мая 2027 в 12:16» → create_reminder с due_at в ISO (часовой пояс из [Текущее время]). - «Напомни через 15 минут», «завтра утром», «12 мая в 9:00» → create_reminder с due_at в ISO (часовой пояс из [Текущее время]).
- День рождения, Новый год и другие праздники → recurrence yearly.
- Относительное время считай от «Сейчас» в контексте. «Утром» ≈ 09:00, «вечером» ≈ 19:00, если не уточнено иначе. - Относительное время считай от «Сейчас» в контексте. «Утром» ≈ 09:00, «вечером» ≈ 19:00, если не уточнено иначе.
""".strip() """.strip()
+12 -1
View File
@@ -14,7 +14,14 @@ RECURRENCE_NONE = "none"
RECURRENCE_DAILY = "daily" RECURRENCE_DAILY = "daily"
RECURRENCE_WEEKLY = "weekly" RECURRENCE_WEEKLY = "weekly"
RECURRENCE_MONTHLY = "monthly" RECURRENCE_MONTHLY = "monthly"
VALID_RECURRENCE = frozenset({RECURRENCE_NONE, RECURRENCE_DAILY, RECURRENCE_WEEKLY, RECURRENCE_MONTHLY}) RECURRENCE_YEARLY = "yearly"
VALID_RECURRENCE = frozenset({
RECURRENCE_NONE,
RECURRENCE_DAILY,
RECURRENCE_WEEKLY,
RECURRENCE_MONTHLY,
RECURRENCE_YEARLY,
})
def _utcnow() -> datetime: def _utcnow() -> datetime:
@@ -50,6 +57,10 @@ def _advance_due(due_at: datetime, recurrence: str) -> datetime:
year += 1 year += 1
day = min(due_at.day, calendar.monthrange(year, month)[1]) day = min(due_at.day, calendar.monthrange(year, month)[1])
return due_at.replace(year=year, month=month, day=day) return due_at.replace(year=year, month=month, day=day)
if recurrence == RECURRENCE_YEARLY:
year = due_at.year + 1
day = min(due_at.day, calendar.monthrange(year, due_at.month)[1])
return due_at.replace(year=year, day=day)
return due_at return due_at
+6 -3
View File
@@ -649,8 +649,8 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"all_day": {"type": "boolean"}, "all_day": {"type": "boolean"},
"recurrence": { "recurrence": {
"type": "string", "type": "string",
"enum": ["none", "daily", "weekly", "monthly"], "enum": ["none", "daily", "weekly", "monthly", "yearly"],
"description": "Повтор", "description": "Повтор (yearly — день рождения, Новый год)",
}, },
}, },
"required": ["title", "due_at"], "required": ["title", "due_at"],
@@ -670,7 +670,10 @@ TOOL_DEFINITIONS: list[dict[str, Any]] = [
"due_at": {"type": "string"}, "due_at": {"type": "string"},
"notes": {"type": "string"}, "notes": {"type": "string"},
"all_day": {"type": "boolean"}, "all_day": {"type": "boolean"},
"recurrence": {"type": "string", "enum": ["none", "daily", "weekly", "monthly"]}, "recurrence": {
"type": "string",
"enum": ["none", "daily", "weekly", "monthly", "yearly"],
},
"enabled": {"type": "boolean"}, "enabled": {"type": "boolean"},
}, },
"required": ["reminder_id"], "required": ["reminder_id"],
+52
View File
@@ -0,0 +1,52 @@
"""Идемпотентно добавляет ежегодные напоминания: ДР 12 мая и Новый год."""
from sqlalchemy import select
from app.db.base import SessionLocal
from app.db.models import Reminder
from app.reminders.service import RECURRENCE_YEARLY, RemindersService
DEFAULTS = (
{
"title": "День рождения",
"due_at": "2027-05-12T09:00:00+03:00",
"notes": "С днём рождения!",
"all_day": True,
"recurrence": RECURRENCE_YEARLY,
},
{
"title": "Новый год",
"due_at": "2026-12-31T23:55:00+03:00",
"notes": "С наступающим Новым годом!",
"all_day": False,
"recurrence": RECURRENCE_YEARLY,
},
)
def main() -> None:
db = SessionLocal()
try:
service = RemindersService(db)
for item in DEFAULTS:
exists = db.scalar(
select(Reminder.id)
.where(
Reminder.title == item["title"],
Reminder.recurrence == RECURRENCE_YEARLY,
Reminder.enabled.is_(True),
)
.limit(1)
)
if exists:
print(f"skip: {item['title']} (id={exists})")
continue
result = service.create(**item)
reminder = result["reminder"]
print(f"created: {reminder['title']} · {reminder['due_at_local']} · yearly")
finally:
db.close()
if __name__ == "__main__":
main()
+4
View File
@@ -9,9 +9,13 @@ server {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Connection "";
proxy_buffering off; proxy_buffering off;
proxy_cache off; proxy_cache off;
chunked_transfer_encoding off; chunked_transfer_encoding off;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
} }
location / { location / {
+14 -1
View File
@@ -271,8 +271,18 @@ export const api = {
} }
}; };
try {
while (true) { while (true) {
const { done, value } = await reader.read(); let done = false;
let value: Uint8Array | undefined;
try {
({ done, value } = await reader.read());
} catch {
throw new Error(
"Соединение прервалось (таймаут прокси). Обновите чат — ответ мог уже сохраниться.",
);
}
if (value) { if (value) {
buffer += decoder.decode(value, { stream: !done }); buffer += decoder.decode(value, { stream: !done });
} }
@@ -288,6 +298,9 @@ export const api = {
break; break;
} }
} }
} finally {
reader.releaseLock();
}
}, },
pomodoroStatus: () => request<PomodoroStatus>("/api/v1/pomodoro/status"), pomodoroStatus: () => request<PomodoroStatus>("/api/v1/pomodoro/status"),
+1
View File
@@ -252,6 +252,7 @@ export default function Reminders() {
<option value="daily">Каждый день</option> <option value="daily">Каждый день</option>
<option value="weekly">Каждую неделю</option> <option value="weekly">Каждую неделю</option>
<option value="monthly">Каждый месяц</option> <option value="monthly">Каждый месяц</option>
<option value="yearly">Каждый год</option>
</select> </select>
</label> </label>
<label> <label>