Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
<!doctype html><html><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Argument-Risk-Engine</title></head><body><div id="root"></div><script type="module" src="/src/main.tsx"></script></body></html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Argument-Risk-Engine</title>
<link rel="stylesheet" href="/src/styles/global.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/app.js"></script>
</body>
</html>
9 changes: 5 additions & 4 deletions frontend/scripts/build_frontend.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { mkdirSync, copyFileSync, writeFileSync } from 'node:fs'
import { mkdirSync, copyFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = dirname(dirname(fileURLToPath(import.meta.url)))
const dist = join(root, 'dist')
mkdirSync(dist, { recursive: true })
mkdirSync(join(dist, 'src/styles'), { recursive: true })
copyFileSync(join(root, 'index.html'), join(dist, 'index.html'))
writeFileSync(join(dist, 'app.js'), 'console.log("Argument-Risk-Engine dashboard build");\n')
console.log('Built frontend MVP into dist/')
copyFileSync(join(root, 'src/styles/global.css'), join(dist, 'src/styles/global.css'))
copyFileSync(join(root, 'src/runtime-dashboard.js'), join(dist, 'app.js'))
console.log('Built frontend dashboard into dist/')
7 changes: 4 additions & 3 deletions frontend/scripts/dev_server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { readFileSync, existsSync } from 'node:fs'
import { extname, join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = dirname(dirname(fileURLToPath(import.meta.url)))
const types = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.tsx': 'application/javascript', '.ts': 'application/javascript' }
const types = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.json': 'application/json' }
const server = http.createServer((req, res) => {
let path = req.url === '/' ? '/index.html' : req.url.split('?')[0]
let file = join(root, path)
const urlPath = req.url === '/' ? '/index.html' : req.url.split('?')[0]
const mappedPath = urlPath === '/app.js' ? '/src/runtime-dashboard.js' : urlPath
let file = join(root, mappedPath)
if (!existsSync(file)) file = join(root, 'index.html')
res.writeHead(200, { 'Content-Type': types[extname(file)] || 'text/plain' })
res.end(readFileSync(file))
Expand Down
25 changes: 24 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react'
import { AnalyzePage } from './components/analyze/AnalyzePage'
import { AppShell } from './components/layout/AppShell'
import { EvaluationPage } from './components/evaluation/EvaluationPage'
Expand All @@ -6,7 +7,29 @@ import { ReviewPage } from './components/review/ReviewPage'
import { ModelSettingsPage } from './components/settings/ModelSettingsPage'
import { TaxonomyPage } from './components/taxonomy/TaxonomyPage'
import { TaxonomyWorkbenchPage } from './components/taxonomy_workbench/TaxonomyWorkbenchPage'
import { Card } from './components/shared/Card'

export type PageId = 'analyze' | 'taxonomy' | 'workbench' | 'settings' | 'review' | 'evaluation' | 'reports' | 'about'

function AboutPage() {
return <Card className="stack">
<h2>About this dashboard</h2>
<p>The dashboard is a Chrome-friendly operator console for the Argument-Risk-Engine backend. It supports text analysis, taxonomy inspection, Excel taxonomy operations, model-provider configuration, human review, evaluation, and report export.</p>
<p className="muted">Use <code>npm install</code> and <code>npm run dev</code> from <code>frontend/</code>, or the repository one-command setup, then keep the backend running on the configured API base.</p>
</Card>
}

export default function App() {
return <AppShell><div className="grid"><AnalyzePage /><TaxonomyPage /><TaxonomyWorkbenchPage /><ModelSettingsPage /><ReviewPage /><EvaluationPage /><ReportsPage /></div></AppShell>
const [activePage, setActivePage] = useState<PageId>('analyze')
const page = {
analyze: <AnalyzePage />,
taxonomy: <TaxonomyPage />,
workbench: <TaxonomyWorkbenchPage />,
settings: <ModelSettingsPage />,
review: <ReviewPage />,
evaluation: <EvaluationPage />,
reports: <ReportsPage />,
about: <AboutPage />,
}[activePage]
return <AppShell activePage={activePage} onNavigate={setActivePage}>{page}</AppShell>
}
187 changes: 124 additions & 63 deletions frontend/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,186 @@
import type { ActiveProviderResponse, AnalysisResponse, ProviderListResponse, ProviderProfile, ProviderTestResponse, TaxonomyCoverage, TaxonomyEntry, TaxonomyImportResult, TaxonomyPackSummary, TaxonomyQualityReport, TaxonomyValidationResult } from './types'
import type {
ActiveProviderResponse,
AnalysisRequest,
AnalysisResponse,
EvaluationResult,
GeneratedReport,
ProviderListResponse,
ProviderProfile,
ProviderTestResponse,
ReportFormat,
ReviewFeedback,
ReviewRecord,
TaxonomyCoverage,
TaxonomyEntry,
TaxonomyImportResult,
TaxonomyPackSummary,
TaxonomyQualityReport,
TaxonomyValidationResult,
} from './types'

export const API_BASE = (import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api').replace(/\/$/, '')
const REVIEW_KEY = 'are.review.records'
const REPORT_KEY = 'are.generated.reports'

async function request<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE}${path}`, init)
const contentType = response.headers.get('content-type') ?? ''
const payload = contentType.includes('application/json') ? await response.json() : await response.text()
if (!response.ok || (typeof payload === 'object' && payload !== null && 'detail' in payload)) {
const detail = typeof payload === 'object' && payload !== null && 'detail' in payload ? String((payload as { detail: unknown }).detail) : response.statusText
throw new Error(detail || `Request failed: ${response.status}`)
}
return payload as T
}

const API_BASE = import.meta.env.VITE_API_BASE ?? 'http://localhost:8000/api'
function downloadBlob(blob: Blob, filename: string): void {
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
link.remove()
URL.revokeObjectURL(url)
}

export async function analyzeText(text: string): Promise<AnalysisResponse> {
const response = await fetch(`${API_BASE}/analysis/analyze`, {
export function downloadText(content: string, filename: string, type = 'text/plain'): void {
downloadBlob(new Blob([content], { type }), filename)
}

export async function analyzeText(payload: AnalysisRequest): Promise<AnalysisResponse> {
return request<AnalysisResponse>('/analysis/analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
body: JSON.stringify(payload),
})
if (!response.ok) throw new Error('Analysis request failed')
return response.json()
}

export async function fetchTaxonomy(params: URLSearchParams = new URLSearchParams()): Promise<TaxonomyEntry[]> {
const query = params.toString()
const response = await fetch(`${API_BASE}/taxonomy${query ? `?${query}` : ''}`)
if (!response.ok) throw new Error('Taxonomy request failed')
const payload = await response.json()
return payload.entries
}

export async function searchTaxonomy(q: string): Promise<TaxonomyEntry[]> {
const response = await fetch(`${API_BASE}/taxonomy/search?q=${encodeURIComponent(q)}`)
if (!response.ok) throw new Error('Taxonomy search failed')
const payload = await response.json()
const payload = await request<{ entries: TaxonomyEntry[] }>(`/taxonomy${query ? `?${query}` : ''}`)
return payload.entries
}

export async function fetchTaxonomyPacks(): Promise<TaxonomyPackSummary[]> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/packs`)
if (!response.ok) throw new Error('Taxonomy packs request failed')
return (await response.json()).packs
return (await request<{ packs: TaxonomyPackSummary[] }>('/taxonomy-workbench/packs')).packs
}

export async function fetchTaxonomyCoverage(): Promise<TaxonomyCoverage> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/coverage`)
if (!response.ok) throw new Error('Taxonomy coverage request failed')
return response.json()
return request<TaxonomyCoverage>('/taxonomy-workbench/coverage')
}

export async function fetchTaxonomyQuality(): Promise<TaxonomyQualityReport> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/quality-report`)
if (!response.ok) throw new Error('Taxonomy quality request failed')
return response.json()
return request<TaxonomyQualityReport>('/taxonomy-workbench/quality-report')
}

export async function validateTaxonomy(): Promise<TaxonomyValidationResult> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/validate`, { method: 'POST' })
if (!response.ok) throw new Error('Taxonomy validation failed')
return response.json()
return request<TaxonomyValidationResult>('/taxonomy-workbench/validate', { method: 'POST' })
}

export async function importTaxonomyWorkbook(file: File): Promise<TaxonomyImportResult> {
const formData = new FormData()
formData.append('file', file)
const response = await fetch(`${API_BASE}/taxonomy-workbench/import-excel`, {
method: 'POST',
body: formData,
})
if (!response.ok) throw new Error('Taxonomy workbook import failed')
return response.json()
return request<TaxonomyImportResult>('/taxonomy-workbench/import-excel', { method: 'POST', body: formData })
}

export function exportTaxonomyWorkbook(): void {
window.location.href = `${API_BASE}/taxonomy-workbench/export-excel`
export async function exportTaxonomyWorkbook(): Promise<void> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/export-excel`)
if (!response.ok) throw new Error('Taxonomy workbook export failed')
const disposition = response.headers.get('content-disposition') ?? ''
const filename = disposition.match(/filename="?([^";]+)"?/)?.[1] ?? 'taxonomy.xlsx'
downloadBlob(await response.blob(), filename)
}

export async function updateTaxonomyActivation(riskId: string, activationStatus: string, enabledForClassification?: boolean): Promise<TaxonomyEntry> {
const response = await fetch(`${API_BASE}/taxonomy-workbench/entries/${encodeURIComponent(riskId)}/activation`, {
const payload = await request<{ entry: TaxonomyEntry }>(`/taxonomy-workbench/entries/${encodeURIComponent(riskId)}/activation`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ activation_status: activationStatus, enabled_for_classification: enabledForClassification }),
})
if (!response.ok) throw new Error('Activation update failed')
const payload = await response.json()
if (payload.detail) throw new Error(payload.detail)
return payload.entry
}


export async function fetchModelProviders(): Promise<ProviderProfile[]> {
const response = await fetch(`${API_BASE}/settings/model-providers`)
if (!response.ok) throw new Error('Model providers request failed')
const payload: ProviderListResponse = await response.json()
const payload = await request<ProviderListResponse>('/settings/model-providers')
return payload.providers
}

export async function saveModelProvider(profile: ProviderProfile): Promise<ProviderProfile> {
const response = await fetch(`${API_BASE}/settings/model-providers/${encodeURIComponent(profile.provider_id)}`, {
return request<ProviderProfile>(`/settings/model-providers/${encodeURIComponent(profile.provider_id)}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(profile),
})
if (!response.ok) throw new Error('Model provider update failed')
const payload = await response.json()
if (payload.detail) throw new Error(payload.detail)
return payload
}

export async function fetchActiveModelProvider(): Promise<ActiveProviderResponse> {
const response = await fetch(`${API_BASE}/settings/active-model-provider`)
if (!response.ok) throw new Error('Active provider request failed')
return response.json()
return request<ActiveProviderResponse>('/settings/active-model-provider')
}

export async function setActiveModelProvider(providerId: string): Promise<ActiveProviderResponse> {
const response = await fetch(`${API_BASE}/settings/active-model-provider`, {
return request<ActiveProviderResponse>('/settings/active-model-provider', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ provider_id: providerId }),
})
if (!response.ok) throw new Error('Active provider update failed')
const payload = await response.json()
if (payload.detail) throw new Error(payload.detail)
return payload
}

export async function testModelProvider(providerId: string): Promise<ProviderTestResponse> {
const response = await fetch(`${API_BASE}/settings/model-providers/${encodeURIComponent(providerId)}/test`, { method: 'POST' })
if (!response.ok) throw new Error('Provider test failed')
const payload = await response.json()
if (payload.detail) throw new Error(payload.detail)
return payload
return request<ProviderTestResponse>(`/settings/model-providers/${encodeURIComponent(providerId)}/test`, { method: 'POST' })
}

export async function runEvaluation(): Promise<EvaluationResult> {
return request<EvaluationResult>('/evaluation/run')
}

export async function submitReviewFeedback(feedback: ReviewFeedback): Promise<void> {
await request<{ status: string }>('/review/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(feedback),
})
}

function readStored<T>(key: string, fallback: T): T {
try { return JSON.parse(localStorage.getItem(key) || '') as T } catch { return fallback }
}

function writeStored<T>(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value))
}

export function listReviewRecords(): ReviewRecord[] {
return readStored<ReviewRecord[]>(REVIEW_KEY, [])
}

export function saveReviewRecord(record: ReviewRecord): void {
const records = listReviewRecords().filter(item => item.id !== record.id)
writeStored(REVIEW_KEY, [record, ...records].slice(0, 50))
}

export function updateReviewRecord(record: ReviewRecord): void {
writeStored(REVIEW_KEY, listReviewRecords().map(item => item.id === record.id ? record : item))
}

export function listReports(): GeneratedReport[] {
return readStored<GeneratedReport[]>(REPORT_KEY, [])
}

export function saveGeneratedReport(report: GeneratedReport): void {
writeStored(REPORT_KEY, [report, ...listReports().filter(item => item.id !== report.id)].slice(0, 50))
}

export function downloadReport(report: GeneratedReport, format: ReportFormat): void {
const content = format === 'json' ? report.json : format === 'html' ? report.html : report.markdown
if (!content) throw new Error(`No ${format} preview is available for this report`)
const extension = format === 'markdown' ? 'md' : format
const type = format === 'json' ? 'application/json' : format === 'html' ? 'text/html' : 'text/markdown'
downloadText(content, `${report.id}.${extension}`, type)
}

export async function fetchDemoReport(): Promise<string> {
const response = await fetch(`${API_BASE}/reports/demo.md`)
if (!response.ok) throw new Error('Could not fetch demo report')
return response.text()
}
Loading