fixed reminder
This commit is contained in:
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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 / {
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user