feat: AI chat conclusion + survey completion & management (#95)
* fix: increase assistant chat input height from 1 to 3 rows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Anthropic prompt caching to assistant chat Cache the static system prompt and conversation history prefix across turns, reducing input token costs by ~80% on multi-turn conversations. RAG context is intentionally uncached since it changes per query. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Microsoft Learn MCP integration + refine assistant system prompt - Integrate Microsoft Learn MCP server via Anthropic's MCP connector for real-time documentation lookups (docs search, fetch, code samples) - Refine system prompt: clear persona, structured answer guidelines, when to use RAG flows vs Microsoft Learn, guardrails against fabrication - Add ENABLE_MCP_MICROSOFT_LEARN config toggle (default: True) - Fix bugs from prior edit: wrong MCP URL, broken indentation, undefined usage/token variables, NOT_GIVEN for disabled MCP params - Log MCP tool usage and cache performance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: AI chat session conclusion + survey completion & management AI Assistant - Conclude Session: - 3-step modal: select outcome (resolved/escalated/paused), add notes, AI-generated summary - AI generates structured ticket notes from conversation transcript (PSA-ready format) - Copy to clipboard for pasting into ticketing systems - "Resume in New Chat" for paused sessions (pre-loads context into new chat) - Backend: POST /chats/{id}/conclude endpoint, conclusion_summary/outcome/concluded_at fields - Migration 048: add conclusion fields to assistant_chats Survey Completion Flow: - Email-to-self option after submission (branded HTML email with formatted responses) - Finish button navigates to /survey/thank-you page - Thank you page with close-window message and feedback email callout - Already-submitted state updated with same messaging - Backend: POST /survey/email-copy public endpoint Survey Admin Management: - Read/unread indicators (cyan dot, bold name, auto-mark on expand) - Unread count stat card - Per-row context menu: mark read/unread, archive/unarchive, delete - Bulk actions bar: select all, mark read/unread, archive, delete - Show Archived toggle to filter archived responses - Backend: 7 new admin endpoints (read, unread, archive, unarchive, delete, bulk) - Migration 049: add is_read, archived_at to survey_responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: initialize VerifyEmailPage state from token to avoid setState in effect Moves the no-token error case from useEffect into initial state to satisfy the react-hooks/set-state-in-effect ESLint rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #95.
This commit is contained in:
@@ -35,6 +35,8 @@ from app.schemas.assistant_chat import (
|
||||
ChatUpdateRequest,
|
||||
RetentionSettingsResponse,
|
||||
RetentionSettingsUpdate,
|
||||
ConcludeChatRequest,
|
||||
ConcludeChatResponse,
|
||||
)
|
||||
from app.schemas.copilot import SuggestedFlow
|
||||
from app.services import assistant_chat_service
|
||||
@@ -203,6 +205,67 @@ async def post_message(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/chats/{chat_id}/conclude", response_model=ConcludeChatResponse)
|
||||
@limiter.limit("10/minute")
|
||||
async def conclude_chat(
|
||||
request: Request,
|
||||
chat_id: UUID,
|
||||
data: ConcludeChatRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
_: None = Depends(require_engineer_or_admin),
|
||||
):
|
||||
"""Conclude a chat session and generate ticket-ready summary."""
|
||||
_require_ai_enabled()
|
||||
|
||||
result = await db.execute(
|
||||
select(AssistantChat).where(
|
||||
AssistantChat.id == chat_id,
|
||||
AssistantChat.user_id == current_user.id,
|
||||
)
|
||||
)
|
||||
chat = result.scalar_one_or_none()
|
||||
if not chat:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Chat not found")
|
||||
|
||||
if chat.concluded_at:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Chat already concluded",
|
||||
)
|
||||
|
||||
if chat.message_count < 2:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Chat must have at least one exchange before concluding",
|
||||
)
|
||||
|
||||
try:
|
||||
summary = await assistant_chat_service.generate_conclusion_summary(
|
||||
chat=chat,
|
||||
outcome=data.outcome,
|
||||
notes=data.notes,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("Failed to generate conclusion summary: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail="Failed to generate summary. Please try again.",
|
||||
)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
chat.conclusion_outcome = data.outcome
|
||||
chat.conclusion_summary = summary
|
||||
chat.concluded_at = now
|
||||
await db.commit()
|
||||
|
||||
return ConcludeChatResponse(
|
||||
summary=summary,
|
||||
outcome=data.outcome,
|
||||
concluded_at=now,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/chats/{chat_id}", response_model=ChatDetailResponse)
|
||||
async def update_chat(
|
||||
chat_id: UUID,
|
||||
|
||||
Reference in New Issue
Block a user