From b23daec6e57a38fdd2f43e26e89b41a902a33e79 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 19:07:38 +0000 Subject: [PATCH] feat: Store API key locally in browser instead of build-time - Add apiKeyService for localStorage-based API key management - Add ApiKeyModal component for entering/updating API key - Update geminiService to use dynamic API key - Remove .env.example as API key is now user-provided - Update to gemini-2.0-flash model The API key is now stored only in the user's browser localStorage, making the app more secure and easier to deploy. https://claude.ai/code/session_01DBAyjuKW8Qtzixc64qn9KP --- .env.example | 3 -- App.tsx | 48 ++++++++++++++++++--- components/ApiKeyModal.tsx | 85 ++++++++++++++++++++++++++++++++++++++ services/apiKeyService.ts | 18 ++++++++ services/geminiService.ts | 12 +++++- 5 files changed, 155 insertions(+), 11 deletions(-) delete mode 100644 .env.example create mode 100644 components/ApiKeyModal.tsx create mode 100644 services/apiKeyService.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index 709917c..0000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -# Google Gemini API Key (erforderlich) -# Holen Sie sich einen Key unter: https://aistudio.google.com/apikey -GEMINI_API_KEY=your_gemini_api_key_here diff --git a/App.tsx b/App.tsx index 8290833..53a67e8 100644 --- a/App.tsx +++ b/App.tsx @@ -2,9 +2,11 @@ import React, { useState, useEffect } from 'react'; import { AppStatus, FileData, FormResponse } from './types'; import { FileUpload } from './components/FileUpload'; import { ReviewPanel } from './components/ReviewPanel'; +import { ApiKeyModal } from './components/ApiKeyModal'; import { processDocuments } from './services/geminiService'; import { getPdfFields, PdfFieldInfo } from './services/pdfService'; -import { Bot, Sparkles, ArrowRight, FileCheck2, ScanText, Loader2, AlertTriangle, FileText, Check } from 'lucide-react'; +import { getApiKey, setApiKey, hasApiKey } from './services/apiKeyService'; +import { Bot, Sparkles, ArrowRight, FileCheck2, ScanText, Loader2, AlertTriangle, FileText, Check, Settings } from 'lucide-react'; const App: React.FC = () => { const [status, setStatus] = useState(AppStatus.IDLE); @@ -13,6 +15,12 @@ const App: React.FC = () => { const [pdfFields, setPdfFields] = useState([]); const [responseData, setResponseData] = useState(null); const [error, setError] = useState(null); + const [showApiKeyModal, setShowApiKeyModal] = useState(!hasApiKey()); + + const handleSaveApiKey = (key: string) => { + setApiKey(key); + setShowApiKeyModal(false); + }; useEffect(() => { const analyzePdf = async () => { @@ -55,14 +63,27 @@ const App: React.FC = () => { if (status === AppStatus.REVIEW && responseData && formFile && sourceFile) { return (
+ setShowApiKeyModal(false)} + currentKey={getApiKey() || ''} + />
-
+
AutoForm AI
+
{ return (
+ setShowApiKeyModal(false) : undefined} + currentKey={getApiKey() || ''} + /> {/* Header */}
@@ -91,10 +118,19 @@ const App: React.FC = () => {

Intelligent Document Processing

-
- 1. Scan - 2. Extract - 3. Review +
+
+ 1. Scan + 2. Extract + 3. Review +
+
diff --git a/components/ApiKeyModal.tsx b/components/ApiKeyModal.tsx new file mode 100644 index 0000000..298c9de --- /dev/null +++ b/components/ApiKeyModal.tsx @@ -0,0 +1,85 @@ +import React, { useState } from 'react'; +import { Key, ExternalLink, X } from 'lucide-react'; + +interface ApiKeyModalProps { + isOpen: boolean; + onSave: (key: string) => void; + onClose?: () => void; + currentKey?: string; +} + +export const ApiKeyModal: React.FC = ({ isOpen, onSave, onClose, currentKey }) => { + const [apiKey, setApiKey] = useState(currentKey || ''); + const [error, setError] = useState(''); + + if (!isOpen) return null; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!apiKey.trim()) { + setError('Bitte gib einen API Key ein'); + return; + } + if (!apiKey.startsWith('AI')) { + setError('Der Key sollte mit "AI" beginnen'); + return; + } + onSave(apiKey.trim()); + }; + + return ( +
+
+
+
+ +

Gemini API Key

+
+ {onClose && ( + + )} +
+ +
+

+ Dein API Key wird nur lokal in deinem Browser gespeichert und nie an unsere Server gesendet. +

+ +
+ + { setApiKey(e.target.value); setError(''); }} + placeholder="AIza..." + className="w-full px-4 py-3 border border-slate-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all" + autoFocus + /> + {error &&

{error}

} +
+ + + + API Key bei Google AI Studio holen + + + +
+
+
+ ); +}; diff --git a/services/apiKeyService.ts b/services/apiKeyService.ts new file mode 100644 index 0000000..ebc2741 --- /dev/null +++ b/services/apiKeyService.ts @@ -0,0 +1,18 @@ +const STORAGE_KEY = 'gemini_api_key'; + +export const getApiKey = (): string | null => { + return localStorage.getItem(STORAGE_KEY); +}; + +export const setApiKey = (key: string): void => { + localStorage.setItem(STORAGE_KEY, key); +}; + +export const clearApiKey = (): void => { + localStorage.removeItem(STORAGE_KEY); +}; + +export const hasApiKey = (): boolean => { + const key = getApiKey(); + return key !== null && key.length > 0; +}; diff --git a/services/geminiService.ts b/services/geminiService.ts index 9233a18..48af742 100644 --- a/services/geminiService.ts +++ b/services/geminiService.ts @@ -1,8 +1,15 @@ import { GoogleGenAI, Type, Schema } from "@google/genai"; import { FileData, FormResponse } from "../types"; import { PdfFieldInfo } from "./pdfService"; +import { getApiKey } from "./apiKeyService"; -const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); +const getAI = () => { + const apiKey = getApiKey(); + if (!apiKey) { + throw new Error("Kein API Key gesetzt. Bitte gib deinen Gemini API Key ein."); + } + return new GoogleGenAI({ apiKey }); +}; const responseSchema: Schema = { type: Type.OBJECT, @@ -130,7 +137,8 @@ export const processDocuments = async ( `; try { - const modelId = "gemini-3-flash-preview"; + const ai = getAI(); + const modelId = "gemini-2.0-flash"; const response = await ai.models.generateContent({ model: modelId,