diff --git a/frontend/package.json b/frontend/package.json index c7a47bf..25e87e3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,14 +8,15 @@ "preview": "vite preview" }, "devDependencies": { - "@sveltejs/adapter-auto": "^3.0.0", + "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/kit": "^2.0.0", - "svelte": "^4.2.0", - "vite": "^5.0.0" + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" }, "dependencies": { - "marked": "^12.0.0", - "highlight.js": "^11.9.0" + "marked": "^15.0.0", + "highlight.js": "^11.11.0" }, "type": "module" } diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index 0f20da1..e5d918a 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -1,189 +1,177 @@ const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8000'; class ApiClient { - constructor() { - this.baseUrl = API_BASE; - } + constructor() { + this.token = null; + if (typeof window !== 'undefined') { + this.token = localStorage.getItem('token'); + } + } - getToken() { - if (typeof window !== 'undefined') { - return localStorage.getItem('token'); - } - return null; - } + setToken(token) { + this.token = token; + if (typeof window !== 'undefined') { + if (token) { + localStorage.setItem('token', token); + } else { + localStorage.removeItem('token'); + } + } + } - setToken(token) { - if (typeof window !== 'undefined') { - localStorage.setItem('token', token); - } - } + async request(endpoint, options = {}) { + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; - clearToken() { - if (typeof window !== 'undefined') { - localStorage.removeItem('token'); - } - } + if (this.token) { + headers['Authorization'] = `Bearer ${this.token}`; + } - getHeaders() { - const headers = { - 'Content-Type': 'application/json' - }; - const token = this.getToken(); - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - return headers; - } + const response = await fetch(`${API_BASE}${endpoint}`, { + ...options, + headers, + }); - async request(endpoint, options = {}) { - const url = `${this.baseUrl}${endpoint}`; - const config = { - ...options, - headers: { - ...this.getHeaders(), - ...options.headers - } - }; + if (response.status === 401) { + this.setToken(null); + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } + throw new Error('Unauthorized'); + } - const response = await fetch(url, config); - - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: 'Request failed' })); - throw new Error(error.detail || 'Request failed'); - } + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: 'Request failed' })); + throw new Error(error.detail || 'Request failed'); + } - return response.json(); - } + if (response.status === 204) { + return null; + } - // Auth endpoints - async login(email, password) { - const formData = new FormData(); - formData.append('username', email); - formData.append('password', password); - - const response = await fetch(`${this.baseUrl}/auth/login`, { - method: 'POST', - body: formData - }); + return response.json(); + } - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: 'Login failed' })); - throw new Error(error.detail || 'Login failed'); - } + async login(email, password) { + const data = await this.request('/auth/login', { + method: 'POST', + body: JSON.stringify({ email, password }), + }); + this.setToken(data.access_token); + return data; + } - const data = await response.json(); - this.setToken(data.access_token); - return data; - } + async register(email, username, password) { + return this.request('/auth/register', { + method: 'POST', + body: JSON.stringify({ email, username, password }), + }); + } - async register(email, username, password) { - return this.request('/auth/register', { - method: 'POST', - body: JSON.stringify({ email, username, password }) - }); - } + async getMe() { + return this.request('/auth/me'); + } - async getMe() { - return this.request('/auth/me'); - } + async updateMe(data) { + return this.request('/auth/me', { + method: 'PUT', + body: JSON.stringify(data), + }); + } - // Endpoints - async getEndpoints() { - return this.request('/endpoints'); - } + async getEndpoints() { + return this.request('/chat/endpoints'); + } - async createEndpoint(data) { - return this.request('/endpoints', { - method: 'POST', - body: JSON.stringify(data) - }); - } + async sendMessage(message, endpointId = null, fileIds = [], conversationHistory = []) { + return this.request('/chat/message', { + method: 'POST', + body: JSON.stringify({ + message, + endpoint_id: endpointId, + file_ids: fileIds, + conversation_history: conversationHistory, + }), + }); + } - async updateEndpoint(id, data) { - return this.request(`/endpoints/${id}`, { - method: 'PUT', - body: JSON.stringify(data) - }); - } + async getChatHistory(limit = 50) { + return this.request(`/chat/history?limit=${limit}`); + } - async deleteEndpoint(id) { - return this.request(`/endpoints/${id}`, { - method: 'DELETE' - }); - } + async uploadFile(file) { + const formData = new FormData(); + formData.append('file', file); - // Chat - async sendMessage(endpointId, message, conversationId = null) { - return this.request('/chat/message', { - method: 'POST', - body: JSON.stringify({ - endpoint_id: endpointId, - message, - conversation_id: conversationId - }) - }); - } + const headers = {}; + if (this.token) { + headers['Authorization'] = `Bearer ${this.token}`; + } - async getConversations() { - return this.request('/chat/conversations'); - } + const response = await fetch(`${API_BASE}/chat/upload`, { + method: 'POST', + headers, + body: formData, + }); - async getConversation(id) { - return this.request(`/chat/conversations/${id}`); - } + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: 'Upload failed' })); + throw new Error(error.detail || 'Upload failed'); + } - async deleteConversation(id) { - return this.request(`/chat/conversations/${id}`, { - method: 'DELETE' - }); - } + return response.json(); + } - // File upload - async uploadFile(file) { - const formData = new FormData(); - formData.append('file', file); + async getFiles() { + return this.request('/chat/files'); + } - const response = await fetch(`${this.baseUrl}/upload`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${this.getToken()}` - }, - body: formData - }); + async deleteFile(fileId) { + return this.request(`/chat/files/${fileId}`, { method: 'DELETE' }); + } - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: 'Upload failed' })); - throw new Error(error.detail || 'Upload failed'); - } + async getAdminStats() { + return this.request('/admin/stats'); + } - return response.json(); - } + async getUsers() { + return this.request('/admin/users'); + } - // Admin endpoints - async getAdminStats() { - return this.request('/admin/stats'); - } + async updateUser(userId, data) { + return this.request(`/admin/users/${userId}`, { + method: 'PUT', + body: JSON.stringify(data), + }); + } - async getUsers() { - return this.request('/admin/users'); - } + async deleteUser(userId) { + return this.request(`/admin/users/${userId}`, { method: 'DELETE' }); + } - async updateUser(id, data) { - return this.request(`/admin/users/${id}`, { - method: 'PUT', - body: JSON.stringify(data) - }); - } + async getAdminEndpoints() { + return this.request('/admin/endpoints'); + } - async deleteUser(id) { - return this.request(`/admin/users/${id}`, { - method: 'DELETE' - }); - } + async createEndpoint(data) { + return this.request('/admin/endpoints', { + method: 'POST', + body: JSON.stringify(data), + }); + } - logout() { - this.clearToken(); - } + async updateEndpoint(endpointId, data) { + return this.request(`/admin/endpoints/${endpointId}`, { + method: 'PUT', + body: JSON.stringify(data), + }); + } + + async deleteEndpoint(endpointId) { + return this.request(`/admin/endpoints/${endpointId}`, { method: 'DELETE' }); + } } export const api = new ApiClient(); diff --git a/frontend/src/lib/stores.js b/frontend/src/lib/stores.js deleted file mode 100644 index d3f01bf..0000000 --- a/frontend/src/lib/stores.js +++ /dev/null @@ -1,28 +0,0 @@ -import { writable } from 'svelte/store'; -import { api } from './api'; - -export const user = writable(null); -export const isLoading = writable(true); - -export async function initializeAuth() { - isLoading.set(true); - const token = api.getToken(); - - if (token) { - try { - const userData = await api.getMe(); - user.set(userData); - } catch (error) { - console.error('Failed to fetch user:', error); - api.clearToken(); - user.set(null); - } - } - - isLoading.set(false); -} - -export function logout() { - api.logout(); - user.set(null); -} diff --git a/frontend/src/lib/stores.svelte.js b/frontend/src/lib/stores.svelte.js new file mode 100644 index 0000000..1d04770 --- /dev/null +++ b/frontend/src/lib/stores.svelte.js @@ -0,0 +1,39 @@ +import { api } from './api.js'; + +// Svelte 5 runes-based stores +let user = $state(null); +let isLoading = $state(true); + +export function getUser() { + return user; +} + +export function getIsLoading() { + return isLoading; +} + +export async function initializeAuth() { + try { + if (api.token) { + const userData = await api.getMe(); + user = userData; + } + } catch (e) { + api.setToken(null); + user = null; + } finally { + isLoading = false; + } +} + +export function setUser(userData) { + user = userData; +} + +export function logout() { + api.setToken(null); + user = null; + if (typeof window !== 'undefined') { + window.location.href = '/login'; + } +} diff --git a/frontend/src/routes/+layout.js b/frontend/src/routes/+layout.js index a3d1578..83addb7 100644 --- a/frontend/src/routes/+layout.js +++ b/frontend/src/routes/+layout.js @@ -1 +1,2 @@ export const ssr = false; +export const prerender = false; diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 5b77da6..4181e0a 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,233 +1,251 @@ -{#if $isLoading} -
+ Moxiegen
+ Redirecting...
+Loading Moxiegen...
| ID | +Username | +Role | +Status | +Created | +Actions | +|
|---|---|---|---|---|---|---|
| {userRow.id} | +{userRow.username} | +{userRow.email} | ++ + {userRow.role} + + | ++ + {userRow.is_active ? 'Active' : 'Inactive'} + + | +{new Date(userRow.created_at).toLocaleDateString()} | +
+
+
+ {#if userRow.role !== 'admin'}
+
+ {/if}
+
+ |
+
| ID | -Username | -Admin | -Created | -Actions | -|
|---|---|---|---|---|---|
| {user.id} | -- {#if editingUser === user.id} - - {:else} - {user.username} - {/if} - | -- {#if editingUser === user.id} - - {:else} - {user.email} - {/if} - | -- {#if editingUser === user.id} - - {:else} - {#if user.is_admin} - Admin - {/if} - {/if} - | -{new Date(user.created_at).toLocaleDateString()} | -- - | -
URL: {endpoint.url}
-Model: {endpoint.model || 'Default'}
-Type: {endpoint.endpoint_type}
+Model: {endpoint.model_name}
+URL: {endpoint.base_url}
+No endpoints configured. Add one to get started.
+ {/each} +
- Sign in to your account
-
+ Sign in to your account to continue
+Demo credentials:
-demo@moxiegen.com / demo123
- Demo Admin:
+Email: admin@moxiegen.local
+Password: admin123
+
- Get started with Moxiegen
-
+ Sign up to start using Moxiegen
+