* 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>
200 lines
8.5 KiB
TypeScript
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
|