Files
resolutionflow/frontend/src/api/admin.ts
chihlasm 0fb1ef33a0 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>
2026-03-05 22:43:02 -05:00

200 lines
8.5 KiB
TypeScript

import api from './client'
import type {
DashboardMetrics,
ActivityEntry,
AuditLogListResponse,
PlanLimitConfig,
AccountOverrideResponse,
AccountOverrideCreate,
FeatureFlagResponse,
FeatureFlagCreate,
PlanDefaultUpdate,
AccountFeatureOverrideResponse,
AccountFeatureOverrideCreate,
AdminCategory,
GlobalCategoryCreate,
InviteCodeResponse,
InviteCodeCreateRequest,
UserDetailResponse,
AdminUserCreate,
AdminUserCreateResponse,
} from '@/types/admin'
export interface SurveyInviteResponse {
id: string
token: string
recipient_name: string
recipient_email: string | null
status: string
email_sent: boolean
created_at: string
completed_at: string | null
survey_url: string
}
export interface SurveyResponseDetail {
id: string
respondent_name: string | null
responses: Record<string, string | string[]>
source: 'invite' | 'direct'
invite_name: string | null
is_read: boolean
archived_at: string | null
created_at: string
}
export interface SurveyResponseListResponse {
responses: SurveyResponseDetail[]
total: number
this_week: number
unread: number
}
export const adminApi = {
// Dashboard
getDashboardMetrics: () =>
api.get<DashboardMetrics>('/admin/dashboard/metrics').then(r => r.data),
getDashboardActivity: () =>
api.get<ActivityEntry[]>('/admin/dashboard/activity').then(r => r.data),
// Users
createUser: (data: AdminUserCreate) =>
api.post<AdminUserCreateResponse>('/admin/users', data).then(r => r.data),
listUsers: (params?: Record<string, unknown>) =>
api.get('/admin/users', { params }).then(r => r.data),
getUser: (id: string) =>
api.get(`/admin/users/${id}`).then(r => r.data),
updateUserRole: (id: string, role: string) =>
api.put(`/admin/users/${id}/role`, { role }).then(r => r.data),
updateAccountRole: (id: string, account_role: string) =>
api.put(`/admin/users/${id}/account-role`, { account_role }).then(r => r.data),
updateSuperAdminStatus: (id: string, is_super_admin: boolean) =>
api.put(`/admin/users/${id}/super-admin`, { is_super_admin }).then(r => r.data),
deactivateUser: (id: string) =>
api.put(`/admin/users/${id}/deactivate`).then(r => r.data),
activateUser: (id: string) =>
api.put(`/admin/users/${id}/activate`).then(r => r.data),
moveUserAccount: (id: string, display_code: string) =>
api.put(`/admin/users/${id}/move-account`, { display_code }).then(r => r.data),
// Users - archive & delete
archiveUser: (id: string) =>
api.put(`/admin/users/${id}/archive`).then(r => r.data),
restoreUser: (id: string) =>
api.put(`/admin/users/${id}/restore`).then(r => r.data),
hardDeleteCheck: (id: string) =>
api.get<{ can_delete: boolean; blockers: Record<string, number> }>(`/admin/users/${id}/hard-delete-check`).then(r => r.data),
hardDeleteUser: (id: string) =>
api.delete(`/admin/users/${id}/hard-delete`),
// Users - quick invite
createInvite: (data: { email: string; account_display_code: string; role: string }) =>
api.post<{ id: string; email: string; code: string; role: string; account_display_code: string; email_sent: boolean }>('/admin/invites', data).then(r => r.data),
// Users - password reset
adminResetPassword: (id: string, mode: 'email_link' | 'temp_password') =>
api.post<{ message: string; temporary_password?: string; email_sent: boolean }>(`/admin/users/${id}/password-reset`, { mode }).then(r => r.data),
// Users - detail + subscription
getUserDetail: (id: string) =>
api.get<UserDetailResponse>(`/admin/users/${id}`).then(r => r.data),
updateUserSubscriptionPlan: (id: string, plan: string) =>
api.put(`/admin/users/${id}/subscription/plan`, { plan }).then(r => r.data),
extendUserTrial: (id: string, days: number) =>
api.put(`/admin/users/${id}/subscription/extend-trial`, { days }).then(r => r.data),
// Invite Codes
listInviteCodes: (params?: Record<string, unknown>) =>
api.get<InviteCodeResponse[]>('/invites', { params }).then(r => r.data),
createInviteCode: (data: InviteCodeCreateRequest = {}) =>
api.post<InviteCodeResponse>('/invites', data).then(r => r.data),
deleteInviteCode: (code: string) =>
api.delete(`/invites/${code}`),
resendInviteCode: (code: string) =>
api.post<InviteCodeResponse>(`/invites/${code}/resend`).then(r => r.data),
// Audit Logs
listAuditLogs: (params?: Record<string, unknown>) =>
api.get<AuditLogListResponse>('/admin/audit-logs', { params }).then(r => r.data),
exportAuditLogs: (params?: Record<string, string>) =>
api.get('/admin/audit-logs/export', { params, responseType: 'blob' }),
// Plan Limits
listPlanLimits: () =>
api.get<PlanLimitConfig[]>('/admin/plan-limits').then(r => r.data),
updatePlanLimits: (data: PlanLimitConfig) =>
api.put<PlanLimitConfig>('/admin/plan-limits', data).then(r => r.data),
// Account Overrides
listAccountOverrides: () =>
api.get<AccountOverrideResponse[]>('/admin/account-overrides').then(r => r.data),
createAccountOverride: (data: AccountOverrideCreate) =>
api.post<AccountOverrideResponse>('/admin/account-overrides', data).then(r => r.data),
updateAccountOverride: (id: string, data: Partial<AccountOverrideCreate>) =>
api.put<AccountOverrideResponse>(`/admin/account-overrides/${id}`, data).then(r => r.data),
deleteAccountOverride: (id: string) =>
api.delete(`/admin/account-overrides/${id}`),
// Feature Flags
listFeatureFlags: () =>
api.get<FeatureFlagResponse[]>('/admin/feature-flags').then(r => r.data),
createFeatureFlag: (data: FeatureFlagCreate) =>
api.post<FeatureFlagResponse>('/admin/feature-flags', data).then(r => r.data),
updateFeatureFlag: (id: string, data: Partial<FeatureFlagCreate>) =>
api.put<FeatureFlagResponse>(`/admin/feature-flags/${id}`, data).then(r => r.data),
deleteFeatureFlag: (id: string) =>
api.delete(`/admin/feature-flags/${id}`),
updatePlanDefault: (data: PlanDefaultUpdate) =>
api.put('/admin/feature-flags/plan-defaults', data).then(r => r.data),
// Feature Flag Account Overrides
listFeatureFlagOverrides: () =>
api.get<AccountFeatureOverrideResponse[]>('/admin/feature-flags/account-overrides').then(r => r.data),
createFeatureFlagOverride: (data: AccountFeatureOverrideCreate) =>
api.post<AccountFeatureOverrideResponse>('/admin/feature-flags/account-overrides', data).then(r => r.data),
deleteFeatureFlagOverride: (id: string) =>
api.delete(`/admin/feature-flags/account-overrides/${id}`),
// Platform Settings
listSettings: () =>
api.get<{ settings: Record<string, unknown> }>('/admin/settings').then(r => r.data),
updateSettings: (settings: Record<string, unknown>) =>
api.put<{ settings: Record<string, unknown> }>('/admin/settings', { settings }).then(r => r.data),
// Global Categories
listGlobalCategories: () =>
api.get<AdminCategory[]>('/admin/categories/global').then(r => r.data),
createGlobalCategory: (data: GlobalCategoryCreate) =>
api.post<AdminCategory>('/admin/categories/global', data).then(r => r.data),
updateGlobalCategory: (id: string, data: Partial<GlobalCategoryCreate>) =>
api.put<AdminCategory>(`/admin/categories/global/${id}`, data).then(r => r.data),
deleteGlobalCategory: (id: string) =>
api.delete(`/admin/categories/global/${id}`),
// Survey Invites
listSurveyInvites: () =>
api.get<SurveyInviteResponse[]>('/admin/survey-invites').then(r => r.data),
createSurveyInvite: (data: { recipient_name: string; recipient_email?: string; send_email?: boolean }) =>
api.post<SurveyInviteResponse>('/admin/survey-invites', data).then(r => r.data),
// Survey Responses
listSurveyResponses: (includeArchived = false) =>
api.get<SurveyResponseListResponse>('/admin/survey-responses', { params: { include_archived: includeArchived } }).then(r => r.data),
exportSurveyResponsesCsv: () =>
api.get('/admin/survey-responses/export', { responseType: 'blob' }).then(r => r.data),
markResponseRead: (id: string) =>
api.put(`/admin/survey-responses/${id}/read`).then(r => r.data),
markResponseUnread: (id: string) =>
api.put(`/admin/survey-responses/${id}/unread`).then(r => r.data),
archiveResponse: (id: string) =>
api.put(`/admin/survey-responses/${id}/archive`).then(r => r.data),
unarchiveResponse: (id: string) =>
api.put(`/admin/survey-responses/${id}/unarchive`).then(r => r.data),
deleteResponse: (id: string) =>
api.delete(`/admin/survey-responses/${id}`),
bulkActionResponses: (action: string, ids: string[]) =>
api.post('/admin/survey-responses/bulk', { action, ids }).then(r => r.data),
}
export default adminApi