diff --git a/surfsense_web/contracts/types/auth.types.ts b/surfsense_web/contracts/types/auth.types.ts new file mode 100644 index 000000000..507ea18fc --- /dev/null +++ b/surfsense_web/contracts/types/auth.types.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; + +export const loginRequest = z.object({ + email: z.string().email(), + password: z.string().min(1), + grant_type: z.string().optional(), +}); + +export const loginResponse = z.object({ + access_token: z.string(), + token_type: z.string(), +}); + +export const registerRequest = z.object({ + email: z.string().email(), + password: z.string().min(1), + is_active: z.boolean().optional(), + is_superuser: z.boolean().optional(), + is_verified: z.boolean().optional(), +}); + +export const registerResponse = z.object({ + id: z.number(), + email: z.string().email(), + is_active: z.boolean(), + is_superuser: z.boolean(), + is_verified: z.boolean(), + pages_limit: z.number(), + pages_used: z.number(), +}); + +export type LoginRequest = z.infer; +export type LoginResponse = z.infer; +export type RegisterRequest = z.infer; +export type RegisterResponse = z.infer; diff --git a/surfsense_web/contracts/types/chat.types.ts b/surfsense_web/contracts/types/chat.types.ts new file mode 100644 index 000000000..3e7eb6911 --- /dev/null +++ b/surfsense_web/contracts/types/chat.types.ts @@ -0,0 +1,48 @@ +import { z } from "zod"; +import { type Message } from "@ai-sdk/react"; +import { paginationQueryParams } from "."; + +export const chatTypeEnum = z.enum(["QNA"]); + +export const chatSummary = z.object({ + created_at: z.string(), + id: z.number(), + type: chatTypeEnum, + title: z.string(), + search_space_id: z.number(), + state_version: z.number(), +}); + +export const chatDetails = chatSummary.extend({ + initial_connectors: z.array(z.string()), + messages: z.array(z.any()), +}); + +export const getChatDetailsRequest = chatSummary.pick({ id: true }); + +export const getChatsBySearchSpaceRequest = chatSummary.pick({ + search_space_id: true, +}).merge(paginationQueryParams); + +export const deleteChatResponse = z.object({ + message: z.literal("Chat deleted successfully"), +}); + +export const deleteChatRequest = chatSummary.pick({ id: true }); + +export const createChatRequest = chatDetails + .omit({ created_at: true, id: true, state_version: true }); + +export const updateChatRequest = chatDetails + .omit({ created_at: true, state_version: true }); + +export type ChatSummary = z.infer; +export type ChatDetails = z.infer & { messages: Message[] }; +export type GetChatDetailsRequest = z.infer; +export type GetChatsBySearchSpaceRequest = z.infer< + typeof getChatsBySearchSpaceRequest +>; +export type DeleteChatResponse = z.infer; +export type DeleteChatRequest = z.infer; +export type CreateChatRequest = z.infer; +export type UpdateChatRequest = z.infer; diff --git a/surfsense_web/contracts/types/index.ts b/surfsense_web/contracts/types/index.ts new file mode 100644 index 000000000..d6c65b5ec --- /dev/null +++ b/surfsense_web/contracts/types/index.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const paginationQueryParams = z.object({ + limit: z.number().optional(), + skip: z.number().optional(), +}); + +export type PaginationQueryParams = z.infer; diff --git a/surfsense_web/lib/apis/auth/auth.service.ts b/surfsense_web/lib/apis/auth/auth.service.ts deleted file mode 100644 index de13b670f..000000000 --- a/surfsense_web/lib/apis/auth/auth.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { LoginRequest, LoginResponse, RegisterRequest, RegisterResponse } from "./contracts"; - -export class AuthApiService { - login = async (request: LoginRequest) : Promise => { - const requestBody = new URLSearchParams(); - requestBody.append("username", request.email); - requestBody.append("password", request.password); - requestBody.append("grant_type", "password"); - - const response = await fetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/jwt/login`, - { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: requestBody.toString(), - } - ); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.detail || `HTTP ${response.status}`); - } - - return data; - }; - - register = async (request: RegisterRequest) : Promise => { - const response = await fetch( - `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/register`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(request), - } - ); - - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.detail || `HTTP ${response.status}`); - } - - return data; - }; -} diff --git a/surfsense_web/lib/apis/auth/contracts.ts b/surfsense_web/lib/apis/auth/contracts.ts deleted file mode 100644 index 5fa574cf3..000000000 --- a/surfsense_web/lib/apis/auth/contracts.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * LOGIN - */ -export type LoginRequest = { - email: string; - password: string; - grant_type?: string; -}; - -export type LoginResponse = { - access_token: string; - token_type: string; -}; - -/** - * REGISTER - */ -export type RegisterRequest = { - email: string; - password: string; - is_active: boolean; - is_superuser: boolean; - is_verified: boolean; -}; - -export type RegisterResponse = { - id: number; - email: string; - is_active: boolean; - is_superuser: boolean; - is_verified: boolean; - pages_limit: number; - pages_used: number; -}; diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts index 0b3639b4f..f94481547 100644 --- a/surfsense_web/lib/apis/base-api.service.ts +++ b/surfsense_web/lib/apis/base-api.service.ts @@ -33,7 +33,7 @@ export class BaseApiService { body?: any, responseSchema?: z.ZodSchema, options?: RequestOptions - ) { + ) : Promise { const defaultOptions: RequestOptions = { headers: { "Content-Type": "application/json", @@ -55,7 +55,10 @@ export class BaseApiService { // Serialize body if (body) { - if (mergedOptions.headers?.["Content-Type"].toLocaleLowerCase() === "application/json") { + if ( + mergedOptions.headers?.["Content-Type"].toLocaleLowerCase() === + "application/json" + ) { requestBody = JSON.stringify(body); } @@ -125,7 +128,7 @@ export class BaseApiService { async get( url: string, responseSchema?: z.ZodSchema, - options?: RequestOptions + options?: Omit ) { return this.request(url, undefined, responseSchema, { ...options, @@ -137,7 +140,7 @@ export class BaseApiService { url: string, body?: any, responseSchema?: z.ZodSchema, - options?: RequestOptions + options?: Omit ) { return this.request(url, body, responseSchema, { ...options, @@ -149,7 +152,7 @@ export class BaseApiService { url: string, body?: any, responseSchema?: z.ZodSchema, - options?: RequestOptions + options?: Omit ) { return this.request(url, body, responseSchema, { ...options, @@ -161,7 +164,7 @@ export class BaseApiService { url: string, body?: any, responseSchema?: z.ZodSchema, - options?: RequestOptions + options?: Omit ) { return this.request(url, body, responseSchema, { ...options, @@ -169,3 +172,8 @@ export class BaseApiService { }); } } + +export const baseApiService = new BaseApiService( + typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") || "" : "", + process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "" +); diff --git a/surfsense_web/lib/apis/chats/chats.api.ts b/surfsense_web/lib/apis/chats/chats.api.ts deleted file mode 100644 index a264f30af..000000000 --- a/surfsense_web/lib/apis/chats/chats.api.ts +++ /dev/null @@ -1 +0,0 @@ -// Will contain a ChatApiService class that will be used to make API calls \ No newline at end of file diff --git a/surfsense_web/lib/apis/chats/contracts.ts b/surfsense_web/lib/apis/chats/contracts.ts deleted file mode 100644 index c5ced8a4f..000000000 --- a/surfsense_web/lib/apis/chats/contracts.ts +++ /dev/null @@ -1 +0,0 @@ -// Will contains contracts for all chat related APIs diff --git a/surfsense_web/lib/error.ts b/surfsense_web/lib/error.ts new file mode 100644 index 000000000..03259c7b5 --- /dev/null +++ b/surfsense_web/lib/error.ts @@ -0,0 +1,30 @@ +export class AppError extends Error { + constructor(message: string) { + super(message); + this.name = this.constructor.name; + } +} + +export class NetworkError extends AppError { + constructor(message: string) { + super(message); + } +} + +export class ValidationError extends AppError { + constructor(message: string) { + super(message); + } +} + +export class AuthenticationError extends AppError { + constructor(message: string) { + super(message); + } +} + +export class AuthorizationError extends AppError { + constructor(message: string) { + super(message); + } +} diff --git a/surfsense_web/lib/utils.ts b/surfsense_web/lib/utils.ts index ac680b303..3b27a4c93 100644 --- a/surfsense_web/lib/utils.ts +++ b/surfsense_web/lib/utils.ts @@ -1,6 +1,14 @@ +import { Message } from "@ai-sdk/react"; import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + + +export function getChatTitleFromMessages(messages: Message[]) { + const userMessages = messages.filter((msg) => msg.role === "user"); + if (userMessages.length === 0) return "Untitled Chat"; + return userMessages[0].content; +} \ No newline at end of file