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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# 12012022
# Legal Evidence Processor

This repository contains a React application that uses Google Gemini to analyze uploaded files and generate a structured report of potential marriage fraud evidence.

The source code lives in the `app` directory. See [app/README.md](app/README.md) for instructions on running the project locally.

24 changes: 24 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
116 changes: 116 additions & 0 deletions app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React, { useState, useCallback } from 'react';
import { Header } from './components/Header';
import { FileUpload } from './components/FileUpload';
import { ReportDisplay } from './components/ReportDisplay';
import { Loader } from './components/Loader';
import { generateReport } from './services/geminiService';
import type { CaseReport } from './types';
import { AlertTriangle, CheckCircle, RefreshCw } from './components/Icons';

const App: React.FC = () => {
const [files, setFiles] = useState<File[]>([]);
const [report, setReport] = useState<CaseReport | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);

const handleFilesChange = useCallback((newFiles: File[]) => {
setFiles(newFiles);
setError(null);
}, []);

const handleGenerateReport = async () => {
if (files.length === 0) {
setError("Please upload at least one file to analyze.");
return;
}
setIsLoading(true);
setError(null);
setReport(null);
try {
const result = await generateReport(files);
setReport(result);
} catch (e) {
console.error(e);
const errorMessage = e instanceof Error ? e.message : "An unknown error occurred.";
setError(`Failed to generate report. ${errorMessage}. Please check the console for details and ensure your API key is configured correctly.`);
} finally {
setIsLoading(false);
}
};

const handleStartNewCase = () => {
setFiles([]);
setReport(null);
setError(null);
setIsLoading(false);
};

return (
<div className="min-h-screen bg-gray-900 text-gray-200 flex flex-col items-center p-4 sm:p-6 md:p-8">
<div className="w-full max-w-4xl mx-auto">
<Header />
<main className="mt-8">
{error && (
<div className="mb-6 bg-red-900/50 border border-red-700 text-red-200 px-4 py-3 rounded-lg flex items-center" role="alert">
<AlertTriangle className="h-5 w-5 mr-3 flex-shrink-0" />
<span className="block sm:inline">{error}</span>
</div>
)}

{isLoading && (
<div className="flex flex-col items-center justify-center p-8 text-center bg-gray-800 border border-gray-700 rounded-lg">
<Loader />
<p className="mt-4 text-lg text-blue-300">Analyzing evidence... This may take a moment.</p>
<p className="text-gray-400">Your files are being processed by the AI. Please do not navigate away.</p>
</div>
)}

{!isLoading && !report && (
<div className="bg-gray-800 border border-gray-700 rounded-lg p-6 shadow-2xl">
<h2 className="text-xl font-semibold text-blue-300 mb-4">Step 1: Upload Evidence</h2>
<p className="text-gray-400 mb-6">
Upload documents (.txt, .pdf, .docx), images (.png, .jpg), audio (.mp3, .m4a), or video (.mp4) files. The AI will process the content to identify relevant evidence.
</p>
<FileUpload onFilesChange={handleFilesChange} />

{!error && files.length > 0 && (
<div className="mt-6 bg-green-900/50 border border-green-700 text-green-200 px-4 py-3 rounded-lg flex items-center" role="status">
<CheckCircle className="h-5 w-5 mr-3" />
<span className="block sm:inline">{files.length} {files.length === 1 ? 'file is' : 'files are'} ready. Click "Generate Report" to begin analysis.</span>
</div>
)}

<div className="mt-6 flex justify-end">
<button
onClick={handleGenerateReport}
disabled={files.length === 0}
className="px-6 py-3 bg-blue-600 text-white font-bold rounded-lg shadow-md hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed transition-all duration-300 flex items-center"
>
Step 2: Generate Fraud Report
</button>
</div>
</div>
)}

{report && !isLoading && (
<>
<div className="flex justify-between items-center mb-6 no-print">
<h2 className="text-xl font-semibold text-blue-300">Step 3: Review & Export Report</h2>
<button
onClick={handleStartNewCase}
className="px-4 py-2 bg-gray-600 text-white font-semibold rounded-lg shadow-md hover:bg-gray-500 transition-all duration-300 flex items-center"
>
<RefreshCw className="w-4 h-4 mr-2" />
Start New Case
</button>
</div>
<ReportDisplay report={report} />
</>
)}
</main>
</div>
</div>
);
};

export default App;
14 changes: 14 additions & 0 deletions app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Run and deploy your AI Studio app

This contains everything you need to run your app locally.

## Run Locally

**Prerequisites:** Node.js


1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`
115 changes: 115 additions & 0 deletions app/components/FileUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@

import React, { useState, useCallback, useMemo } from 'react';
import { FileText, ImageIcon, Music, Video, UploadCloud, X } from './Icons';

interface FileUploadProps {
onFilesChange: (files: File[]) => void;
}

const getFileIcon = (fileType: string) => {
if (fileType.startsWith('image/')) return <ImageIcon className="h-6 w-6 text-purple-400" />;
if (fileType.startsWith('audio/')) return <Music className="h-6 w-6 text-pink-400" />;
if (fileType.startsWith('video/')) return <Video className="h-6 w-6 text-red-400" />;
return <FileText className="h-6 w-6 text-blue-400" />;
};

const formatBytes = (bytes: number, decimals = 2): string => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
};

export const FileUpload: React.FC<FileUploadProps> = ({ onFilesChange }) => {
const [files, setFiles] = useState<File[]>([]);
const [isDragging, setIsDragging] = useState(false);

const handleFiles = useCallback((incomingFiles: FileList | null) => {
if (incomingFiles) {
const newFiles = Array.from(incomingFiles);
const updatedFiles = [...files, ...newFiles];
setFiles(updatedFiles);
onFilesChange(updatedFiles);
}
}, [files, onFilesChange]);

const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
};

const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
};

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
};

const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(false);
handleFiles(e.dataTransfer.files);
};

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
handleFiles(e.target.files);
};

const removeFile = (index: number) => {
const updatedFiles = files.filter((_, i) => i !== index);
setFiles(updatedFiles);
onFilesChange(updatedFiles);
};

const fileList = useMemo(() => (
<div className="mt-4 space-y-3">
{files.map((file, index) => (
<div key={index} className="flex items-center bg-gray-700/50 p-3 rounded-lg border border-gray-600">
<div className="flex-shrink-0">{getFileIcon(file.type)}</div>
<div className="ml-4 flex-grow truncate">
<p className="text-sm font-medium text-gray-200 truncate">{file.name}</p>
<p className="text-xs text-gray-400">{formatBytes(file.size)}</p>
</div>
<button onClick={() => removeFile(index)} className="ml-4 p-1 rounded-full hover:bg-gray-600 transition-colors">
<X className="h-5 w-5 text-gray-400 hover:text-white" />
</button>
</div>
))}
</div>
), [files, onFilesChange]);


return (
<div>
<div
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDragOver={handleDragOver}
onDrop={handleDrop}
className={`relative border-2 border-dashed rounded-lg p-8 text-center transition-all duration-300 ${isDragging ? 'border-blue-500 bg-gray-700/50' : 'border-gray-600 hover:border-gray-500'}`}
>
<input
type="file"
multiple
onChange={handleFileChange}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
accept=".txt,.pdf,.docx,.png,.jpg,.jpeg,.webp,.m4a,.mp3,.wav,.ogg,.mp4,.mov,.webm"
/>
<div className="flex flex-col items-center justify-center space-y-2 text-gray-400">
<UploadCloud className={`h-12 w-12 transition-transform duration-300 ${isDragging ? 'scale-110' : ''}`} />
<p className="font-semibold text-gray-300">Drag & drop files here</p>
<p>or <span className="text-blue-400 font-semibold">click to browse</span></p>
</div>
</div>
{files.length > 0 && fileList}
</div>
);
};
17 changes: 17 additions & 0 deletions app/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import React from 'react';
import { Briefcase } from './Icons';

export const Header: React.FC = () => (
<header className="text-center">
<div className="flex justify-center items-center gap-4 mb-4">
<Briefcase className="w-10 h-10 text-blue-400"/>
<h1 className="text-3xl sm:text-4xl font-bold tracking-tight text-transparent bg-clip-text bg-gradient-to-r from-blue-300 to-cyan-400">
Marriage Fraud Evidence Processor
</h1>
</div>
<p className="max-w-2xl mx-auto text-gray-400">
This tool leverages AI to analyze submitted evidence and generate structured legal reports for potential marriage fraud cases, cross-referencing findings with federal and state statutes.
</p>
</header>
);
Loading