fix somethings
This commit is contained in:
211
frontend/src/contexts/PageStateContext.tsx
Normal file
211
frontend/src/contexts/PageStateContext.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* PageStateContext — preserves page-level session state across route changes.
|
||||
*
|
||||
* When React Router unmounts a page component, all its useState values are lost.
|
||||
* This context lives above the router and holds the state that must survive
|
||||
* navigation so users can switch modules and return without losing their work.
|
||||
*
|
||||
* Covered pages:
|
||||
* - RagChat: message history, citation rail, sessionId, input draft
|
||||
* - Compliance: analysis result (sources, findings, conclusion, meta)
|
||||
* - Perception: selected signal, filter state, AI analysis output
|
||||
*/
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
|
||||
|
||||
// ── RagChat types ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface RagMessage {
|
||||
id: string;
|
||||
role: 'user' | 'assistant';
|
||||
text: string;
|
||||
citationRefs?: number[];
|
||||
}
|
||||
|
||||
export interface RagCitation {
|
||||
index: number;
|
||||
score: number;
|
||||
name: string;
|
||||
clause: string;
|
||||
snippet: string;
|
||||
docId?: string;
|
||||
}
|
||||
|
||||
export interface RagChatState {
|
||||
messages: RagMessage[];
|
||||
citations: RagCitation[];
|
||||
sessionId: string | null;
|
||||
inputDraft: string;
|
||||
}
|
||||
|
||||
const RAG_INIT: RagChatState = {
|
||||
messages: [
|
||||
{
|
||||
id: 'init',
|
||||
role: 'assistant',
|
||||
text: 'Hello! I can answer questions about your indexed regulations and compliance documents. Try asking about EU AI Act requirements, MIIT rules, or ISO/SAE 21434 scope.',
|
||||
},
|
||||
],
|
||||
citations: [],
|
||||
sessionId: null,
|
||||
inputDraft: '',
|
||||
};
|
||||
|
||||
// ── Compliance types ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface ComplianceSourceEvent {
|
||||
standard: string;
|
||||
clause: string;
|
||||
score: number;
|
||||
status: string;
|
||||
full_content: string;
|
||||
}
|
||||
|
||||
export interface ComplianceFindingEvent {
|
||||
title: string;
|
||||
desc: string;
|
||||
status: 'ok' | 'warn' | 'risk';
|
||||
clause_ref?: string;
|
||||
}
|
||||
|
||||
export interface ComplianceActionItem {
|
||||
label: string;
|
||||
value: string;
|
||||
risk?: boolean;
|
||||
}
|
||||
|
||||
export interface ComplianceDonePayload {
|
||||
conclusion: string;
|
||||
actions: ComplianceActionItem[];
|
||||
risk_score: number;
|
||||
highlight_terms: string[];
|
||||
para_text: string;
|
||||
}
|
||||
|
||||
export interface ComplianceMeta {
|
||||
title: string;
|
||||
sourceType: 'text' | 'doc' | 'upload';
|
||||
startedAt: string;
|
||||
}
|
||||
|
||||
export type ComplianceStatus = 'idle' | 'streaming' | 'done' | 'error';
|
||||
|
||||
export interface ComplianceState {
|
||||
status: ComplianceStatus;
|
||||
stageLabel: string;
|
||||
stageKey: string;
|
||||
meta: ComplianceMeta | null;
|
||||
sources: ComplianceSourceEvent[];
|
||||
findings: ComplianceFindingEvent[];
|
||||
done: ComplianceDonePayload | null;
|
||||
errorText: string;
|
||||
}
|
||||
|
||||
const COMPLIANCE_INIT: ComplianceState = {
|
||||
status: 'idle',
|
||||
stageLabel: '',
|
||||
stageKey: '',
|
||||
meta: null,
|
||||
sources: [],
|
||||
findings: [],
|
||||
done: null,
|
||||
errorText: '',
|
||||
};
|
||||
|
||||
// ── Perception types ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface PerceptionSignal {
|
||||
id: string;
|
||||
source: string;
|
||||
standard: string;
|
||||
status: 'ok' | 'warn' | 'risk' | 'info';
|
||||
title: string;
|
||||
summary: string;
|
||||
date: string;
|
||||
tags: string[];
|
||||
impact: 'High' | 'Medium' | 'Low';
|
||||
}
|
||||
|
||||
export interface PerceptionPageState {
|
||||
signals: PerceptionSignal[];
|
||||
searchQuery: string;
|
||||
sourceFilter: string;
|
||||
impactFilter: string;
|
||||
selectedId: string | null;
|
||||
aiOutput: string;
|
||||
detailTab: 'overview' | 'obligations' | 'assessment' | 'diff';
|
||||
crawlStatus: string;
|
||||
}
|
||||
|
||||
const PERCEPTION_INIT: PerceptionPageState = {
|
||||
signals: [],
|
||||
searchQuery: '',
|
||||
sourceFilter: 'All',
|
||||
impactFilter: 'All',
|
||||
selectedId: null,
|
||||
aiOutput: '',
|
||||
detailTab: 'overview',
|
||||
crawlStatus: '',
|
||||
};
|
||||
|
||||
// ── Context value ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface PageStateContextValue {
|
||||
// RagChat
|
||||
ragState: RagChatState;
|
||||
setRagState: React.Dispatch<React.SetStateAction<RagChatState>>;
|
||||
ragStreamingRef: React.MutableRefObject<boolean>;
|
||||
ragAbortRef: React.MutableRefObject<AbortController | null>;
|
||||
|
||||
// Compliance
|
||||
complianceState: ComplianceState;
|
||||
setComplianceState: React.Dispatch<React.SetStateAction<ComplianceState>>;
|
||||
complianceAbortRef: React.MutableRefObject<AbortController | null>;
|
||||
resetCompliance: () => void;
|
||||
|
||||
// Perception
|
||||
perceptionState: PerceptionPageState;
|
||||
setPerceptionState: React.Dispatch<React.SetStateAction<PerceptionPageState>>;
|
||||
perceptionAbortRef: React.MutableRefObject<AbortController | null>;
|
||||
perceptionCrawlAbortRef: React.MutableRefObject<AbortController | null>;
|
||||
}
|
||||
|
||||
const PageStateContext = createContext<PageStateContextValue | null>(null);
|
||||
|
||||
// ── Provider ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export function PageStateProvider({ children }: { children: React.ReactNode }) {
|
||||
const [ragState, setRagState] = useState<RagChatState>(RAG_INIT);
|
||||
const ragStreamingRef = useRef(false);
|
||||
const ragAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const [complianceState, setComplianceState] = useState<ComplianceState>(COMPLIANCE_INIT);
|
||||
const complianceAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const resetCompliance = useCallback(() => {
|
||||
complianceAbortRef.current?.abort();
|
||||
setComplianceState(COMPLIANCE_INIT);
|
||||
}, []);
|
||||
|
||||
const [perceptionState, setPerceptionState] = useState<PerceptionPageState>(PERCEPTION_INIT);
|
||||
const perceptionAbortRef = useRef<AbortController | null>(null);
|
||||
const perceptionCrawlAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
return (
|
||||
<PageStateContext.Provider value={{
|
||||
ragState, setRagState, ragStreamingRef, ragAbortRef,
|
||||
complianceState, setComplianceState, complianceAbortRef, resetCompliance,
|
||||
perceptionState, setPerceptionState, perceptionAbortRef, perceptionCrawlAbortRef,
|
||||
}}>
|
||||
{children}
|
||||
</PageStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Hook ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export function usePageState() {
|
||||
const ctx = useContext(PageStateContext);
|
||||
if (!ctx) throw new Error('usePageState must be used inside PageStateProvider');
|
||||
return ctx;
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
export { ThemeProvider, useTheme } from './ThemeContext';
|
||||
export { AuthProvider, useAuth } from './AuthContext';
|
||||
export type { AuthUser } from './AuthContext';
|
||||
export { PageStateProvider, usePageState } from './PageStateContext';
|
||||
export type {
|
||||
RagChatState,
|
||||
RagMessage,
|
||||
RagCitation,
|
||||
ComplianceState,
|
||||
ComplianceStatus,
|
||||
ComplianceSourceEvent,
|
||||
ComplianceFindingEvent,
|
||||
ComplianceDonePayload,
|
||||
ComplianceMeta,
|
||||
ComplianceActionItem,
|
||||
PerceptionPageState,
|
||||
PerceptionSignal,
|
||||
} from './PageStateContext';
|
||||
|
||||
Reference in New Issue
Block a user