From 5ff41e2af447ec252fd9e13d5b34693424d38461 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 11 May 2026 15:04:37 +0000 Subject: [PATCH 1/4] Add AI governance copilot --- .../src/components/AiGovernanceCopilot.tsx | 479 ++++++++++++++++++ 1 file changed, 479 insertions(+) create mode 100644 frontend/src/components/AiGovernanceCopilot.tsx diff --git a/frontend/src/components/AiGovernanceCopilot.tsx b/frontend/src/components/AiGovernanceCopilot.tsx new file mode 100644 index 0000000..80cdffa --- /dev/null +++ b/frontend/src/components/AiGovernanceCopilot.tsx @@ -0,0 +1,479 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { mdiShieldCheckOutline } from '@mdi/js' +import { useRouter } from 'next/router' +import BaseIcon from './BaseIcon' +import { aiResponse } from '../stores/openAiSlice' +import { useAppDispatch, useAppSelector } from '../stores/hooks' + +type CopilotRole = 'assistant' | 'user' + +type CopilotMessage = { + id: string + role: CopilotRole + content: string + time?: string +} + +type PageContext = { + title: string + focus: string +} + +const pageContexts: Array<{ match: string; title: string; focus: string }> = [ + { + match: '/governance-workbench', + title: 'Governance workbench', + focus: 'AI use-case intake, review paths, approval readiness, vendor assessments, and policy coverage.', + }, + { + match: '/dashboard', + title: 'System overview', + focus: 'Executive visibility across AI adoption, risk posture, approvals, audit activity, and ROI.', + }, + { + match: '/ai_use_cases', + title: 'AI request register', + focus: 'Registration of proposed AI workflows before they touch client work.', + }, + { + match: '/approval_steps', + title: 'Approval queue', + focus: 'Partner, general counsel, IT/security, and ethics review decisions.', + }, + { + match: '/usage_audit_logs', + title: 'Usage and audit log', + focus: 'Traceability for prompts, outputs, reviewers, decisions, and evidence.', + }, + { + match: '/review_exceptions', + title: 'Review exceptions', + focus: 'Human-review gaps, risky shortcuts, and items needing remediation.', + }, + { + match: '/ai_tools', + title: 'AI tool registry', + focus: 'Approved, suspended, and in-review AI tools with model and usage constraints.', + }, + { + match: '/vendors', + title: 'Vendor registry', + focus: 'AI vendors, commercial owners, contracts, due diligence, and status.', + }, + { + match: '/vendor_risk_assessments', + title: 'Vendor risk assessment', + focus: 'Security, data retention, client data use, subprocessors, deletion, and legal-specific risk.', + }, + { + match: '/integrations', + title: 'Integrations', + focus: 'Connections to DMS, practice management, identity, workflow, reporting, and AI model layers.', + }, + { + match: '/data_classifications', + title: 'Data sensitivity', + focus: 'Public, internal, confidential, privileged, and regulated matter data classifications.', + }, + { + match: '/policies', + title: 'Policies and guardrails', + focus: 'AI usage policy, client notices, review rules, consent language, and prohibited use cases.', + }, + { + match: '/human_review_checklists', + title: 'Human review checklists', + focus: 'Reviewer checklists before AI-assisted work is filed, sent, billed, or relied upon.', + }, + { + match: '/checklist_items', + title: 'Checklist items', + focus: 'Atomic controls for human review, privilege checks, source verification, and output validation.', + }, + { + match: '/training_courses', + title: 'Training courses', + focus: 'AI governance training for attorneys, legal ops, IT/security, and reviewers.', + }, + { + match: '/practice_groups', + title: 'Practice groups', + focus: 'Practice-specific AI adoption patterns, owners, risk profiles, and rollout readiness.', + }, + { + match: '/matter_types', + title: 'Matter types', + focus: 'Matter-specific governance for litigation, transactions, employment, privacy, and other work.', + }, + { + match: '/roles_catalog', + title: 'Roles catalog', + focus: 'Governance roles, approval authority, access boundaries, and accountability.', + }, +] + +const starterPrompts = [ + 'Classify a deposition summary workflow and suggest the approval path.', + 'Draft vendor risk questions for a legal AI tool.', + 'What should we track to prove ROI for this AI workflow?', +] + +const welcomeMessage: CopilotMessage = { + id: 'welcome', + role: 'assistant', + content: + 'I can help map AI use cases, vendor reviews, human-review controls, and ROI evidence for this Legal AI Governance Hub. Tell me the workflow, tool, or risk you want to reason through.', +} + +const getNow = () => { + return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) +} + +const findPageContext = (path: string): PageContext => { + const match = pageContexts.find((item) => path.startsWith(item.match)) + + if (match) { + return { + title: match.title, + focus: match.focus, + } + } + + return { + title: 'Legal AI Governance Hub', + focus: 'Controlled AI adoption across legal use cases, tools, vendors, policies, approvals, and audit evidence.', + } +} + +const extractTextFromResponse = (response: any): string => { + const payload = response?.data || response + + if (typeof payload === 'string') { + return payload + } + + if (typeof payload?.output_text === 'string') { + return payload.output_text + } + + if (typeof payload?.text === 'string') { + return payload.text + } + + if (typeof payload?.message === 'string') { + return payload.message + } + + if (payload?.choices?.[0]?.message?.content) { + return payload.choices[0].message.content + } + + if (Array.isArray(payload?.output)) { + const outputText = payload.output + .flatMap((item) => item?.content || []) + .map((part) => part?.text || part?.content || '') + .filter(Boolean) + .join('\n') + + if (outputText.trim()) { + return outputText + } + } + + return '' +} + +const getErrorMessage = (error: any) => { + if (typeof error === 'string') { + return error + } + + if (typeof error?.message === 'string') { + return error.message + } + + if (typeof error?.error?.message === 'string') { + return error.error.message + } + + if (typeof error?.data?.error?.message === 'string') { + return error.data.error.message + } + + return 'The AI service did not return a usable response. Please try again.' +} + +const buildSystemPrompt = (page: PageContext, route: string, userLabel: string) => { + return `You are the AI Governance Copilot inside Legal AI Governance Hub. + +Product context: +Legal AI Governance Hub is a control plane for law firms and legal departments adopting AI. It is not an AI lawyer and it is not a contract generation chatbot. It helps teams register AI use cases, classify data sensitivity, evaluate AI tools and vendors, route approvals, enforce human review, keep audit trails, and prove ROI. + +Current app page: ${page.title} +Current route: ${route} +Current page focus: ${page.focus} +Signed-in workspace user: ${userLabel} + +Core modules available in the app: +- AI use-case intake and risk classification +- Matter and client data sensitivity: public, internal, confidential, privileged, regulated +- AI tool registry: ChatGPT, Claude, Gemini, Harvey, CoCounsel, Lexis, Spellbook, internal tools +- Vendor registry and vendor risk assessments +- Approval workflows: partner, general counsel, IT/security, ethics/risk +- Policy and guardrail library +- Human-review checklists +- Usage and audit logs +- Training tracker +- ROI dashboard and adoption evidence +- Integrations with DMS, practice management, identity, workflow, reporting, and model providers + +Behavior rules: +- Give practical governance and workflow guidance, not legal advice. +- Do not claim that you changed application data. You can suggest what the user should add or review. +- When useful, answer with concrete fields, approval steps, risks, and next actions. +- Prefer concise, executive-ready language for legal operations and firm leadership. +- Mention confidentiality, privilege, human review, vendor risk, and ROI when relevant.` +} + +export default function AiGovernanceCopilot() { + const dispatch = useAppDispatch() + const router = useRouter() + const currentUser = useAppSelector((state) => state.auth.currentUser) + const isAskingResponse = useAppSelector((state) => state.openAi.isAskingResponse) + const [isOpen, setIsOpen] = useState(false) + const [input, setInput] = useState('') + const [messages, setMessages] = useState([welcomeMessage]) + const [localError, setLocalError] = useState('') + const messagesEndRef = useRef(null) + + const page = useMemo(() => findPageContext(router.pathname), [router.pathname]) + const userLabel = currentUser?.email || currentUser?.firstName || 'authenticated user' + + useEffect(() => { + if (!isOpen) { + return + } + + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }) + }, [messages, isOpen]) + + const askCopilot = async (prompt: string) => { + const cleanPrompt = prompt.trim() + + if (!cleanPrompt || isAskingResponse) { + return + } + + setLocalError('') + setInput('') + + const userMessage: CopilotMessage = { + id: `user-${Date.now()}`, + role: 'user', + content: cleanPrompt, + time: getNow(), + } + + const history = messages.filter((message) => message.id !== welcomeMessage.id) + setMessages((currentMessages) => [...currentMessages, userMessage]) + + try { + const response = await dispatch( + aiResponse({ + input: [ + { role: 'system', content: buildSystemPrompt(page, router.asPath, userLabel) }, + ...history.map((message) => ({ role: message.role, content: message.content })), + { role: 'user', content: cleanPrompt }, + ], + options: { + poll_interval: 3, + poll_timeout: 180, + }, + }), + ).unwrap() + + const responseText = extractTextFromResponse(response).trim() + + if (!responseText) { + throw new Error('The AI service returned an empty response.') + } + + const assistantMessage: CopilotMessage = { + id: `assistant-${Date.now()}`, + role: 'assistant', + content: responseText, + time: getNow(), + } + + setMessages((currentMessages) => [...currentMessages, assistantMessage]) + } catch (error) { + console.error('AI Governance Copilot failed', error) + const errorMessage = getErrorMessage(error) + setLocalError(errorMessage) + setMessages((currentMessages) => [ + ...currentMessages, + { + id: `assistant-error-${Date.now()}`, + role: 'assistant', + content: `I could not complete that request. ${errorMessage}`, + time: getNow(), + }, + ]) + } + } + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault() + await askCopilot(input) + } + + const handleSuggestion = (prompt: string) => { + setInput(prompt) + setIsOpen(true) + } + + if (!isOpen) { + return ( + + ) + } + + return ( +
+
+
+
+ + + +
+

Legal AI control plane

+

AI Governance Copilot

+

Context: {page.title}

+
+
+ +
+
+ +
+
+

Workspace context

+

{page.focus}

+
+
+ +
+
+ {messages.map((message) => ( +
+
+
{message.content}
+ {message.time ? ( +
+ {message.time} +
+ ) : null} +
+
+ ))} + + {isAskingResponse ? ( +
+
+ + + Reviewing governance context... + +
+
+ ) : null} +
+
+
+ +
+
+ {starterPrompts.map((prompt) => ( + + ))} +
+ + {localError ? ( +

+ {localError} +

+ ) : null} + +
+