Skip to content

Commit 22f1e0d

Browse files
committed
fix(kba): fix draft deletion bug and add collapsible AutoGenSettings
- Fix delete draft error: use response.items instead of response.drafts - Make AutoGenSettings card collapsible with chevron icon - Starts collapsed to reduce visual dominance - Smooth slide-down animation when expanded - Status badge visible in collapsed header - Clickable header with keyboard support (Enter key)
1 parent 4165045 commit 22f1e0d

14 files changed

Lines changed: 1472 additions & 12 deletions

File tree

backend/=3.10.4

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Collecting APScheduler
2+
Downloading apscheduler-3.11.2-py3-none-any.whl.metadata (6.4 kB)
3+
Collecting tzlocal>=3.0 (from APScheduler)
4+
Downloading tzlocal-5.3.1-py3-none-any.whl.metadata (7.6 kB)
5+
Downloading apscheduler-3.11.2-py3-none-any.whl (64 kB)
6+
Downloading tzlocal-5.3.1-py3-none-any.whl (18 kB)
7+
Installing collected packages: tzlocal, APScheduler
8+
9+
Successfully installed APScheduler-3.11.2 tzlocal-5.3.1

backend/app.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,42 @@
105105
# Service instances live in operations.py so every interface shares them
106106

107107

108+
# ============================================================================
109+
# APPLICATION LIFECYCLE - Scheduler Management
110+
# ============================================================================
111+
112+
@app.before_serving
113+
async def startup():
114+
"""Initialize scheduler on application startup"""
115+
from scheduler import start_scheduler
116+
import logging
117+
118+
logger = logging.getLogger(__name__)
119+
logger.info("Starting auto-generation scheduler...")
120+
121+
try:
122+
start_scheduler()
123+
logger.info("Scheduler started successfully")
124+
except Exception as e:
125+
logger.error(f"Failed to start scheduler: {e}", exc_info=True)
126+
127+
128+
@app.after_serving
129+
async def shutdown():
130+
"""Cleanup scheduler on application shutdown"""
131+
from scheduler import stop_scheduler
132+
import logging
133+
134+
logger = logging.getLogger(__name__)
135+
logger.info("Stopping auto-generation scheduler...")
136+
137+
try:
138+
stop_scheduler()
139+
logger.info("Scheduler stopped successfully")
140+
except Exception as e:
141+
logger.error(f"Failed to stop scheduler: {e}", exc_info=True)
142+
143+
108144
# ============================================================================
109145
# UTILITY FUNCTIONS
110146
# ============================================================================
@@ -1182,6 +1218,41 @@ async def rest_kba_health():
11821218
})
11831219

11841220

1221+
# ============================================================================
1222+
# KBA AUTO-GENERATION ROUTES
1223+
# ============================================================================
1224+
1225+
@app.route("/api/kba/auto-gen/settings", methods=["GET"])
1226+
async def rest_kba_get_auto_gen_settings():
1227+
"""REST wrapper: get auto-generation settings."""
1228+
from operations import op_kba_get_auto_gen_settings
1229+
result = await op_kba_get_auto_gen_settings()
1230+
return jsonify(result.model_dump())
1231+
1232+
1233+
@app.route("/api/kba/auto-gen/settings", methods=["PATCH"])
1234+
async def rest_kba_update_auto_gen_settings():
1235+
"""REST wrapper: update auto-generation settings."""
1236+
from operations import op_kba_update_auto_gen_settings
1237+
from auto_gen_models import AutoGenSettingsUpdate
1238+
1239+
data = await request.get_json()
1240+
updates = AutoGenSettingsUpdate(**data)
1241+
result = await op_kba_update_auto_gen_settings(updates)
1242+
return jsonify(result.model_dump())
1243+
1244+
1245+
@app.route("/api/kba/auto-gen/trigger", methods=["POST"])
1246+
async def rest_kba_trigger_auto_gen():
1247+
"""REST wrapper: manually trigger auto-generation."""
1248+
from operations import op_kba_trigger_auto_gen
1249+
1250+
data = await request.get_json() or {}
1251+
user_id = data.get("user_id", "manual-trigger")
1252+
result = await op_kba_trigger_auto_gen(user_id)
1253+
return jsonify(result.model_dump())
1254+
1255+
11851256
# ============================================================================
11861257
# MCP JSON-RPC ENDPOINT
11871258
# ============================================================================

backend/auto_gen_models.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
Auto-Generation Settings Models
3+
4+
Pydantic models for automatic KBA draft generation configuration.
5+
Uses SQLModel for persistence.
6+
"""
7+
8+
from datetime import datetime
9+
from typing import Optional
10+
from uuid import UUID, uuid4
11+
12+
from pydantic import BaseModel, Field
13+
from sqlmodel import SQLModel, Field as SQLField
14+
15+
16+
# ============================================================================
17+
# SETTINGS MODELS
18+
# ============================================================================
19+
20+
class AutoGenSettings(BaseModel):
21+
"""Auto-generation configuration (READ operations)"""
22+
id: int = 1 # Singleton - always ID 1
23+
enabled: bool = Field(default=False, description="Enable/disable auto-generation")
24+
daily_limit: int = Field(
25+
default=5,
26+
ge=1,
27+
le=50,
28+
description="Number of drafts to generate per day"
29+
)
30+
schedule_time: str = Field(
31+
default="12:00",
32+
pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$",
33+
description="Time of day to run (HH:MM, 24-hour format)"
34+
)
35+
last_run_at: Optional[datetime] = Field(
36+
default=None,
37+
description="Timestamp of last successful run"
38+
)
39+
last_run_count: Optional[int] = Field(
40+
default=None,
41+
description="Number of drafts created in last run"
42+
)
43+
updated_at: datetime = Field(default_factory=datetime.now)
44+
45+
class Config:
46+
json_schema_extra = {
47+
"example": {
48+
"id": 1,
49+
"enabled": True,
50+
"daily_limit": 5,
51+
"schedule_time": "12:00",
52+
"last_run_at": "2026-03-04T12:00:15Z",
53+
"last_run_count": 5,
54+
"updated_at": "2026-03-04T10:30:00Z"
55+
}
56+
}
57+
58+
59+
class AutoGenSettingsUpdate(BaseModel):
60+
"""DTO for updating settings (PATCH /api/kba/auto-gen/settings)"""
61+
enabled: Optional[bool] = None
62+
daily_limit: Optional[int] = Field(None, ge=1, le=50)
63+
schedule_time: Optional[str] = Field(
64+
None,
65+
pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$"
66+
)
67+
68+
class Config:
69+
json_schema_extra = {
70+
"example": {
71+
"enabled": True,
72+
"daily_limit": 10
73+
}
74+
}
75+
76+
77+
class AutoGenSettingsTable(SQLModel, table=True):
78+
"""SQLModel table for auto-generation settings (singleton)"""
79+
__tablename__ = "auto_gen_settings"
80+
81+
id: int = SQLField(default=1, primary_key=True)
82+
enabled: bool = SQLField(default=False)
83+
daily_limit: int = SQLField(default=5)
84+
schedule_time: str = SQLField(default="12:00")
85+
last_run_at: Optional[datetime] = None
86+
last_run_count: Optional[int] = None
87+
updated_at: datetime = SQLField(default_factory=datetime.now)
88+
89+
90+
class AutoGenRunResult(BaseModel):
91+
"""Result of an auto-generation run"""
92+
success: bool
93+
drafts_created: int
94+
drafts_failed: int
95+
tickets_processed: int
96+
errors: list[str] = Field(default_factory=list)
97+
run_time_seconds: float
98+
timestamp: datetime = Field(default_factory=datetime.now)
99+
100+
class Config:
101+
json_schema_extra = {
102+
"example": {
103+
"success": True,
104+
"drafts_created": 5,
105+
"drafts_failed": 0,
106+
"tickets_processed": 5,
107+
"errors": [],
108+
"run_time_seconds": 25.3,
109+
"timestamp": "2026-03-04T12:00:25Z"
110+
}
111+
}

0 commit comments

Comments
 (0)