195 lines
7.3 KiB
TypeScript
195 lines
7.3 KiB
TypeScript
import { useState } from 'preact/hooks';
|
|
import { MindforgeApiService } from '../services/MindforgeApiService';
|
|
import { FileTreeComponent } from './FileTreeComponent';
|
|
import { Button } from './Button';
|
|
import * as diff from 'diff';
|
|
import { marked } from 'marked';
|
|
import './VerificadorComponent.css';
|
|
|
|
function utf8ToBase64(str: string): string {
|
|
const bytes = new TextEncoder().encode(str);
|
|
const binary = Array.from(bytes, byte => String.fromCharCode(byte)).join('');
|
|
return window.btoa(binary);
|
|
}
|
|
|
|
type CheckTypeEnum = 'language' | 'content' | 'both';
|
|
|
|
interface FileResult {
|
|
path: string;
|
|
fileName: string;
|
|
originalContent: string;
|
|
languageResult: string | null;
|
|
contentResult: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export function VerificadorComponent() {
|
|
const [selectedPaths, setSelectedPaths] = useState<string[]>([]);
|
|
const [checkType, setCheckType] = useState<CheckTypeEnum>('language');
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [results, setResults] = useState<FileResult[]>([]);
|
|
|
|
const handleSubmit = async () => {
|
|
if (selectedPaths.length === 0) {
|
|
setError('Selecione pelo menos um arquivo do repositório.');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
setResults([]);
|
|
|
|
try {
|
|
const fileResults = await Promise.all(
|
|
selectedPaths.map(async (path): Promise<FileResult> => {
|
|
const fileName = path.split('/').pop() ?? path;
|
|
try {
|
|
const { content } = await MindforgeApiService.getFileContent(path);
|
|
const base64Content = utf8ToBase64(content);
|
|
|
|
let languageResult: string | null = null;
|
|
let contentResult: string | null = null;
|
|
|
|
if (checkType === 'both') {
|
|
const [langRes, contRes] = await Promise.all([
|
|
MindforgeApiService.checkFile({ fileContent: base64Content, fileName, checkType: 'language' }),
|
|
MindforgeApiService.checkFile({ fileContent: base64Content, fileName, checkType: 'content' }),
|
|
]);
|
|
languageResult = langRes.result;
|
|
contentResult = contRes.result;
|
|
} else {
|
|
const res = await MindforgeApiService.checkFile({ fileContent: base64Content, fileName, checkType });
|
|
if (checkType === 'language') languageResult = res.result;
|
|
else contentResult = res.result;
|
|
}
|
|
|
|
return { path, fileName, originalContent: content, languageResult, contentResult, error: null };
|
|
} catch (err: any) {
|
|
return { path, fileName, originalContent: '', languageResult: null, contentResult: null, error: err.message };
|
|
}
|
|
})
|
|
);
|
|
|
|
setResults(fileResults);
|
|
} catch (err: any) {
|
|
setError(err.message || 'Ocorreu um erro ao processar os arquivos.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const renderDiff = (original: string, updated: string) => {
|
|
const diffResult = diff.diffWordsWithSpace(original, updated);
|
|
return (
|
|
<div className="diff-view">
|
|
{diffResult.map((part, index) => {
|
|
const className = part.added ? 'diff-added' : part.removed ? 'diff-removed' : '';
|
|
return (
|
|
<span key={index} className={className}>
|
|
{part.value}
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="verificador-container">
|
|
<h2 className="title" style={{ fontSize: '2.5rem' }}>Verificador de Arquivos</h2>
|
|
<p className="subtitle">Selecione os arquivos do repositório para validação de linguagem ou conteúdo.</p>
|
|
|
|
<div className="verificador-form">
|
|
<div className="input-group">
|
|
<label>Arquivos do Repositório</label>
|
|
<FileTreeComponent selectedPaths={selectedPaths} onSelectionChange={setSelectedPaths} />
|
|
{selectedPaths.length > 0 && (
|
|
<div style={{ fontSize: '0.85rem', color: 'rgba(255,255,255,0.5)', marginTop: '0.4rem' }}>
|
|
{selectedPaths.length} arquivo{selectedPaths.length !== 1 ? 's' : ''} selecionado{selectedPaths.length !== 1 ? 's' : ''}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="input-group">
|
|
<label>Tipo de Verificação</label>
|
|
<select
|
|
className="select-input"
|
|
value={checkType}
|
|
onChange={(e) => setCheckType((e.target as HTMLSelectElement).value as CheckTypeEnum)}
|
|
>
|
|
<option value="language">Linguagem</option>
|
|
<option value="content">Conteúdo</option>
|
|
<option value="both">Linguagem e Conteúdo</option>
|
|
</select>
|
|
</div>
|
|
|
|
<Button variant="primary" onClick={handleSubmit} disabled={loading} style={{ marginTop: '1rem' }}>
|
|
{loading ? 'Processando...' : 'Verificar'}
|
|
</Button>
|
|
|
|
{error && <div style={{ color: '#ff7b72', marginTop: '1rem' }}>{error}</div>}
|
|
</div>
|
|
|
|
{loading && (
|
|
<div className="spinner-container">
|
|
<div className="spinner"></div>
|
|
<p>Analisando sua forja mental...</p>
|
|
</div>
|
|
)}
|
|
|
|
{!loading && results.length > 0 && (
|
|
<div className="response-section">
|
|
{results.map((fileResult) => (
|
|
<div key={fileResult.path} className="file-result-block">
|
|
<div className="file-result-title">{fileResult.fileName}</div>
|
|
|
|
{fileResult.error && (
|
|
<div style={{ color: '#ff7b72', padding: '0.5rem' }}>{fileResult.error}</div>
|
|
)}
|
|
|
|
{!fileResult.error && checkType === 'language' && fileResult.languageResult && (
|
|
<div className="side-pane">
|
|
<div className="pane-title">Linguagem (Diff)</div>
|
|
<div className="response-content">
|
|
{renderDiff(fileResult.originalContent, fileResult.languageResult)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{!fileResult.error && checkType === 'content' && fileResult.contentResult && (
|
|
<div className="side-pane">
|
|
<div className="pane-title">Conteúdo</div>
|
|
<div
|
|
className="response-content markdown-body"
|
|
dangerouslySetInnerHTML={{ __html: marked.parse(fileResult.contentResult) as string }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{!fileResult.error && checkType === 'both' && fileResult.languageResult && fileResult.contentResult && (
|
|
<div className="side-by-side">
|
|
<div className="side-pane">
|
|
<div className="pane-title">Linguagem (Diff)</div>
|
|
<div className="response-content" style={{ minHeight: '200px' }}>
|
|
{renderDiff(fileResult.originalContent, fileResult.languageResult)}
|
|
</div>
|
|
</div>
|
|
<div className="side-pane">
|
|
<div className="pane-title">Conteúdo</div>
|
|
<div
|
|
className="response-content markdown-body"
|
|
style={{ minHeight: '200px' }}
|
|
dangerouslySetInnerHTML={{ __html: marked.parse(fileResult.contentResult) as string }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|