Adding components for Excel Analizer

This commit is contained in:
Getasew Amsalu 2026-02-21 10:24:24 +03:00
parent dc243e4cf9
commit 4d749962fb
3 changed files with 1656 additions and 0 deletions

View file

@ -0,0 +1,484 @@
# Excel Analyzer - Component Specifications
## Component Architecture
### 1. UploadComponent
**Location**: `src/components/excel-analyzer/UploadComponent.tsx`
**Purpose**: Handle Excel file uploads with drag-and-drop interface
**Features**:
- Drag-and-drop file upload area
- File type validation (.xlsx, .csv)
- File size limits enforcement
- Progress tracking
- Error handling and user feedback
- Preview of uploaded file structure
**Props**:
```typescript
interface UploadComponentProps {
onFileUploaded: (file: File) => void
onError: (error: string) => void
maxFileSize?: number // in bytes
acceptedFormats?: string[]
}
```
**State**:
- `isDragging: boolean`
- `uploadProgress: number`
- `selectedFile: File | null`
- `error: string | null`
### 2. IntentInputComponent
**Location**: `src/components/excel-analyzer/IntentInputComponent.tsx`
**Purpose**: Capture user's analysis intent through natural language input
**Features**:
- Text input area for analysis requests
- AI-powered intent recognition
- Parameter extraction
- Context understanding
- Real-time suggestions
- Error handling for invalid requests
**Props**:
```typescript
interface IntentInputComponentProps {
onIntentRecognized: (intent: AnalysisIntent) => void
onError: (error: string) => void
placeholder?: string
}
```
**State**:
- `userInput: string`
- `recognizedIntent: AnalysisIntent | null`
- `processing: boolean`
- `error: string | null`
### 3. DashboardComponent
**Location**: `src/components/excel-analyzer/DashboardComponent.tsx`
**Purpose**: Display analysis results and insights
**Features**:
- Real-time progress tracking
- Interactive charts and graphs
- Data tables with sorting/filtering
- Summary statistics
- Export controls
- Multiple view modes (table, chart, summary)
**Props**:
```typescript
interface DashboardComponentProps {
analysisResults: AnalysisResults
onExport: (format: ExportFormat) => void
onViewChange: (view: "table" | "chart" | "summary") => void
}
```
**State**:
- `currentView: 'table' | 'chart' | 'summary'`
- `selectedData: any[]`
- `filters: FilterOptions`
- `sort: SortOptions`
### 4. ExportComponent
**Location**: `src/components/excel-analyzer/ExportComponent.tsx`
**Purpose**: Handle result export in multiple formats
**Features**:
- Format selection (Excel, PDF, CSV, JSON)
- Export progress tracking
- Download management
- Format-specific options
- Error handling
**Props**:
```typescript
interface ExportComponentProps {
analysisResults: AnalysisResults
onExport: (format: ExportFormat, options?: ExportOptions) => void
onDownload: (fileUrl: string) => void
}
```
**State**:
- `selectedFormat: ExportFormat`
- `exportProgress: number`
- `exportOptions: ExportOptions`
- `downloadUrl: string | null`
### 5. ProgressComponent
**Location**: `src/components/excel-analyzer/ProgressComponent.tsx`
**Purpose**: Display overall workflow progress
**Features**:
- Multi-step progress indicator
- Current step highlighting
- Estimated time remaining
- Pause/resume functionality
- Error recovery options
**Props**:
```typescript
interface ProgressComponentProps {
currentStep: number
totalSteps: number
stepLabels: string[]
progress: number
status: "pending" | "processing" | "completed" | "error"
onRetry?: () => void
onPause?: () => void
onResume?: () => void
}
```
**State**:
- `currentStep: number`
- `progress: number`
- `status: 'pending' | 'processing' | 'completed' | 'error'`
- `error: string | null`
## Service Specifications
### 1. FileUploadService
**Location**: `src/lib/fileUploadService.ts`
**Purpose**: Handle file upload and validation
**Methods**:
```typescript
interface FileUploadService {
uploadFile(file: File): Promise<UploadedFile>
validateFile(file: File): ValidationResult
getFilePreview(file: File): Promise<FilePreview>
getFileSize(file: File): number
}
```
### 2. AIService
**Location**: `src/lib/aiService.ts`
**Purpose**: Handle AI intent recognition and analysis
**Methods**:
```typescript
interface AIService {
recognizeIntent(userInput: string): Promise<AnalysisIntent>
extractParameters(intent: AnalysisIntent): ParameterSet
analyzeData(data: any[], intent: AnalysisIntent): Promise<AnalysisResults>
}
```
### 3. DataProcessingService
**Location**: `src/lib/dataProcessingService.ts`
**Purpose**: Handle data preprocessing and transformation
**Methods**:
```typescript
interface DataProcessingService {
preprocessData(data: any[]): ProcessedData
cleanData(data: any[]): CleanedData
transformData(data: any[], transformation: Transformation): TransformedData
validateSchema(data: any[], schema: Schema): ValidationResult
}
```
### 4. ExportService
**Location**: `src/lib/exportService.ts`
**Purpose**: Handle result export in multiple formats
**Methods**:
```typescript
interface ExportService {
exportToExcel(data: any[], options?: ExportOptions): Promise<Blob>
exportToPDF(data: any[], options?: ExportOptions): Promise<Blob>
exportToCSV(data: any[], options?: ExportOptions): Promise<Blob>
exportToJSON(data: any[], options?: ExportOptions): Promise<Blob>
downloadFile(blob: Blob, filename: string): void
}
```
### 5. OrchestrationService
**Location**: `src/lib/orchestrationService.ts`
**Purpose**: Manage workflow and coordinate services
**Methods**:
```typescript
interface OrchestrationService {
startWorkflow(file: File, intent: AnalysisIntent): Promise<WorkflowResult>
pauseWorkflow(workflowId: string): Promise<void>
resumeWorkflow(workflowId: string): Promise<void>
cancelWorkflow(workflowId: string): Promise<void>
getWorkflowStatus(workflowId: string): Promise<WorkflowStatus>
}
```
## Hook Specifications
### 1. useFileUpload
**Location**: `src/hooks/useFileUpload.ts`
**Purpose**: Manage file upload state and logic
**Returns**:
```typescript
interface UseFileUploadReturn {
isDragging: boolean
uploadProgress: number
selectedFile: File | null
error: string | null
handleDragEnter: () => void
handleDragLeave: () => void
handleDrop: (files: FileList) => void
handleFileSelect: (file: File) => void
uploadFile: () => Promise<void>
}
```
### 2. useIntentRecognition
**Location**: `src/hooks/useIntentRecognition.ts`
**Purpose**: Manage intent recognition state and logic
**Returns**:
```typescript
interface UseIntentRecognitionReturn {
userInput: string
recognizedIntent: AnalysisIntent | null
processing: boolean
error: string | null
handleInputChange: (input: string) => void
recognizeIntent: () => Promise<void>
clearIntent: () => void
}
```
### 3. useDataProcessing
**Location**: `src/hooks/useDataProcessing.ts`
**Purpose**: Manage data processing state and logic
**Returns**:
```typescript
interface UseDataProcessingReturn {
processedData: ProcessedData | null
processing: boolean
error: string | null
processData: (data: any[]) => Promise<void>
cleanData: (data: any[]) => Promise<void>
transformData: (data: any[], transformation: Transformation) => Promise<void>
}
```
### 4. useExport
**Location**: `src/hooks/useExport.ts`
**Purpose**: Manage export state and logic
**Returns**:
```typescript
interface UseExportReturn {
selectedFormat: ExportFormat
exportProgress: number
exportOptions: ExportOptions
downloadUrl: string | null
handleFormatChange: (format: ExportFormat) => void
handleExport: () => Promise<void>
handleDownload: () => void
clearExport: () => void
}
```
## Type Specifications
### File Types
```typescript
interface FileTypes {
UploadedFile: {
id: string
name: string
size: number
type: string
preview: FilePreview
}
FilePreview: {
columns: string[]
sampleData: any[][]
rowCount: number
columnTypes: Record<string, string>
}
ValidationResult: {
isValid: boolean
errors: string[]
warnings: string[]
}
}
```
### Analysis Types
```typescript
interface AnalysisTypes {
AnalysisIntent: {
type: "descriptive" | "predictive" | "diagnostic" | "prescriptive"
parameters: Record<string, any>
context: string
}
AnalysisResults: {
summary: AnalysisSummary
charts: ChartData[]
tables: DataTable[]
insights: Insight[]
recommendations: Recommendation[]
}
AnalysisSummary: {
metrics: Record<string, number>
trends: Trend[]
patterns: Pattern[]
anomalies: Anomaly[]
}
}
```
### Export Types
```typescript
interface ExportTypes {
ExportFormat: "excel" | "pdf" | "csv" | "json"
ExportOptions: {
includeCharts: boolean
includeTables: boolean
includeInsights: boolean
formatOptions: Record<string, any>
}
ExportProgress: {
totalSteps: number
currentStep: number
stepName: string
percentage: number
}
}
```
## Integration Points
### 1. Component-Service Integration
- UploadComponent → FileUploadService
- IntentInputComponent → AIService
- DashboardComponent → DataProcessingService
- ExportComponent → ExportService
- ProgressComponent → OrchestrationService
### 2. Hook-Service Integration
- useFileUpload → FileUploadService
- useIntentRecognition → AIService
- useDataProcessing → DataProcessingService
- useExport → ExportService
### 3. State Management
- Global state for workflow management
- Local state for component-specific data
- Context providers for shared services
## Error Handling Strategy
### 1. Component-Level Errors
- User-friendly error messages
- Retry mechanisms
- Fallback options
### 2. Service-Level Errors
- Error logging and monitoring
- Graceful degradation
- Circuit breaker patterns
### 3. Workflow Errors
- Transaction rollback
- State recovery
- User notification
## Performance Considerations
### 1. File Processing
- Streaming for large files
- Web Workers for heavy processing
- Caching for repeated operations
### 2. AI Processing
- Debouncing for user input
- Caching for common intents
- Background processing
### 3. UI Rendering
- Virtual scrolling for large datasets
- Memoization for expensive computations
- Lazy loading for components
---
_This component specification provides a detailed blueprint for implementing the Excel Analyzer functionality within the existing web-roo-code app structure._

View file

@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excel Analyzer</title>
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/papaparse/5.3.1/papaparse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
.hidden-file-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.spinner {
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold text-center mb-8 text-gray-800">Excel Analyzer</h1>
<!-- Upload Component -->
<div id="uploadComponent" class="mb-8">
<h2 class="text-xl font-semibold mb-4 text-gray-700">Upload Excel File</h2>
<div class="bg-white rounded-lg shadow-md p-6">
<div id="uploadArea" class="border-2 border-dashed rounded-lg p-8 text-center transition-all">
<div class="text-4xl text-gray-400 mb-4">
<svg class="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
</svg>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-2">Upload Excel File</h3>
<p class="text-gray-500 mb-4">Drag and drop your Excel file here or click to browse</p>
<p class="text-sm text-gray-500 mb-4">Supported formats: .xlsx, .csv</p>
<input type="file" id="fileInput" class="hidden-file-input" accept=".xlsx,.csv">
<label for="fileInput" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg cursor-pointer hover:bg-blue-700 transition-colors">
Browse Files
</label>
</div>
<!-- File Preview -->
<div id="filePreview" class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg hidden">
<h3 class="text-lg font-semibold mb-2">File Preview</h3>
<div class="space-y-2">
<p><strong>Name:</strong> <span id="previewName"></span></p>
<p><strong>Size:</strong> <span id="previewSize"></span> KB</p>
<p><strong>Columns:</strong> <span id="previewColumns"></span></p>
<p><strong>Rows:</strong> <span id="previewRows"></span></p>
</div>
</div>
<!-- Selected File Info -->
<div id="selectedFileInfo" class="mt-4 p-4 bg-green-50 border border-green-200 rounded-lg hidden">
<div class="flex justify-between items-center">
<div>
<h4 class="font-medium">Selected File:</h4>
<p class="text-sm text-gray-600" id="selectedFileName"></p>
</div>
<button id="removeFileBtn" class="text-red-600 hover:text-red-700">
Remove
</button>
</div>
</div>
<!-- Error Message -->
<div id="errorMessage" class="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 hidden"></div>
<!-- Upload Button -->
<div id="uploadButton" class="mt-4 hidden">
<button id="uploadFileBtn" class="w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed">
Upload File
</button>
</div>
</div>
</div>
<!-- Intent Input Component -->
<div id="intentInputComponent" class="mb-8 hidden">
<h2 class="text-xl font-semibold mb-4 text-gray-700">Analyze Your Data</h2>
<div class="bg-white rounded-lg shadow-md p-6">
<!-- Processing Indicator -->
<div id="processingIndicator" class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg text-blue-700 flex items-center justify-between hidden">
<div class="flex items-center">
<div class="spinner"></div>
<span>Analyzing your request...</span>
</div>
</div>
<!-- Recognized Intent -->
<div id="recognizedIntent" class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg hidden">
<h4 class="font-medium mb-2">Recognized Intent:</h4>
<div class="space-y-2">
<p><strong>Type:</strong> <span id="intentType"></span></p>
<p><strong>Context:</strong> <span id="intentContext"></span></p>
<p><strong>Parameters:</strong> <span id="intentParameters"></span></p>
</div>
</div>
<!-- Suggestions -->
<div id="suggestions" class="mb-4 p-3 bg-gray-50 border border-gray-200 rounded-lg hidden">
<h5 class="font-medium mb-2">Similar Requests:</h5>
<div id="suggestionsList" class="flex flex-wrap gap-2"></div>
</div>
<!-- Input Area -->
<div class="mb-4">
<textarea id="userInput" rows="4" placeholder="Describe what you want to analyze..." class="w-full p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"></textarea>
</div>
<!-- Action Buttons -->
<div class="flex justify-between items-center">
<button id="clearIntentBtn" class="px-4 py-2 text-gray-600 hover:text-gray-800">
Clear
</button>
<button id="analyzeBtn" class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:bg-gray-400 disabled:cursor-not-allowed">
Analyze
</button>
</div>
</div>
</div>
<!-- Dashboard Component -->
<div id="dashboardComponent" class="mb-8 hidden">
<h2 class="text-xl font-semibold mb-4 text-gray-700">Analysis Results</h2>
<div class="bg-white rounded-lg shadow-md p-6">
<!-- View Selector -->
<div class="mb-4 flex space-x-2">
<button id="chartViewBtn" class="px-4 py-2 rounded-lg transition-colors bg-blue-600 text-white">
Chart
</button>
<button id="tableViewBtn" class="px-4 py-2 rounded-lg transition-colors bg-gray-200 text-gray-700 hover:bg-gray-300">
Table
</button>
<button id="summaryViewBtn" class="px-4 py-2 rounded-lg transition-colors bg-gray-200 text-gray-700 hover:bg-gray-300">
Summary
</button>
</div>
<!-- Export Controls -->
<div class="mb-4 flex space-x-2">
<button id="exportExcelBtn" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
Export to Excel
</button>
<button id="exportPDFBtn" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
Export to PDF
</button>
<button id="exportCSVBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Export to CSV
</button>
<button id="exportJSONBtn" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors">
Export to JSON
</button>
</div>
<!-- Main Content -->
<div id="mainContent" class="bg-white rounded-lg shadow-md p-6">
<div class="text-center py-8 text-gray-500">
<p>No analysis results available. Please upload a file and perform analysis.</p>
</div>
</div>
</div>
</div>
</div>
<script src="excel-analyzer.js"></script>
</body>
</html>

View file

@ -0,0 +1,988 @@
// Excel Analyzer - JavaScript Implementation
class FileUploadService {
constructor() {
this.maxFileSize = 10 * 1024 * 1024 // 10MB
}
validateFile(file) {
const errors = []
const warnings = []
// Check file size
if (file.size > this.maxFileSize) {
errors.push(`File size exceeds limit of ${this.maxFileSize / (1024 * 1024)}MB`)
}
// Check file type
const fileType = file.type.toLowerCase()
const fileName = file.name.toLowerCase()
const isExcel = fileType.includes("sheet") || fileType.includes("spreadsheet") || fileName.endsWith(".xlsx")
const isCSV = fileType.includes("csv") || fileName.endsWith(".csv")
if (!isExcel && !isCSV) {
errors.push("Invalid file format. Only .xlsx and .csv files are supported.")
}
return {
isValid: errors.length === 0,
errors,
warnings,
}
}
async getFilePreview(file) {
const data = await this.parseFile(file)
const firstRow = data[0] || []
const sampleData = data.slice(1, 6) // First 5 rows of data
// Infer column types
const columnTypes = {}
firstRow.forEach((col, index) => {
const sampleValues = data.slice(1, 20).map((row) => row[index]) // Sample 20 rows
const types = new Set(sampleValues.map(this.inferType))
columnTypes[col] = Array.from(types).join(" | ")
})
return {
columns: firstRow,
sampleData,
rowCount: data.length - 1, // Exclude header row
columnTypes,
}
}
async uploadFile(file) {
const data = await this.parseFile(file)
const preview = await this.getFilePreview(file)
return {
id: this.generateId(),
name: file.name,
size: file.size,
type: file.type,
preview,
data,
}
}
async parseFile(file) {
const fileType = file.name.toLowerCase().split(".").pop()
if (fileType === "xlsx") {
return this.parseExcel(file)
} else if (fileType === "csv") {
return this.parseCSV(file)
} else {
throw new Error("Unsupported file format")
}
}
async parseExcel(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
if (!e.target?.result) {
reject(new Error("Failed to read file"))
return
}
try {
const data = new Uint8Array(e.target.result)
const workbook = XLSX.read(data, { type: "array" })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
resolve(jsonData)
} catch (error) {
reject(new Error("Failed to parse Excel file"))
}
}
reader.onerror = () => {
reject(new Error("Failed to read file"))
}
reader.readAsArrayBuffer(file)
})
}
async parseCSV(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
if (!e.target?.result) {
reject(new Error("Failed to read file"))
return
}
try {
const csvData = e.target.result
const result = Papa.parse(csvData, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
})
if (result.errors.length > 0) {
reject(new Error(result.errors.map((e) => e.message).join(", ")))
return
}
// Convert to array of arrays format
const data = []
if (result.data.length > 0) {
const headers = Object.keys(result.data[0])
data.push(headers)
result.data.forEach((row) => {
data.push(headers.map((header) => row[header]))
})
}
resolve(data)
} catch (error) {
reject(new Error("Failed to parse CSV file"))
}
}
reader.onerror = () => {
reject(new Error("Failed to read file"))
}
reader.readAsText(file)
})
}
inferType(value) {
if (value === null || value === undefined || value === "") return "empty"
if (!isNaN(Date.parse(value))) return "date"
if (!isNaN(parseFloat(value)) && isFinite(value)) return "number"
if (typeof value === "boolean" || value === "true" || value === "false") return "boolean"
return "string"
}
generateId() {
return Math.random().toString(36).substr(2, 9)
}
}
class AIService {
constructor() {
this.openAiApiKey = "" // No API key for mock implementation
}
async recognizeIntent(userInput) {
// Mock implementation - replace with actual OpenAI API call
const mockIntents = [
{
type: "descriptive",
parameters: { columns: ["Sales", "Profit"] },
context: "Analyze sales and profit data",
},
{
type: "predictive",
parameters: { target: "Sales", timePeriod: "next quarter" },
context: "Forecast future sales based on historical data",
},
{
type: "diagnostic",
parameters: { issue: "declining sales", timeframe: "last 6 months" },
context: "Investigate reasons for declining sales",
},
{
type: "prescriptive",
parameters: { goal: "increase profit", budget: 10000 },
context: "Recommend actions to increase profit",
},
]
// Simple intent recognition based on keywords
const lowerInput = userInput.toLowerCase()
if (lowerInput.includes("forecast") || lowerInput.includes("predict")) {
return mockIntents[1] || mockIntents[0]
} else if (lowerInput.includes("why") || lowerInput.includes("reason")) {
return mockIntents[2] || mockIntents[0]
} else if (lowerInput.includes("recommend") || lowerInput.includes("suggest")) {
return mockIntents[3] || mockIntents[0]
} else {
return mockIntents[0]
}
}
async extractParameters(intent) {
// Mock implementation - replace with actual parameter extraction
const mockParameters = {
filters: [],
aggregations: [],
transformations: [],
}
// Add default filters based on intent type
if (intent.type === "descriptive") {
mockParameters.aggregations.push({
type: "sum",
column: "Sales",
alias: "Total Sales",
})
mockParameters.aggregations.push({
type: "average",
column: "Profit",
alias: "Average Profit",
})
}
if (intent.type === "predictive") {
mockParameters.filters.push({
column: "Date",
operator: "greaterThan",
value: "2023-01-01",
})
}
return mockParameters
}
async analyzeData(data, intent) {
// Mock implementation - replace with actual data analysis
const mockResults = {
summary: {
metrics: {
totalRows: data.length - 1,
totalColumns: data[0]?.length || 0,
completeness: 0.95,
},
trends: [],
patterns: [],
anomalies: [],
},
charts: [],
tables: [],
insights: [],
recommendations: [],
}
// Generate mock insights based on intent type
if (intent.type === "descriptive") {
mockResults.insights.push({
type: "pattern",
description: "Sales show a consistent upward trend",
severity: "medium",
data: { trend: "upward", confidence: 0.8 },
})
}
if (intent.type === "predictive") {
mockResults.recommendations.push({
type: "action",
description: "Increase marketing budget by 10% to boost sales",
priority: "high",
steps: ["Review current marketing spend", "Allocate additional budget", "Monitor results"],
})
}
return mockResults
}
async getSimilarRequests(intent) {
// Mock implementation - replace with actual similar request generation
const similarRequests = [
"Show me the total sales by region",
"What are the top performing products?",
"Analyze profit margins by category",
"Compare sales performance year over year",
"Identify trends in customer behavior",
]
return similarRequests
}
}
class ExcelAnalyzer {
constructor() {
this.fileUploadService = new FileUploadService()
this.aiService = new AIService()
this.uploadedFile = null
this.analysisResults = null
this.currentView = "chart"
this.initializeEventListeners()
}
initializeEventListeners() {
// Upload Component Events
const fileInput = document.getElementById("fileInput")
const uploadArea = document.getElementById("uploadArea")
const removeFileBtn = document.getElementById("removeFileBtn")
const uploadFileBtn = document.getElementById("uploadFileBtn")
if (fileInput) {
fileInput.addEventListener("change", (e) => {
const files = e.target.files
if (files && files.length > 0) {
this.handleFileSelect(files[0])
}
})
}
if (uploadArea) {
uploadArea.addEventListener("dragenter", (e) => this.handleDragEnter(e))
uploadArea.addEventListener("dragleave", (e) => this.handleDragLeave(e))
uploadArea.addEventListener("drop", (e) => this.handleDrop(e))
uploadArea.addEventListener("dragover", (e) => e.preventDefault())
}
if (removeFileBtn) {
removeFileBtn.addEventListener("click", () => this.clearFile())
}
if (uploadFileBtn) {
uploadFileBtn.addEventListener("click", () => this.uploadFile())
}
// Intent Input Component Events
const analyzeBtn = document.getElementById("analyzeBtn")
const clearIntentBtn = document.getElementById("clearIntentBtn")
const userInput = document.getElementById("userInput")
if (analyzeBtn) {
analyzeBtn.addEventListener("click", () => this.recognizeIntent())
}
if (clearIntentBtn) {
clearIntentBtn.addEventListener("click", () => this.clearIntent())
}
if (userInput) {
userInput.addEventListener("input", (e) => this.handleInputChange(e))
}
// Dashboard Component Events
const chartViewBtn = document.getElementById("chartViewBtn")
const tableViewBtn = document.getElementById("tableViewBtn")
const summaryViewBtn = document.getElementById("summaryViewBtn")
if (chartViewBtn) {
chartViewBtn.addEventListener("click", () => this.switchView("chart"))
}
if (tableViewBtn) {
tableViewBtn.addEventListener("click", () => this.switchView("table"))
}
if (summaryViewBtn) {
summaryViewBtn.addEventListener("click", () => this.switchView("summary"))
}
// Export Buttons
const exportExcelBtn = document.getElementById("exportExcelBtn")
const exportPDFBtn = document.getElementById("exportPDFBtn")
const exportCSVBtn = document.getElementById("exportCSVBtn")
const exportJSONBtn = document.getElementById("exportJSONBtn")
if (exportExcelBtn) {
exportExcelBtn.addEventListener("click", () => this.exportData("excel"))
}
if (exportPDFBtn) {
exportPDFBtn.addEventListener("click", () => this.exportData("pdf"))
}
if (exportCSVBtn) {
exportCSVBtn.addEventListener("click", () => this.exportData("csv"))
}
if (exportJSONBtn) {
exportJSONBtn.addEventListener("click", () => this.exportData("json"))
}
}
handleDragEnter(e) {
e.preventDefault()
const uploadArea = document.getElementById("uploadArea")
if (uploadArea) {
uploadArea.classList.add("border-blue-500", "bg-blue-50")
}
}
handleDragLeave(e) {
e.preventDefault()
const uploadArea = document.getElementById("uploadArea")
if (uploadArea) {
uploadArea.classList.remove("border-blue-500", "bg-blue-50")
}
}
handleDrop(e) {
e.preventDefault()
const uploadArea = document.getElementById("uploadArea")
if (uploadArea) {
uploadArea.classList.remove("border-blue-500", "bg-blue-50")
}
const files = e.dataTransfer.files
if (files.length === 0) return
const file = files[0]
this.handleFileSelect(file)
}
async handleFileSelect(file) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.classList.add("hidden")
}
// Validate file
const validation = this.fileUploadService.validateFile(file)
if (!validation.isValid) {
if (errorMessage) {
errorMessage.textContent = validation.errors.join(", ")
errorMessage.classList.remove("hidden")
}
return
}
// Check file size
if (file.size > this.fileUploadService.maxFileSize) {
if (errorMessage) {
errorMessage.textContent = `File size exceeds limit of ${this.fileUploadService.maxFileSize / (1024 * 1024)}MB`
errorMessage.classList.remove("hidden")
}
return
}
// Check file format
const fileExtension = file.name.split(".").pop()?.toLowerCase()
const acceptedFormats = [".xlsx", ".csv"]
if (!acceptedFormats.includes(`.${fileExtension}`)) {
if (errorMessage) {
errorMessage.textContent = `Invalid file format. Accepted formats: ${acceptedFormats.join(", ")}`
errorMessage.classList.remove("hidden")
}
return
}
// Show selected file info
const selectedFileInfo = document.getElementById("selectedFileInfo")
const selectedFileName = document.getElementById("selectedFileName")
if (selectedFileInfo && selectedFileName) {
selectedFileInfo.classList.remove("hidden")
selectedFileName.textContent = file.name
}
// Get file preview
try {
const preview = await this.fileUploadService.getFilePreview(file)
this.updateFilePreview(preview, file)
} catch (err) {
if (errorMessage) {
errorMessage.textContent = "Failed to preview file"
errorMessage.classList.remove("hidden")
}
}
}
updateFilePreview(preview, file) {
const filePreview = document.getElementById("filePreview")
const previewName = document.getElementById("previewName")
const previewSize = document.getElementById("previewSize")
const previewColumns = document.getElementById("previewColumns")
const previewRows = document.getElementById("previewRows")
const uploadButton = document.getElementById("uploadButton")
if (filePreview) filePreview.classList.remove("hidden")
if (previewName) previewName.textContent = file.name
if (previewSize) previewSize.textContent = Math.round(file.size / 1024)
if (previewColumns) previewColumns.textContent = preview.columns.join(", ")
if (previewRows) previewRows.textContent = preview.rowCount
if (uploadButton) uploadButton.classList.remove("hidden")
}
clearFile() {
this.uploadedFile = null
this.analysisResults = null
// Hide components
const uploadComponent = document.getElementById("uploadComponent")
const intentInputComponent = document.getElementById("intentInputComponent")
const dashboardComponent = document.getElementById("dashboardComponent")
if (uploadComponent) uploadComponent.classList.remove("hidden")
if (intentInputComponent) intentInputComponent.classList.add("hidden")
if (dashboardComponent) dashboardComponent.classList.add("hidden")
// Reset upload area
const uploadArea = document.getElementById("uploadArea")
if (uploadArea) {
uploadArea.classList.remove("border-blue-500", "bg-blue-50")
}
// Clear file preview and selected file info
const filePreview = document.getElementById("filePreview")
const selectedFileInfo = document.getElementById("selectedFileInfo")
const errorMessage = document.getElementById("errorMessage")
const uploadButton = document.getElementById("uploadButton")
if (filePreview) filePreview.classList.add("hidden")
if (selectedFileInfo) selectedFileInfo.classList.add("hidden")
if (errorMessage) errorMessage.classList.add("hidden")
if (uploadButton) uploadButton.classList.add("hidden")
}
async uploadFile() {
const selectedFileInfo = document.getElementById("selectedFileInfo")
const selectedFileName = document.getElementById("selectedFileName")
if (!selectedFileInfo || !selectedFileName) return
const fileName = selectedFileName.textContent
const fileInput = document.getElementById("fileInput")
const file = fileInput?.files?.[0]
if (!file) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.textContent = "No file selected"
errorMessage.classList.remove("hidden")
}
return
}
try {
// Show upload progress
const uploadFileBtn = document.getElementById("uploadFileBtn")
if (uploadFileBtn) {
uploadFileBtn.textContent = "Uploading..."
uploadFileBtn.disabled = true
}
// Upload file
this.uploadedFile = await this.fileUploadService.uploadFile(file)
// Show next component
const intentInputComponent = document.getElementById("intentInputComponent")
if (intentInputComponent) {
intentInputComponent.classList.remove("hidden")
}
// Hide upload component
if (uploadFileBtn) {
uploadFileBtn.textContent = "Upload File"
uploadFileBtn.disabled = false
}
} catch (err) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.textContent = "Failed to upload file"
errorMessage.classList.remove("hidden")
}
}
}
handleInputChange(e) {
const userInput = e.target.value
const errorMessage = document.getElementById("errorMessage")
const recognizedIntent = document.getElementById("recognizedIntent")
const suggestions = document.getElementById("suggestions")
if (errorMessage) errorMessage.classList.add("hidden")
if (recognizedIntent) recognizedIntent.classList.add("hidden")
if (suggestions) suggestions.classList.add("hidden")
}
async recognizeIntent() {
const userInput = document.getElementById("userInput")
const analyzeBtn = document.getElementById("analyzeBtn")
const processingIndicator = document.getElementById("processingIndicator")
if (!userInput || !analyzeBtn || !processingIndicator) return
const input = userInput.value.trim()
if (!input) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.textContent = "Please enter your analysis request"
errorMessage.classList.remove("hidden")
}
return
}
// Show processing indicator
if (processingIndicator) processingIndicator.classList.remove("hidden")
if (analyzeBtn) {
analyzeBtn.textContent = "Analyzing..."
analyzeBtn.disabled = true
}
try {
const intent = await this.aiService.recognizeIntent(input)
const parameters = await this.aiService.extractParameters(intent)
this.analysisResults = await this.aiService.analyzeData(this.uploadedFile.data, intent)
// Show recognized intent
const intentType = document.getElementById("intentType")
const intentContext = document.getElementById("intentContext")
const intentParameters = document.getElementById("intentParameters")
const recognizedIntent = document.getElementById("recognizedIntent")
if (intentType) intentType.textContent = intent.type
if (intentContext) intentContext.textContent = intent.context
if (intentParameters) intentParameters.textContent = JSON.stringify(intent.parameters, null, 2)
if (recognizedIntent) recognizedIntent.classList.remove("hidden")
// Show suggestions
const suggestionsList = document.getElementById("suggestionsList")
const suggestions = await this.aiService.getSimilarRequests(intent)
if (suggestionsList) {
suggestionsList.innerHTML = suggestions
.map(
(suggestion) =>
`<button class="px-3 py-1 bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
onclick="excelAnalyzer.handleSuggestionClick('${suggestion.replace(/'/g, "\\'")}')">${suggestion}</button>`,
)
.join("")
const suggestionsContainer = document.getElementById("suggestions")
if (suggestionsContainer) suggestionsContainer.classList.remove("hidden")
}
// Show dashboard component
const dashboardComponent = document.getElementById("dashboardComponent")
if (dashboardComponent) {
dashboardComponent.classList.remove("hidden")
}
} catch (err) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.textContent = "Failed to recognize intent. Please try again."
errorMessage.classList.remove("hidden")
}
} finally {
if (processingIndicator) processingIndicator.classList.add("hidden")
if (analyzeBtn) {
analyzeBtn.textContent = "Analyze"
analyzeBtn.disabled = false
}
}
}
handleSuggestionClick(suggestion) {
const userInput = document.getElementById("userInput")
const suggestionsList = document.getElementById("suggestionsList")
if (userInput) {
userInput.value = suggestion
}
if (suggestionsList) {
suggestionsList.innerHTML = ""
const suggestions = document.getElementById("suggestions")
if (suggestions) suggestions.classList.add("hidden")
}
}
clearIntent() {
const userInput = document.getElementById("userInput")
const recognizedIntent = document.getElementById("recognizedIntent")
const suggestions = document.getElementById("suggestions")
const processingIndicator = document.getElementById("processingIndicator")
const errorMessage = document.getElementById("errorMessage")
if (userInput) userInput.value = ""
if (recognizedIntent) recognizedIntent.classList.add("hidden")
if (suggestions) suggestions.classList.add("hidden")
if (processingIndicator) processingIndicator.classList.add("hidden")
if (errorMessage) errorMessage.classList.add("hidden")
}
switchView(view) {
const chartViewBtn = document.getElementById("chartViewBtn")
const tableViewBtn = document.getElementById("tableViewBtn")
const summaryViewBtn = document.getElementById("summaryViewBtn")
// Update button styles
if (chartViewBtn) {
chartViewBtn.className = `px-4 py-2 rounded-lg transition-colors ${view === "chart" ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-700 hover:bg-gray-300"}`
}
if (tableViewBtn) {
tableViewBtn.className = `px-4 py-2 rounded-lg transition-colors ${view === "table" ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-700 hover:bg-gray-300"}`
}
if (summaryViewBtn) {
summaryViewBtn.className = `px-4 py-2 rounded-lg transition-colors ${view === "summary" ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-700 hover:bg-gray-300"}`
}
this.currentView = view
this.renderView()
}
renderView() {
const mainContent = document.getElementById("mainContent")
if (!mainContent || !this.analysisResults) return
let viewContent
switch (this.currentView) {
case "chart":
viewContent = this.renderChart()
break
case "table":
viewContent = this.renderTable()
break
case "summary":
viewContent = this.renderSummary()
break
default:
viewContent =
'<div class="text-center py-8 text-gray-500"><p>No analysis results available. Please upload a file and perform analysis.</p></div>'
}
mainContent.innerHTML = viewContent
}
renderChart() {
if (!this.analysisResults || !this.analysisResults.charts || this.analysisResults.charts.length === 0) {
return '<div class="text-center py-8 text-gray-500"><p>No chart data available</p></div>'
}
const chartData = this.analysisResults.charts[0]
const chartId = "analysisChart"
// Create canvas element
const canvas = document.createElement("canvas")
canvas.id = chartId
canvas.className = "relative h-64"
// Append to main content
const mainContent = document.getElementById("mainContent")
mainContent.innerHTML = ""
mainContent.appendChild(canvas)
// Create chart
const ctx = canvas.getContext("2d")
if (ctx) {
const chartConfig = {
type: chartData.type,
data: {
labels: chartData.data.map((row) => row[0]),
datasets: [
{
label: chartData.title,
data: chartData.data.map((row) => row[1]),
backgroundColor: "rgba(54, 162, 235, 0.5)",
borderColor: "rgba(54, 162, 235, 1)",
borderWidth: 1,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
},
},
},
}
return new Chart(ctx, chartConfig)
}
return '<div class="text-center py-8 text-gray-500"><p>Failed to render chart</p></div>'
}
renderTable() {
if (!this.analysisResults || !this.analysisResults.tables || this.analysisResults.tables.length === 0) {
return '<div class="text-center py-8 text-gray-500"><p>No table data available</p></div>'
}
const tables = this.analysisResults.tables
let tableContent = ""
tables.forEach((table, index) => {
tableContent += `
<div class="mb-4">
<h4 class="font-medium mb-2">${table.title}</h4>
<div class="overflow-x-auto">
<table class="w-full border-collapse">
<thead>
<tr class="border-b">
${table.columns
.map((column) => `<th class="px-4 py-2 text-left">${column}</th>`)
.join("")}
</tr>
</thead>
<tbody>
${table.data
.map(
(row, rowIndex) =>
`<tr class="border-b hover:bg-gray-50">
${row.map((cell, cellIndex) => `<td class="px-4 py-2">${cell}</td>`).join("")}
</tr>`,
)
.join("")}
</tbody>
</table>
</div>
</div>
`
})
return `
<div class="p-4">
<h3 class="text-lg font-semibold mb-3">Data Table</h3>
${tableContent || '<p class="text-gray-500">No table data available</p>'}
</div>
`
}
renderSummary() {
if (!this.analysisResults || !this.analysisResults.summary) {
return '<div class="text-center py-8 text-gray-500"><p>No summary data available</p></div>'
}
const summary = this.analysisResults.summary
return `
<div class="p-4">
<h3 class="text-lg font-semibold mb-3">Analysis Summary</h3>
<div class="space-y-4">
<div class="bg-blue-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">Key Metrics</h4>
<div class="space-y-2">
${Object.entries(summary.metrics || {})
.map(
([metric, value]) =>
`<div class="flex justify-between">
<span class="text-gray-600">${metric}:</span>
<span class="font-medium">${value}</span>
</div>`,
)
.join("")}
</div>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">Insights</h4>
${
summary.insights && summary.insights.length > 0
? `
${summary.insights
.map(
(insight, index) =>
`<div class="mb-2 p-2 bg-white rounded">
<p class="text-sm font-medium text-green-800">${insight.description}</p>
<p class="text-xs text-gray-600">Severity: ${insight.severity}</p>
</div>`,
)
.join("")}
`
: '<p class="text-gray-500">No insights available</p>'
}
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<h4 class="font-medium mb-2">Recommendations</h4>
${
summary.recommendations && summary.recommendations.length > 0
? `
${summary.recommendations
.map(
(recommendation, index) =>
`<div class="mb-2 p-2 bg-white rounded">
<p class="text-sm font-medium text-purple-800">${recommendation.description}</p>
<p class="text-xs text-gray-600">Priority: ${recommendation.priority}</p>
<ul class="text-xs text-gray-600 mt-1">
${recommendation.steps.map((step, stepIndex) => `<li>- ${step}</li>`).join("")}
</ul>
</div>`,
)
.join("")}
`
: '<p class="text-gray-500">No recommendations available</p>'
}
</div>
</div>
</div>
`
}
exportData(format) {
if (!this.analysisResults) {
const errorMessage = document.getElementById("errorMessage")
if (errorMessage) {
errorMessage.textContent = "No analysis results to export"
errorMessage.classList.remove("hidden")
}
return
}
// Mock export functionality
let data = JSON.stringify(this.analysisResults, null, 2)
let filename = "analysis-results"
let contentType = "application/json"
switch (format) {
case "excel":
filename += ".xlsx"
// Note: Actual Excel export would require additional libraries
break
case "pdf":
filename += ".pdf"
// Note: Actual PDF export would require additional libraries
break
case "csv":
filename += ".csv"
// Convert to CSV format
data = this.convertToCSV(this.analysisResults)
contentType = "text/csv"
break
case "json":
filename += ".json"
break
}
// Create and download file
const blob = new Blob([data], { type: contentType })
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
}
convertToCSV(data) {
// Simple CSV conversion for demonstration
const results = data
const rows = []
// Add headers
const headers = ["Type", "Metrics", "Trends", "Patterns", "Anomalies", "Insights", "Recommendations"]
rows.push(headers.join(","))
// Add data
const row = [
"Summary",
JSON.stringify(results.summary?.metrics || ""),
JSON.stringify(results.summary?.trends || ""),
JSON.stringify(results.summary?.patterns || ""),
JSON.stringify(results.summary?.anomalies || ""),
JSON.stringify(results.summary?.insights || ""),
JSON.stringify(results.summary?.recommendations || ""),
]
rows.push(row.join(","))
return rows.join("\n")
}
}
// Initialize the Excel Analyzer
const excelAnalyzer = new ExcelAnalyzer()