mirror of
https://github.com/temetro/temetro.git
synced 2026-08-31 12:58:08 +00:00
feat(ai): clinic-wide AI kill-switch (admin-controlled)
- new org_ai_policy table + GET/PUT /api/ai/policy (read for any member, write owner/admin only); migration 0018 - /api/chat hard-blocks (403) when AI is off for the caller - Settings → AI "Availability" section: enable AI, or disable for employees only (owners/admins keep access); read-only for non-admins - sidebar, command palette and route guard hide/redirect the AI chat when it's disabled for the current user Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,10 +18,55 @@ import {
|
||||
saveAiConfig,
|
||||
toAiConfig,
|
||||
} from "../services/ai/config.js";
|
||||
import { getPolicy, savePolicy } from "../services/ai/policy.js";
|
||||
import * as patients from "../services/patients.js";
|
||||
|
||||
export const aiRouter = Router();
|
||||
|
||||
// --- Clinic-wide AI policy (admin-controlled kill-switch) -------------------
|
||||
// Any member can READ the policy (the frontend needs it to gate nav/routes);
|
||||
// only owners/admins can change it.
|
||||
aiRouter.get("/policy", requireAuth, requireOrg, async (req, res, next) => {
|
||||
try {
|
||||
res.json(await getPolicy(req.organizationId!));
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
aiRouter.put("/policy", requireAuth, requireOrg, async (req, res, next) => {
|
||||
try {
|
||||
const roles = String(req.memberRole ?? "")
|
||||
.split(",")
|
||||
.map((s) => s.trim());
|
||||
const isAdmin = roles.some((r) => r === "owner" || r === "admin");
|
||||
if (!isAdmin) {
|
||||
throw new HttpError(403, "Only owners and admins can change this.");
|
||||
}
|
||||
const body = req.body as {
|
||||
aiEnabled?: unknown;
|
||||
disabledForEmployees?: unknown;
|
||||
};
|
||||
const saved = await savePolicy(req.organizationId!, {
|
||||
aiEnabled: Boolean(body.aiEnabled),
|
||||
disabledForEmployees: Boolean(body.disabledForEmployees),
|
||||
});
|
||||
void recordActivity({
|
||||
orgId: req.organizationId!,
|
||||
actor: { id: req.user!.id, name: req.user!.name },
|
||||
action: saved.aiEnabled
|
||||
? saved.disabledForEmployees
|
||||
? "Restricted AI to owners and admins"
|
||||
: "Enabled the AI assistant clinic-wide"
|
||||
: "Disabled the AI assistant clinic-wide",
|
||||
entityType: "patient",
|
||||
});
|
||||
res.json(saved);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Per-user AI config (no clinic/RBAC needed, like /api/settings) ---------
|
||||
aiRouter.get("/config", requireAuth, async (req, res, next) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user