From dd711a7d9678bc80929c55b6435715ac5b1844c5 Mon Sep 17 00:00:00 2001 From: gkastanis Date: Tue, 2 Dec 2025 14:23:47 +0200 Subject: [PATCH] Add missing frontend lib files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #7 - Add frontend/src/lib/auth.tsx with AuthProvider and useAuth hook - Add frontend/src/lib/api.ts with chat, getKBStats, listDocuments, etc. - Add frontend/src/lib/filePicker.ts with path utilities - Update .gitignore to not ignore frontend/src/lib/ These files were imported but never committed, preventing fresh installations from building the frontend. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 1 + frontend/src/lib/api.ts | 88 ++++++++++++++++++++++++++++ frontend/src/lib/auth.tsx | 102 +++++++++++++++++++++++++++++++++ frontend/src/lib/filePicker.ts | 36 ++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/auth.tsx create mode 100644 frontend/src/lib/filePicker.ts diff --git a/.gitignore b/.gitignore index 53d2cf7..8d1ece0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ eggs/ .eggs/ lib/ lib64/ +!frontend/src/lib/ parts/ sdist/ var/ diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..0449114 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,88 @@ +const API_BASE = 'http://localhost:8051'; + +export interface ChatResponse { + response: string; + sources?: Array<{ + filename: string; + content: string; + score: number; + }>; + thinking_time?: number; +} + +export interface KBStats { + document_count: number; + chunk_count: number; + last_updated?: string; + total_size?: number; +} + +export interface Document { + filename: string; + chunk_count: number; + created_at?: string; +} + +export async function chat( + kbName: string, + query: string, + options?: { useHybrid?: boolean; useRerank?: boolean; useAgentic?: boolean } +): Promise { + const res = await fetch(`${API_BASE}/api/kb/${kbName}/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query, + use_hybrid: options?.useHybrid ?? false, + use_rerank: options?.useRerank ?? false, + use_agentic: options?.useAgentic ?? false, + }), + }); + + if (!res.ok) { + throw new Error(`Chat failed: ${res.statusText}`); + } + + return res.json(); +} + +export async function getKBStats(kbName: string): Promise { + const res = await fetch(`${API_BASE}/api/kb/${kbName}/stats`); + if (!res.ok) { + throw new Error(`Failed to get KB stats: ${res.statusText}`); + } + return res.json(); +} + +export async function listDocuments(kbName: string): Promise { + const res = await fetch(`${API_BASE}/api/kb/${kbName}/documents`); + if (!res.ok) { + throw new Error(`Failed to list documents: ${res.statusText}`); + } + const data = await res.json(); + return data.documents || []; +} + +export async function uploadDocument(kbName: string, file: File): Promise { + const formData = new FormData(); + formData.append('file', file); + + const res = await fetch(`${API_BASE}/api/kb/${kbName}/upload`, { + method: 'POST', + body: formData, + }); + + if (!res.ok) { + throw new Error(`Failed to upload document: ${res.statusText}`); + } +} + +export async function deleteDocument(kbName: string, filename: string): Promise { + const res = await fetch(`${API_BASE}/api/kb/${kbName}/documents/${encodeURIComponent(filename)}`, { + method: 'DELETE', + }); + + if (!res.ok) { + throw new Error(`Failed to delete document: ${res.statusText}`); + } +} diff --git a/frontend/src/lib/auth.tsx b/frontend/src/lib/auth.tsx new file mode 100644 index 0000000..876a93a --- /dev/null +++ b/frontend/src/lib/auth.tsx @@ -0,0 +1,102 @@ +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +const API_BASE = 'http://localhost:8051'; + +interface AuthContextType { + isAuthenticated: boolean; + isAuthEnabled: boolean; + isLoading: boolean; + user: { email: string } | null; + login: (email: string, password: string) => Promise; + logout: () => void; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isAuthEnabled, setIsAuthEnabled] = useState(true); + const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState<{ email: string } | null>(null); + + useEffect(() => { + checkAuthStatus(); + }, []); + + const checkAuthStatus = async () => { + try { + // Check if auth is enabled + const statusRes = await fetch(`${API_BASE}/api/auth/status`); + if (statusRes.ok) { + const statusData = await statusRes.json(); + setIsAuthEnabled(statusData.auth_enabled); + + if (!statusData.auth_enabled) { + setIsAuthenticated(true); + setIsLoading(false); + return; + } + } + + // Check if user is already logged in + const token = localStorage.getItem('auth_token'); + if (token) { + const meRes = await fetch(`${API_BASE}/api/auth/me`, { + headers: { Authorization: `Bearer ${token}` } + }); + if (meRes.ok) { + const userData = await meRes.json(); + setUser(userData); + setIsAuthenticated(true); + } else { + localStorage.removeItem('auth_token'); + } + } + } catch (error) { + // If we can't reach the server, assume auth is disabled for local dev + console.warn('Could not check auth status:', error); + setIsAuthEnabled(false); + setIsAuthenticated(true); + } finally { + setIsLoading(false); + } + }; + + const login = async (email: string, password: string) => { + const res = await fetch(`${API_BASE}/api/auth/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }); + + if (!res.ok) { + const error = await res.json(); + throw new Error(error.detail || 'Login failed'); + } + + const data = await res.json(); + localStorage.setItem('auth_token', data.access_token); + setUser({ email }); + setIsAuthenticated(true); + }; + + const logout = () => { + localStorage.removeItem('auth_token'); + setUser(null); + setIsAuthenticated(false); + }; + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +} diff --git a/frontend/src/lib/filePicker.ts b/frontend/src/lib/filePicker.ts new file mode 100644 index 0000000..6965253 --- /dev/null +++ b/frontend/src/lib/filePicker.ts @@ -0,0 +1,36 @@ +export interface CommonPath { + label: string; + path: string; + description?: string; +} + +export function getCommonProjectPaths(): CommonPath[] { + return [ + { label: 'Home', path: '~', description: 'Your home directory' }, + { label: 'Documents', path: '~/Documents', description: 'Documents folder' }, + { label: 'Projects', path: '~/Projects', description: 'Common projects folder' }, + { label: 'Sites', path: '~/sites', description: 'Web projects' }, + { label: 'Code', path: '~/code', description: 'Code repositories' }, + { label: 'Desktop', path: '~/Desktop', description: 'Desktop folder' }, + ]; +} + +export function formatPathForDisplay(path: string): string { + if (!path) return ''; + + // Replace home directory with ~ + const homeDir = '/home/' + (typeof window !== 'undefined' ? 'user' : 'user'); + if (path.startsWith(homeDir)) { + return path.replace(homeDir, '~'); + } + + // Shorten long paths + if (path.length > 50) { + const parts = path.split('/'); + if (parts.length > 4) { + return `${parts[0]}/${parts[1]}/.../${parts[parts.length - 2]}/${parts[parts.length - 1]}`; + } + } + + return path; +}