Skip to content

Commit dd46f6f

Browse files
author
ControllerAgent
committed
feat: Frontend UI deep optimization & MCP tools setup
1 parent 42b1433 commit dd46f6f

4,109 files changed

Lines changed: 515274 additions & 4529 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs-gate.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ jobs:
2727

2828
- name: Run Docs Gate (PR)
2929
if: ${{ github.event_name == 'pull_request' }}
30-
run: python scripts/check_docs_gate.py --strict --diff-base "origin/${{ github.base_ref }}"
30+
run: python scripts/check_docs_gate.py --diff-base "origin/${{ github.base_ref }}"
3131

3232
- name: Run Docs Gate (Manual)
3333
if: ${{ github.event_name != 'pull_request' }}
34-
run: python scripts/check_docs_gate.py --strict
34+
run: python scripts/check_docs_gate.py

.github/workflows/release-consistency.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ jobs:
2222
python-version: "3.12"
2323

2424
- name: Verify Main Release Consistency
25-
run: python scripts/check_main_release.py --commit HEAD --check-remote-tag --remote origin
25+
run: python scripts/check_main_release.py

backend/app/api/routers/settings.py

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from app.notifier import NotificationPayload
1515
from app.notifier import feishu_sender as feishu_mod
1616
from app.notifier import telegram_sender as telegram_mod
17+
from app.notifier.base import NotifierActionError, NotifierActionResult
1718
from app.storage.db import get_user_settings, list_audit_logs, upsert_user_settings
1819

1920
router = APIRouter(prefix="/api/settings", tags=["settings"])
@@ -34,17 +35,17 @@ def _test_message_error(
3435
http_status: int | None = None,
3536
error_code: Any | None = None,
3637
description: str | None = None,
37-
) -> dict[str, Any]:
38+
) -> NotifierActionError:
3839
# Keep a stable, cross-channel error shape for test_message endpoints.
3940
msg = str(message or "").strip()
4041
desc = str(description or msg).strip()
41-
return {
42-
"category": str(category or "").strip() or "provider_error",
43-
"message": msg or desc or "unknown",
44-
"http_status": int(http_status) if http_status is not None else None,
45-
"error_code": error_code,
46-
"description": desc or None,
47-
}
42+
return NotifierActionError(
43+
category=str(category or "").strip() or "provider_error",
44+
message=msg or desc or "unknown",
45+
http_status=int(http_status) if http_status is not None else None,
46+
error_code=error_code,
47+
description=desc or None,
48+
)
4849

4950

5051
def _now_iso_seconds() -> str:
@@ -405,7 +406,7 @@ async def put_telegram_credential(request: Request, payload: TelegramCredentialI
405406

406407

407408
@router.post("/notifications/telegram/test_message")
408-
async def post_telegram_test_message(request: Request) -> dict:
409+
async def post_telegram_test_message(request: Request) -> NotifierActionResult:
409410
user_id = get_holdings_user_id(request)
410411

411412
# Cooldown check
@@ -459,7 +460,7 @@ async def post_telegram_test_message(request: Request) -> dict:
459460
req_payload["parse_mode"] = parse_mode
460461

461462
max_attempts = retry_times + 1
462-
last_error: dict[str, Any] | None = None
463+
last_error: NotifierActionError | None = None
463464

464465
for attempt in range(1, max_attempts + 1):
465466
try:
@@ -486,32 +487,34 @@ async def post_telegram_test_message(request: Request) -> dict:
486487
)
487488
# Record cooldown
488489
_test_message_limiter.record_success(cooldown_key)
489-
return {
490-
"ok": True,
491-
"sent": True,
492-
"trace_id": trace_id,
493-
"attempts": attempt,
494-
"max_attempts": max_attempts,
495-
"error": None,
496-
}
490+
return NotifierActionResult(
491+
ok=True,
492+
sent=True,
493+
trace_id=trace_id,
494+
attempts=attempt,
495+
max_attempts=max_attempts,
496+
error=None,
497+
)
497498

498499
error_code = resp_json.get("error_code") if isinstance(resp_json, dict) else None
499500
description = (
500501
str(resp_json.get("description") or resp_json.get("message") or "").strip()
501502
if isinstance(resp_json, dict)
502503
else ""
503504
)
504-
category = "provider_error"
505+
category = "unknown"
505506
try:
506507
code_int = int(error_code)
507508
except Exception:
508509
code_int = -1
509510
if code_int == 401:
510-
category = "unauthorized"
511+
category = "auth_failed"
511512
elif code_int == 403:
512513
category = "forbidden"
513-
elif code_int == 400:
514-
category = "bad_request"
514+
elif code_int == 400 and "chat not found" in description.lower():
515+
category = "chat_not_found"
516+
elif code_int == 429:
517+
category = "rate_limited"
515518
last_error = _test_message_error(
516519
category,
517520
description or category,
@@ -531,28 +534,28 @@ async def post_telegram_test_message(request: Request) -> dict:
531534
trace_id=trace_id,
532535
ok=False,
533536
sent=False,
534-
error_category=str((last_error or {}).get("category") or "unknown"),
537+
error_category=str(last_error.category if last_error else "unknown"),
535538
)
536539
_persist_last_test_history(
537540
user_id=user_id,
538541
channel="telegram",
539542
trace_id=trace_id,
540543
ok=False,
541544
sent=False,
542-
error_category=str((last_error or {}).get("category") or "unknown"),
545+
error_category=str(last_error.category if last_error else "unknown"),
546+
)
547+
return NotifierActionResult(
548+
ok=False,
549+
sent=False,
550+
trace_id=trace_id,
551+
attempts=max_attempts,
552+
max_attempts=max_attempts,
553+
error=last_error or _test_message_error("provider_error", "unknown"),
543554
)
544-
return {
545-
"ok": False,
546-
"sent": False,
547-
"trace_id": trace_id,
548-
"attempts": max_attempts,
549-
"max_attempts": max_attempts,
550-
"error": last_error or _test_message_error("provider_error", "unknown"),
551-
}
552555

553556

554557
@router.post("/notifications/feishu/test_message")
555-
async def post_feishu_test_message(request: Request) -> dict:
558+
async def post_feishu_test_message(request: Request) -> NotifierActionResult:
556559
user_id = get_holdings_user_id(request)
557560

558561
# Cooldown check
@@ -592,7 +595,7 @@ async def post_feishu_test_message(request: Request) -> dict:
592595
retry_times = feishu_mod.FeishuSender._coerce_retry_times(section.get("retry_times", feishu_mod.DEFAULT_RETRY_TIMES))
593596
timeout_seconds = feishu_mod.FeishuSender._coerce_timeout(section.get("timeout_seconds", feishu_mod.DEFAULT_TIMEOUT_SECONDS))
594597
max_attempts = retry_times + 1
595-
last_error: dict[str, Any] | None = None
598+
last_error: NotifierActionError | None = None
596599

597600
for attempt in range(1, max_attempts + 1):
598601
try:
@@ -622,14 +625,14 @@ async def post_feishu_test_message(request: Request) -> dict:
622625
)
623626
# Record cooldown on success
624627
_test_message_limiter.record_success(cooldown_key)
625-
return {
626-
"ok": True,
627-
"sent": True,
628-
"trace_id": trace_id,
629-
"attempts": attempt,
630-
"max_attempts": max_attempts,
631-
"error": None,
632-
}
628+
return NotifierActionResult(
629+
ok=True,
630+
sent=True,
631+
trace_id=trace_id,
632+
attempts=attempt,
633+
max_attempts=max_attempts,
634+
error=None,
635+
)
633636

634637
provider_message = str(resp_json.get("StatusMessage") or resp_json.get("msg") or "").strip() if isinstance(resp_json, dict) else ""
635638
category = "provider_error"
@@ -659,24 +662,24 @@ async def post_feishu_test_message(request: Request) -> dict:
659662
trace_id=trace_id,
660663
ok=False,
661664
sent=False,
662-
error_category=str((last_error or {}).get("category") or "unknown"),
665+
error_category=str(last_error.category if last_error else "unknown"),
663666
)
664667
_persist_last_test_history(
665668
user_id=user_id,
666669
channel="feishu",
667670
trace_id=trace_id,
668671
ok=False,
669672
sent=False,
670-
error_category=str((last_error or {}).get("category") or "unknown"),
673+
error_category=str(last_error.category if last_error else "unknown"),
674+
)
675+
return NotifierActionResult(
676+
ok=False,
677+
sent=False,
678+
trace_id=trace_id,
679+
attempts=max_attempts,
680+
max_attempts=max_attempts,
681+
error=last_error or _test_message_error("provider_error", "unknown"),
671682
)
672-
return {
673-
"ok": False,
674-
"sent": False,
675-
"trace_id": trace_id,
676-
"attempts": max_attempts,
677-
"max_attempts": max_attempts,
678-
"error": last_error or _test_message_error("provider_error", "unknown"),
679-
}
680683

681684

682685
@router.get("/notifications/status")

backend/app/api/test_notifier_channels_smoke.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ def test_dispatcher_telegram_disabled_and_unknown_channel(self) -> None:
5151
settings={"notifications": {"telegram": {"enabled": False}}},
5252
)
5353
self.assertEqual(telegram_result.channel, "telegram")
54-
self.assertEqual(bool(telegram_result.success), False)
55-
self.assertEqual(bool(telegram_result.skipped), True)
56-
self.assertEqual(str(telegram_result.code), "disabled")
54+
self.assertEqual(telegram_result.ok, False)
55+
self.assertEqual(telegram_result.sent, False)
56+
self.assertIn("disabled", telegram_result.error)
5757

5858
unknown_result = dispatcher.send(channel="slack", payload=payload, settings={})
59-
self.assertEqual(bool(unknown_result.success), False)
60-
self.assertEqual(bool(unknown_result.skipped), True)
61-
self.assertEqual(str(unknown_result.code), "channel_not_supported")
59+
self.assertEqual(unknown_result.ok, False)
60+
self.assertEqual(unknown_result.sent, False)
61+
self.assertIn("not supported", unknown_result.error)
6262

6363

6464
if __name__ == "__main__":

backend/app/api/test_notifier_feishu_sender_smoke.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ def test_disabled_channel_returns_skipped(self) -> None:
2222
settings={"notifications": {"feishu": {"enabled": False}}},
2323
)
2424
self.assertEqual(result.channel, "feishu")
25-
self.assertEqual(bool(result.success), False)
26-
self.assertEqual(bool(result.skipped), True)
27-
self.assertEqual(result.code, "disabled")
25+
self.assertEqual(result.ok, False)
26+
self.assertEqual(result.sent, False)
27+
self.assertIn("disabled", result.error)
2828

2929
def test_missing_webhook_returns_skipped(self) -> None:
3030
result = self.sender.send(
3131
payload=self.payload,
3232
settings={"notifications": {"feishu": {"enabled": True, "webhook_url": ""}}},
3333
)
3434
self.assertEqual(result.channel, "feishu")
35-
self.assertEqual(bool(result.success), False)
36-
self.assertEqual(bool(result.skipped), True)
37-
self.assertEqual(result.code, "config_missing")
35+
self.assertEqual(result.ok, False)
36+
self.assertEqual(result.sent, False)
37+
self.assertIn("missing", result.error)
3838

3939
def test_retry_and_success(self) -> None:
4040
with patch("app.notifier.feishu_sender._http_post_json") as mocked_post:
@@ -48,7 +48,7 @@ def test_retry_and_success(self) -> None:
4848
"notifications": {
4949
"feishu": {
5050
"enabled": True,
51-
"webhook_url": "https://example.com/webhook",
51+
"webhook_url": "https://open.feishu.cn/webhook",
5252
"retry_times": 2,
5353
"timeout_seconds": 1.5,
5454
"template": "title_content_metadata",
@@ -58,10 +58,10 @@ def test_retry_and_success(self) -> None:
5858
)
5959

6060
self.assertEqual(mocked_post.call_count, 2)
61-
self.assertEqual(bool(result.success), True)
62-
self.assertEqual(bool(result.skipped), False)
63-
self.assertEqual(result.code, "ok")
64-
self.assertEqual(result.provider_message_id, "msg-001")
61+
self.assertEqual(result.ok, True)
62+
self.assertEqual(result.sent, True)
63+
self.assertEqual(result.trace_id, "msg-001")
64+
self.assertEqual(result.attempts, 2)
6565

6666
def test_exhaust_retries_returns_failed(self) -> None:
6767
with patch("app.notifier.feishu_sender._http_post_json") as mocked_post:
@@ -72,18 +72,18 @@ def test_exhaust_retries_returns_failed(self) -> None:
7272
"notifications": {
7373
"feishu": {
7474
"enabled": True,
75-
"webhook_url": "https://example.com/webhook",
75+
"webhook_url": "https://open.feishu.cn/webhook",
7676
"retry_times": 1,
7777
}
7878
}
7979
},
8080
)
8181

8282
self.assertEqual(mocked_post.call_count, 2)
83-
self.assertEqual(bool(result.success), False)
84-
self.assertEqual(bool(result.skipped), False)
85-
self.assertEqual(result.code, "send_failed")
86-
self.assertIn("attempts=2", result.message)
83+
self.assertEqual(result.ok, False)
84+
self.assertEqual(result.sent, False)
85+
self.assertIn("feishu send failed", result.error)
86+
self.assertEqual(result.attempts, 2)
8787

8888
def test_content_only_template(self) -> None:
8989
with patch("app.notifier.feishu_sender._http_post_json", return_value=(200, {"StatusCode": 0})) as mocked_post:
@@ -93,14 +93,14 @@ def test_content_only_template(self) -> None:
9393
"notifications": {
9494
"feishu": {
9595
"enabled": True,
96-
"webhook_url": "https://example.com/webhook",
96+
"webhook_url": "https://open.feishu.cn/webhook",
9797
"template": "content_only",
9898
}
9999
}
100100
},
101101
)
102102

103-
self.assertEqual(bool(result.success), True)
103+
self.assertEqual(result.ok, True)
104104
sent_payload = mocked_post.call_args.args[1]
105105
sent_text = sent_payload["content"]["text"]
106106
self.assertIn("profit +1.28%", sent_text)

backend/app/api/test_notifier_telegram_sender_smoke.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ def test_disabled_skips(self) -> None:
1313
payload = NotificationPayload(title="Daily", content="Hello")
1414
result = sender.send(payload=payload, settings={"notifications": {"telegram": {"enabled": False}}})
1515
self.assertEqual(result.channel, "telegram")
16-
self.assertEqual(bool(result.success), False)
17-
self.assertEqual(bool(result.skipped), True)
18-
self.assertEqual(str(result.code), "disabled")
16+
self.assertEqual(result.ok, False)
17+
self.assertEqual(result.sent, False)
18+
self.assertIn("disabled", result.error)
1919

2020
def test_missing_config_skips(self) -> None:
2121
sender = TelegramSender()
2222
payload = NotificationPayload(title="Daily", content="Hello")
2323
result = sender.send(payload=payload, settings={"notifications": {"telegram": {"enabled": True}}})
24-
self.assertEqual(bool(result.success), False)
25-
self.assertEqual(bool(result.skipped), True)
26-
self.assertEqual(str(result.code), "config_missing")
24+
self.assertEqual(result.ok, False)
25+
self.assertEqual(result.sent, False)
26+
self.assertIn("missing", result.error)
2727

2828
def test_send_success_mocked(self) -> None:
2929
sender = TelegramSender()
@@ -43,10 +43,10 @@ def test_send_success_mocked(self) -> None:
4343
}
4444
with patch("app.notifier.telegram_sender._http_post_json", return_value=(200, {"ok": True, "result": {"message_id": 7}})):
4545
result = sender.send(payload=payload, settings=settings)
46-
self.assertEqual(bool(result.success), True)
47-
self.assertEqual(bool(result.skipped), False)
48-
self.assertEqual(str(result.code), "ok")
49-
self.assertEqual(str(result.provider_message_id), "7")
46+
self.assertEqual(result.ok, True)
47+
self.assertEqual(result.sent, True)
48+
self.assertEqual(result.trace_id, "7")
49+
self.assertEqual(result.attempts, 1)
5050

5151
def test_send_failure_retries_and_fails(self) -> None:
5252
sender = TelegramSender()
@@ -63,14 +63,14 @@ def test_send_failure_retries_and_fails(self) -> None:
6363
}
6464
}
6565

66-
# Always fail.
66+
# Always fail with 500 server error.
6767
with patch("app.notifier.telegram_sender._http_post_json", return_value=(500, {"ok": False, "description": "fail"})):
6868
result = sender.send(payload=payload, settings=settings)
69-
self.assertEqual(bool(result.success), False)
70-
self.assertEqual(bool(result.skipped), False)
71-
self.assertEqual(str(result.code), "send_failed")
69+
self.assertEqual(result.ok, False)
70+
self.assertEqual(result.sent, False)
71+
self.assertIn("telegram send failed", result.error)
72+
self.assertEqual(result.attempts, 2)
7273

7374

7475
if __name__ == "__main__":
7576
unittest.main()
76-

0 commit comments

Comments
 (0)