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:
chihlasm
2026-03-05 22:43:02 -05:00
committed by GitHub
parent b46f41e7bb
commit 0fb1ef33a0
21 changed files with 1630 additions and 70 deletions

View File

@@ -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,