diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt index 179c1e99c..2277ed4f2 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt @@ -1,12 +1,12 @@ package com.xebia.functional.xef.server.http.routes -import com.xebia.functional.openai.generated.model.AssistantObject import com.xebia.functional.openai.generated.model.CreateAssistantRequest -import com.xebia.functional.openai.generated.model.ListAssistantsResponse +import com.xebia.functional.openai.generated.model.ModifyAssistantRequest import com.xebia.functional.xef.Config import com.xebia.functional.xef.OpenAI import com.xebia.functional.xef.llm.assistants.Assistant -import com.xebia.functional.xef.server.models.Token +import io.github.oshai.kotlinlogging.KLogger +import io.ktor.client.request.* import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* @@ -14,15 +14,16 @@ import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -fun Routing.assistantRoutes() { +fun Routing.assistantRoutes(logger: KLogger) { authenticate("auth-bearer") { post("/v1/settings/assistants") { try { val contentType = call.request.contentType() if (contentType == ContentType.Application.Json) { val request = call.receive() - val token = call.getToken() - val response = createAssistant(token, request) + val assistant = Assistant(request) + val response = assistant.get() + logger.info("Created assistant: ${response.name} with id: ${response.id}") call.respond(status = HttpStatusCode.Created, response) } else { call.respond( @@ -36,53 +37,68 @@ fun Routing.assistantRoutes() { } } - // put("/v1/settings/assistants/{id}") { - // val request = Json.decodeFromString(call.receive()) - // val token = call.getToken() - // val id = call.getId() - // val response = updateAssistant(token, request, id) - // call.respond(status = HttpStatusCode.NoContent, response) - // } - // get("/v1/settings/assistants") { - // val token = call.getToken() - // val response = ListAssistantsResponse("list", emptyList(), null, null, false) - // val assistantResponse = listAssistants(token, response) - // call.respond(assistantResponse) - // } - // delete("/v1/settings/assistants/{id}") { - // val token = call.getToken() - // val id = call.parameters["id"]?.toIntOrNull() - // if (id == null) { - // call.respond(HttpStatusCode.BadRequest, "Invalid assistant id") - // return@delete - // } - // val response = deleteAssistant(token, id) - // call.respond(status = HttpStatusCode.NoContent, response) - // } - } -} - -suspend fun createAssistant(token: Token, request: CreateAssistantRequest): AssistantObject { - val openAIConfig = Config(token = token.value) - val openAI = OpenAI(openAIConfig, logRequests = true) - val assistants = openAI.assistants - val assistant = Assistant(request) - return assistant.get() -} - -// suspend fun updateAssistant(token: String, request: AssistantRequest, id: Int): String { -// // Implement the logic for updating an assistant in OpenAI here -// } + get("/v1/settings/assistants") { + try { + val token = call.getToken() + val openAI = OpenAI(Config(token = token.value), logRequests = true) + val assistantsApi = openAI.assistants + val response = + assistantsApi.listAssistants(configure = { header("OpenAI-Beta", "assistants=v1") }) + call.respond(HttpStatusCode.OK, response) + } catch (e: Exception) { + val trace = e.stackTraceToString() + logger.error("Error creating assistant: $trace") + call.respond(HttpStatusCode.BadRequest, "Invalid request: $trace") + } + } -suspend fun listAssistants(token: Token, response: ListAssistantsResponse): ListAssistantsResponse { - val openAIConfig = Config(token = token.value) - val openAI = OpenAI(openAIConfig) - val assistants = openAI.assistants - val listAssistants = assistants.listAssistants() + put("/v1/settings/assistants/{id}") { + try { + val contentType = call.request.contentType() + if (contentType == ContentType.Application.Json) { + val request = call.receive() + val id = call.parameters["id"] + if (id == null) { + call.respond(HttpStatusCode.BadRequest, "Invalid assistant id") + return@put + } + val assistant = Assistant(id) + val response = assistant.modify(request).get() + logger.info("Modified assistant: ${response.name} with id: ${response.id}") + call.respond(HttpStatusCode.OK, response) + } else { + call.respond( + HttpStatusCode.UnsupportedMediaType, + "Unsupported content type: $contentType" + ) + } + } catch (e: Exception) { + val trace = e.stackTraceToString() + logger.error("Error modifying assistant: $trace") + call.respond(HttpStatusCode.BadRequest, "Invalid request: $trace") + } + } - return listAssistants + delete("/v1/settings/assistants/{id}") { + try { + val token = call.getToken() + val id = call.parameters["id"] + if (id == null) { + call.respond(HttpStatusCode.BadRequest, "Invalid assistant id") + return@delete + } + val openAI = OpenAI(Config(token = token.value), logRequests = true) + val assistantsApi = openAI.assistants + val assistant = assistantsApi.getAssistant(id) + val response = + assistantsApi.deleteAssistant(id, configure = { header("OpenAI-Beta", "assistants=v1") }) + logger.info("Deleted assistant: ${assistant.name} with id: ${response.id}") + call.respond(status = HttpStatusCode.NoContent, response) + } catch (e: Exception) { + val trace = e.stackTraceToString() + logger.error("Error deleting assistant: $trace") + call.respond(HttpStatusCode.BadRequest, "Invalid request: $trace") + } + } + } } - -/*suspend fun deleteAssistant(token: String, id: Int): String { - // Implement the logic for deleting an assistant in OpenAI here -}*/ diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt index a4dcbfbb6..7780c3ce9 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/XefRoutes.kt @@ -12,5 +12,5 @@ fun Routing.xefRoutes(logger: KLogger) { organizationRoutes(OrganizationRepositoryService(logger)) projectsRoutes(ProjectRepositoryService(logger)) tokensRoutes(TokenRepositoryService(logger)) - assistantRoutes() + assistantRoutes(logger) } diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml index 9a90533b5..fb058d664 100644 --- a/server/src/main/resources/logback.xml +++ b/server/src/main/resources/logback.xml @@ -12,7 +12,7 @@ - + diff --git a/server/web/src/assets/assistants-icon.svg b/server/web/src/assets/assistants-icon.svg new file mode 100644 index 000000000..fcc7189f0 --- /dev/null +++ b/server/web/src/assets/assistants-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/chat-icon.svg b/server/web/src/assets/chat-icon.svg new file mode 100644 index 000000000..bd5fca737 --- /dev/null +++ b/server/web/src/assets/chat-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/generic-question-icon.svg b/server/web/src/assets/generic-question-icon.svg new file mode 100644 index 000000000..a3de74801 --- /dev/null +++ b/server/web/src/assets/generic-question-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/home-icon.svg b/server/web/src/assets/home-icon.svg new file mode 100644 index 000000000..fbebaf2d2 --- /dev/null +++ b/server/web/src/assets/home-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/organizations-icon.svg b/server/web/src/assets/organizations-icon.svg new file mode 100644 index 000000000..7b4e35ef4 --- /dev/null +++ b/server/web/src/assets/organizations-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/projects-icon.svg b/server/web/src/assets/projects-icon.svg new file mode 100644 index 000000000..812813e2b --- /dev/null +++ b/server/web/src/assets/projects-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/assets/settings-icon.svg b/server/web/src/assets/settings-icon.svg new file mode 100644 index 000000000..8cb503db9 --- /dev/null +++ b/server/web/src/assets/settings-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/web/src/components/Pages/Assistants/Assistants.module.css b/server/web/src/components/Pages/Assistants/Assistants.module.css new file mode 100644 index 000000000..8d3575269 --- /dev/null +++ b/server/web/src/components/Pages/Assistants/Assistants.module.css @@ -0,0 +1,14 @@ +.container { + position: relative; + height: 100%; +} + +.sliders { + display: flex; + justify-content: space-between; + align-items: center; +} + +.boldText { + font-weight: bold; +} \ No newline at end of file diff --git a/server/web/src/components/Pages/Assistants/Assistants.tsx b/server/web/src/components/Pages/Assistants/Assistants.tsx index 83e2a61b9..021d4d7ca 100644 --- a/server/web/src/components/Pages/Assistants/Assistants.tsx +++ b/server/web/src/components/Pages/Assistants/Assistants.tsx @@ -1,7 +1,8 @@ import { useContext, useEffect, useState, ChangeEvent } from "react"; import { useAuth } from "@/state/Auth"; import { LoadingContext } from "@/state/Loading"; - +import styles from './Assistants.module.css'; +import { getAssistants } from '../../../utils/api/assistants'; import { Alert, Box, @@ -31,22 +32,54 @@ import { Switch } from "@mui/material"; -type Assistant = { - id: number; +type AssistantToolsCode = { + type: 'code_interpreter'; +}; + +type FunctionObject = { name: string; - createdAt: string; + description: string; + parameters: Record; +}; + +type AssistantToolsFunction = { + type: 'function'; + function: FunctionObject; +}; + +type AssistantToolsRetrieval = { + type: 'retrieval'; +}; + +type AssistantObjectToolsInner = AssistantToolsCode | AssistantToolsFunction | AssistantToolsRetrieval; + +type AssistantObject = { + id: string; + object: 'assistant'; + createdAt: number; + name?: string; + description?: string; + model: string; + instructions?: string; + tools: AssistantObjectToolsInner[]; + fileIds: string[]; + metadata: Record | null; }; -const emptyAssistant: Assistant = { - id: 0, - name: "", - createdAt: "" +const emptyAssistant: AssistantObject = { + id: "", + object: 'assistant', + createdAt: 0, + model: "", + tools: [], + fileIds: [], + metadata: null }; export function Assistants() { const auth = useAuth(); const [loading, setLoading] = useContext(LoadingContext); - const [assistants, setAssistants] = useState([]); + const [assistants, setAssistants] = useState([]); const [showAlert, setShowAlert] = useState(''); const [selectedAssistant, setSelectedAssistant] = useState(emptyAssistant); const [openEditDialog, setOpenEditDialog] = useState(false); @@ -157,25 +190,26 @@ export function Assistants() { }; const handleCreateAssistant = async () => { - try { - console.log("Creating assistant with name:", selectedAssistant.name); - await postAssistant(auth.authToken, { name: selectedAssistant.name }); - const response = await getAssistants(auth.authToken); - setAssistants(response); - setAssistantCreated(true); - } catch (error) { - console.error('Error creating assistant:', error); - setShowAlert('Failed to create assistant.'); - } + }; const models = [ + { value: 'gpt-4o-2024-05-13', label: 'gpt-4o-2024-05-13' }, + { value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' }, + { value: 'gpt-4o', label: 'gpt-4o' }, + { value: 'gpt-4-vision-preview', label: 'gpt-4-vision-preview' }, + { value: 'gpt-4-turbo-preview', label: 'gpt-4-turbo-preview' }, + { value: 'gpt-4-2024-04-09', label: 'gpt-4-2024-04-09' }, { value: 'gpt-4-turbo', label: 'gpt-4-turbo' }, + { value: 'gpt-4-1106-preview', label: 'gpt-4-1106-preview' }, + { value: 'gpt-4-0613', label: 'gpt-4-0613' }, + { value: 'gpt-4-0125-preview', label: 'gpt-4-0125-preview' }, { value: 'gpt-4', label: 'gpt-4' }, + { value: 'gpt-3.5-turbo-16k-0613', label: 'gpt-3.5-turbo-16k-0613' }, { value: 'gpt-3.5-turbo-16k', label: 'gpt-3.5-turbo-16k' }, + { value: 'gpt-3.5-turbo-1106', label: 'gpt-3.5-turbo-1106' }, + { value: 'gpt-3.5-turbo-0613', label: 'gpt-3.5-turbo-0613' }, { value: 'gpt-3.5-turbo-0125', label: 'gpt-3.5-turbo-0125' }, - { value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' }, - { value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' }, ]; const largeDialogStyles = { @@ -183,8 +217,29 @@ export function Assistants() { textAlign: 'left', }; + useEffect(() => { + const fetchAssistants = async () => { + setLoading(true); + try { + console.log('auth.token:', auth.token); // Log auth.token + const response = await getAssistants(auth.token); + console.log('response:', response); // Log response + setAssistants(response.data); + console.log('assistants:', assistants); // Log assistants + } catch (error) { + console.error(error); + // handle error + } finally { + setLoading(false); + } + }; + + fetchAssistants(); + }, [auth.token]); + + return ( - + @@ -210,7 +265,7 @@ export function Assistants() { {assistants.map((assistant) => ( - {assistant.createdAt} + {assistant.id} {assistant.name} + {fileSearchDialogOpen && ( - + {fileSearchSelectedFile.length > 0 && ( @@ -512,9 +567,9 @@ export function Assistants() {
- MODEL CONFIGURATION + MODEL CONFIGURATION - Response format + Response format } @@ -524,7 +579,7 @@ export function Assistants() {
-
+
Temperature -
+
Top P - + Home + + Organizations - {/* Puedes agregar un ícono si es necesario, por ejemplo */} + Assistants + Projects + Chat + Generic question + Settings diff --git a/server/web/src/utils/api/assistants.ts b/server/web/src/utils/api/assistants.ts index f7f5556fd..15de45678 100644 --- a/server/web/src/utils/api/assistants.ts +++ b/server/web/src/utils/api/assistants.ts @@ -1,110 +1,93 @@ -// Este código asume que tienes un enum similar para los endpoints de la API de asistentes import { - ApiOptions, - EndpointsEnum, - apiConfigConstructor, - apiFetch, - baseHeaders, - defaultApiServer, -} from '@/utils/api'; + ApiOptions, + EndpointsEnum, + apiConfigConstructor, + apiFetch, + baseHeaders, + defaultApiServer, + } from '@/utils/api'; -// Definiciones de tipos para las respuestas y solicitudes de asistentes (ajústalas a tu API) -export type AssistantRequest = { - name: string; - // Otros campos relevantes para la creación o actualización de un asistente -}; + export type CreateAssistantRequest = { + model: string; + name?: string; + description?: string; + instructions?: string; + tools?: AssistantTool[]; + fileIds?: string[]; + metadata?: Record; + }; -export type AssistantResponse = { - id: number; - name: string; - createdAt: string; - // Otros campos que tu API devuelve -}; + export type ModifyAssistantRequest = { + model?: string; + name?: string; + description?: string; + instructions?: string; + tools?: AssistantTool[]; + fileIds?: string[]; + metadata?: Record; + }; -const assistantApiBaseOptions: ApiOptions = { - endpointServer: defaultApiServer, - endpointPath: EndpointsEnum.assistant, // Asegúrate de que este enum existe y es correcto - endpointValue: '', - requestOptions: { - headers: baseHeaders, - }, -}; + type CodeInterpreter = { + type: 'code_interpreter'; + }; -// POST: Crear un nuevo asistente -export async function postAssistant(authToken: string, data: AssistantRequest): Promise { - const apiOptions: ApiOptions = { - ...assistantApiBaseOptions, - body: JSON.stringify(data), - requestOptions: { - method: 'POST', - ...assistantApiBaseOptions.requestOptions, - headers: { - ...assistantApiBaseOptions.requestOptions.headers, - Authorization: `Bearer ${authToken}`, - }, - }, + type Retrieval = { + type: 'retrieval'; }; - const apiConfig = apiConfigConstructor(apiOptions); - const response = await apiFetch(apiConfig); - return response.status; // Asumiendo que la API devuelve un código de estado para representar el resultado -} -// GET: Obtener todos los asistentes -export async function getAssistants(authToken: string): Promise { - const apiOptions: ApiOptions = { - ...assistantApiBaseOptions, - requestOptions: { - method: 'GET', - ...assistantApiBaseOptions.requestOptions, - headers: { - ...assistantApiBaseOptions.requestOptions.headers, - Authorization: `Bearer ${authToken}`, - }, - }, + type Function = { + type: 'function'; + name: string; + description: string; + parameters: string; }; - const apiConfig = apiConfigConstructor(apiOptions); - const response = await apiFetch(apiConfig); - if (!response.data) { - throw new Error('No assistants data returned from the API'); - } - return response.data; -} -// PUT: Actualizar un asistente existente -export async function putAssistant(authToken: string, id: number, data: AssistantRequest): Promise { - const apiOptions: ApiOptions = { - ...assistantApiBaseOptions, - endpointValue: `/${id}`, - body: JSON.stringify(data), + type AssistantTool = CodeInterpreter | Retrieval | Function; + + const assistantApiBaseOptions: ApiOptions = { + endpointServer: defaultApiServer, + endpointPath: EndpointsEnum.assistant, + endpointValue: '', requestOptions: { - method: 'PUT', - ...assistantApiBaseOptions.requestOptions, - headers: { - ...assistantApiBaseOptions.requestOptions.headers, - Authorization: `Bearer ${authToken}`, - }, + headers: baseHeaders, }, }; - const apiConfig = apiConfigConstructor(apiOptions); - const response = await apiFetch(apiConfig); - return response.status; -} -// DELETE: Eliminar un asistente -export async function deleteAssistant(authToken: string, id: number): Promise { - const apiOptions: ApiOptions = { - ...assistantApiBaseOptions, - endpointValue: `/${id}`, - requestOptions: { - method: 'DELETE', - ...assistantApiBaseOptions.requestOptions, - headers: { - ...assistantApiBaseOptions.requestOptions.headers, - Authorization: `Bearer ${authToken}`, + async function executeRequest(authToken: string, method: string, endpointValue: string = '', body?: any): Promise { + const apiOptions: ApiOptions = { + ...assistantApiBaseOptions, + endpointValue: endpointValue, + body: body ? JSON.stringify(body) : undefined, + requestOptions: { + method: method, + ...assistantApiBaseOptions.requestOptions, + headers: { + ...assistantApiBaseOptions.requestOptions.headers, + Authorization: `Bearer ${authToken}`, + "OpenAI-Beta": "assistants=v1" + }, }, - }, - }; - const apiConfig = apiConfigConstructor(apiOptions); - const response = await apiFetch(apiConfig); - return response.status; -} + }; + const apiConfig = apiConfigConstructor(apiOptions); + const response = await apiFetch(apiConfig); + if (!response.data) { + throw new Error('No data returned from the API'); + } + return response.data; + } + + export async function postAssistant(authToken: string, data: CreateAssistantRequest): Promise { + return executeRequest(authToken, 'POST', '', data); + } + + export async function getAssistants(authToken: string): Promise { + return executeRequest(authToken, 'GET'); + } + + export async function putAssistant(authToken: string, id: string, data: ModifyAssistantRequest): Promise { + return executeRequest(authToken, 'PUT', `/${id}`, data); + } + + export async function deleteAssistant(authToken: string, id: string): Promise { + await executeRequest(authToken, 'DELETE', `/${id}`); + } diff --git a/server/web/src/utils/api/chatCompletions.ts b/server/web/src/utils/api/chatCompletions.ts index 52e06a33b..56cdba055 100644 --- a/server/web/src/utils/api/chatCompletions.ts +++ b/server/web/src/utils/api/chatCompletions.ts @@ -1,16 +1,11 @@ -/*import { - defaultApiServer, -} from '@/utils/api';*/ - import {OpenAI} from "openai/index"; import {Settings} from "@/state/Settings"; export function openai (settings: Settings): OpenAI { if (!settings.apiKey) throw 'API key not set'; return new OpenAI({ - //baseURL: defaultApiServer, // TODO: remove this when the key is the user token used for client auth dangerouslyAllowBrowser: true, apiKey: settings.apiKey, // defaults to process.env["OPENAI_API_KEY"] }); -} +} \ No newline at end of file