-
Notifications
You must be signed in to change notification settings - Fork 0
feat: basic ocr #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
88f5ecc
3260050
2e0d1af
44aa8f5
ca56225
6b069df
5c3d082
972d47b
9c446b1
ba81982
5cd438d
faa7495
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
|
||
| # dependencies | ||
| /node_modules | ||
| /.pnp | ||
| .pnp.* | ||
| .yarn/* | ||
| !.yarn/patches | ||
| !.yarn/plugins | ||
| !.yarn/releases | ||
| !.yarn/versions | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| # next.js | ||
| /.next/ | ||
| /out/ | ||
|
|
||
| # production | ||
| /build | ||
|
|
||
| # misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| .pnpm-debug.log* | ||
|
|
||
| # env files (can opt-in for committing if needed) | ||
| .env* | ||
|
|
||
| # vercel | ||
| .vercel | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { NextRequest, NextResponse } from 'next/server' | ||
| import { GeminiProcessor } from '../../../lib/gemini' | ||
|
|
||
| export async function POST(request: NextRequest) { | ||
| try { | ||
| const formData = await request.formData() | ||
| const file = formData.get('file') as File | ||
|
|
||
| if (!file) { | ||
| return NextResponse.json({ error: 'No file provided' }, { status: 400 }) | ||
| } | ||
|
|
||
| const apiKey = process.env.GEMINI_API_KEY | ||
| if (!apiKey) { | ||
| return NextResponse.json({ error: 'Gemini API key not configured' }, { status: 500 }) | ||
| } | ||
|
|
||
| // Validate file type | ||
| const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'application/pdf'] | ||
| if (!allowedTypes.includes(file.type)) { | ||
| return NextResponse.json({ | ||
| error: 'Invalid file type. Please upload PNG, JPG, JPEG, or PDF files.' | ||
| }, { status: 400 }) | ||
| } | ||
|
|
||
| // Validate file size (max 10MB) | ||
| if (file.size > 10 * 1024 * 1024) { | ||
| return NextResponse.json({ | ||
| error: 'File too large. Please upload files smaller than 10MB.' | ||
| }, { status: 400 }) | ||
| } | ||
|
|
||
| const processor = new GeminiProcessor(apiKey) | ||
| const timetableData = await processor.processTimetableImage(file) | ||
|
|
||
| return NextResponse.json({ | ||
| success: true, | ||
| data: timetableData | ||
| }) | ||
|
|
||
| } catch (error) { | ||
| console.error('Error processing timetable:', error) | ||
| return NextResponse.json({ | ||
| success: false, | ||
| error: error instanceof Error ? error.message : 'Unknown error occurred' | ||
| }, { status: 500 }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| @tailwind base; | ||
| @tailwind components; | ||
| @tailwind utilities; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import type { Metadata } from 'next' | ||
| import { Inter } from 'next/font/google' | ||
| import './globals.css' | ||
|
|
||
| const inter = Inter({ subsets: ['latin'] }) | ||
|
|
||
| export const metadata: Metadata = { | ||
| title: 'TT2Cal - Timetable to iCal Converter', | ||
| description: 'Convert your timetable images to iCal format for easy calendar import', | ||
| } | ||
|
|
||
| export default function RootLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode | ||
| }) { | ||
| return ( | ||
| <html lang="en"> | ||
| <body className={inter.className}>{children}</body> | ||
| </html> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,158 @@ | ||||||||||||||||||||||||||||||||
| 'use client' | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import { useState } from 'react' | ||||||||||||||||||||||||||||||||
| import FileUpload from '../components/FileUpload' | ||||||||||||||||||||||||||||||||
| import DataEditor from '../components/DataEditor' | ||||||||||||||||||||||||||||||||
| import { TimetableData, ProcessingResult } from '../lib/types' | ||||||||||||||||||||||||||||||||
| import { generateIcal } from '../lib/ical-generator' | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export default function Home() { | ||||||||||||||||||||||||||||||||
| const [isProcessing, setIsProcessing] = useState(false) | ||||||||||||||||||||||||||||||||
| const [timetableData, setTimetableData] = useState<TimetableData | null>(null) | ||||||||||||||||||||||||||||||||
| const [error, setError] = useState<string | null>(null) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const handleFileUpload = async (file: File) => { | ||||||||||||||||||||||||||||||||
| setIsProcessing(true) | ||||||||||||||||||||||||||||||||
| setError(null) | ||||||||||||||||||||||||||||||||
| setTimetableData(null) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const formData = new FormData() | ||||||||||||||||||||||||||||||||
| formData.append('file', file) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const response = await fetch('/api/process-timetable', { | ||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||
| body: formData, | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| if (!response.ok) { | |
| let errorMessage = `Failed to process timetable (status ${response.status})` | |
| try { | |
| const errorBody = await response.json() | |
| if (errorBody && typeof (errorBody as any).error === 'string') { | |
| errorMessage = (errorBody as any).error | |
| } | |
| } catch { | |
| // Ignore JSON parsing errors and use the default error message | |
| } | |
| setError(errorMessage) | |
| return | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation for the API key environment variable. Although the code checks if the API key exists, it doesn't validate its format or provide guidance on what a valid key looks like. Consider adding a check for the expected key format (e.g., starts with expected prefix) and providing a more helpful error message that guides users to obtain and configure the API key correctly.