From ab0344bc42ece7521b8a31427ed1a777459384cf Mon Sep 17 00:00:00 2001 From: JoseP3r32 <146430742+JoseP3r32@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:33:20 +0200 Subject: [PATCH 1/5] chatCompletions comment line --- server/web/src/utils/api/chatCompletions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/web/src/utils/api/chatCompletions.ts b/server/web/src/utils/api/chatCompletions.ts index 1b5160660..52e06a33b 100644 --- a/server/web/src/utils/api/chatCompletions.ts +++ b/server/web/src/utils/api/chatCompletions.ts @@ -1,6 +1,6 @@ -import { +/*import { defaultApiServer, -} from '@/utils/api'; +} from '@/utils/api';*/ import {OpenAI} from "openai/index"; import {Settings} from "@/state/Settings"; @@ -8,7 +8,7 @@ import {Settings} from "@/state/Settings"; export function openai (settings: Settings): OpenAI { if (!settings.apiKey) throw 'API key not set'; return new OpenAI({ - baseURL: defaultApiServer, + //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"] From 7ef23cfe8f988ada70affba6169ac777d9b6f32d Mon Sep 17 00:00:00 2001 From: JoseP3r32 <146430742+JoseP3r32@users.noreply.github.com> Date: Wed, 24 Apr 2024 11:37:31 +0200 Subject: [PATCH 2/5] Assistants-page-view --- .../Pages/Assistants/Assistants.tsx | 122 ++++++++++++++++++ .../src/components/Pages/Assistants/index.ts | 1 + server/web/src/components/Sidebar/Sidebar.tsx | 6 + server/web/src/main.tsx | 9 ++ server/web/src/utils/api/assistants.ts | 110 ++++++++++++++++ 5 files changed, 248 insertions(+) create mode 100644 server/web/src/components/Pages/Assistants/Assistants.tsx create mode 100644 server/web/src/components/Pages/Assistants/index.ts create mode 100644 server/web/src/utils/api/assistants.ts diff --git a/server/web/src/components/Pages/Assistants/Assistants.tsx b/server/web/src/components/Pages/Assistants/Assistants.tsx new file mode 100644 index 000000000..a2c8f897f --- /dev/null +++ b/server/web/src/components/Pages/Assistants/Assistants.tsx @@ -0,0 +1,122 @@ +import { useContext, useEffect, useState, ChangeEvent } from "react"; +import { useAuth } from "@/state/Auth"; +import { LoadingContext } from "@/state/Loading"; +import { + getAssistants, + postAssistant, + putAssistant, + deleteAssistant, +} from "@/utils/api/assistants"; // Asegúrate de que tienes estas funciones en tus utils de API +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Grid, + Paper, + Snackbar, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography +} from "@mui/material"; + +// Asumiendo que tienes un tipo definido para Assistant similar a tu tipo OrganizationResponse +type Assistant = { + id: number; + name: string; + createdAt: string; +}; + +const emptyAssistant: Assistant = { + id: 0, + name: "", + createdAt: "" +}; + +export function Assistants() { + const auth = useAuth(); + const [loading, setLoading] = useContext(LoadingContext); + const [assistants, setAssistants] = useState([]); + const [showAlert, setShowAlert] = useState(''); + const [selectedAssistant, setSelectedAssistant] = useState(emptyAssistant); + const [openEditDialog, setOpenEditDialog] = useState(false); + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + + async function loadAssistants() { + setLoading(true); + try { + const response = await getAssistants(auth.authToken); + setAssistants(response); + } catch (error) { + console.error('Error fetching assistants:', error); + setShowAlert('Failed to load assistants.'); + } + setLoading(false); + } + + useEffect(() => { + loadAssistants(); + }, []); + + // Agrega aquí las funciones para manejar la creación, edición y eliminación, siguiendo el ejemplo de tu componente de Organizations + + return ( + + + Assistants + + + {loading ? ( + Loading... + ) : ( + assistants.length === 0 ? ( + No assistants available. + ) : ( + + + + + Date + Name + Actions + + + + {assistants.map((assistant) => ( + + {assistant.createdAt} + {assistant.name} + + + + + + ))} + +
+
+ ) + )} + {/* Add/Edit Assistant Dialog */} + {/* Delete Assistant Dialog */} + {/* Alert Snackbar */} +
+ ); +} diff --git a/server/web/src/components/Pages/Assistants/index.ts b/server/web/src/components/Pages/Assistants/index.ts new file mode 100644 index 000000000..02d1533d3 --- /dev/null +++ b/server/web/src/components/Pages/Assistants/index.ts @@ -0,0 +1 @@ +export * from './Assistants'; diff --git a/server/web/src/components/Sidebar/Sidebar.tsx b/server/web/src/components/Sidebar/Sidebar.tsx index 0d8e15b51..74bc9f268 100644 --- a/server/web/src/components/Sidebar/Sidebar.tsx +++ b/server/web/src/components/Sidebar/Sidebar.tsx @@ -43,6 +43,12 @@ export function Sidebar({ drawerWidth, open }: SidebarProps) { + + + {/* Puedes agregar un ícono si es necesario, por ejemplo */} + + + diff --git a/server/web/src/main.tsx b/server/web/src/main.tsx index 3427b940e..a2c36d3bc 100644 --- a/server/web/src/main.tsx +++ b/server/web/src/main.tsx @@ -8,6 +8,7 @@ import { App } from '@/components/App'; import { Root } from '@/components/Pages/Root'; import { ErrorPage } from '@/components/Pages/ErrorPage'; import { Organizations } from '@/components/Pages/Organizations'; +import { Assistants } from '@/components/Pages/Assistants'; import { Chat } from '@/components/Pages/Chat'; import { GenericQuestion } from '@/components/Pages/GenericQuestion'; import { SettingsPage } from '@/components/Pages/SettingsPage'; @@ -54,6 +55,14 @@ const router = createBrowserRouter([ ), }, + { + path: 'assistants', + element: ( + + + + ), + }, { path: 'projects', element: ( diff --git a/server/web/src/utils/api/assistants.ts b/server/web/src/utils/api/assistants.ts new file mode 100644 index 000000000..f7f5556fd --- /dev/null +++ b/server/web/src/utils/api/assistants.ts @@ -0,0 +1,110 @@ +// 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'; + +// 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 AssistantResponse = { + id: number; + name: string; + createdAt: string; + // Otros campos que tu API devuelve +}; + +const assistantApiBaseOptions: ApiOptions = { + endpointServer: defaultApiServer, + endpointPath: EndpointsEnum.assistant, // Asegúrate de que este enum existe y es correcto + endpointValue: '', + requestOptions: { + headers: baseHeaders, + }, +}; + +// 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}`, + }, + }, + }; + 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}`, + }, + }, + }; + 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), + requestOptions: { + method: 'PUT', + ...assistantApiBaseOptions.requestOptions, + headers: { + ...assistantApiBaseOptions.requestOptions.headers, + Authorization: `Bearer ${authToken}`, + }, + }, + }; + 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}`, + }, + }, + }; + const apiConfig = apiConfigConstructor(apiOptions); + const response = await apiFetch(apiConfig); + return response.status; +} From b9fe17cd881d4399cdbea16ed0e25f1ac1a1e7e3 Mon Sep 17 00:00:00 2001 From: JoseP3r32 <146430742+JoseP3r32@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:29:31 +0200 Subject: [PATCH 3/5] assistant create form update --- .../Pages/Assistants/Assistants.tsx | 305 ++++++++++++++---- server/web/src/xef Dashboard.html | 20 ++ 2 files changed, 254 insertions(+), 71 deletions(-) create mode 100644 server/web/src/xef Dashboard.html diff --git a/server/web/src/components/Pages/Assistants/Assistants.tsx b/server/web/src/components/Pages/Assistants/Assistants.tsx index a2c8f897f..d6e58d6da 100644 --- a/server/web/src/components/Pages/Assistants/Assistants.tsx +++ b/server/web/src/components/Pages/Assistants/Assistants.tsx @@ -1,35 +1,42 @@ import { useContext, useEffect, useState, ChangeEvent } from "react"; import { useAuth } from "@/state/Auth"; import { LoadingContext } from "@/state/Loading"; + import { getAssistants, postAssistant, putAssistant, deleteAssistant, -} from "@/utils/api/assistants"; // Asegúrate de que tienes estas funciones en tus utils de API +} from "@/utils/api/assistants"; import { Alert, - Box, - Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - Grid, - Paper, - Snackbar, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - TextField, - Typography + Box, + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + FormControlLabel, + FormGroup, + MenuItem, + Grid, + Divider, + Paper, + Switch, + Snackbar, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Slider } from "@mui/material"; -// Asumiendo que tienes un tipo definido para Assistant similar a tu tipo OrganizationResponse type Assistant = { id: number; name: string; @@ -50,6 +57,12 @@ export function Assistants() { const [selectedAssistant, setSelectedAssistant] = useState(emptyAssistant); const [openEditDialog, setOpenEditDialog] = useState(false); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const [showCreatePanel, setShowCreatePanel] = useState(false); + const [fileSearchEnabled, setFileSearchEnabled] = useState(false); + const [codeInterpreterEnabled, setCodeInterpreterEnabled] = useState(false); + const [JsonObjectEnabled, setJsonObjectEnabled] = useState(false); + const [temperature, setTemperature] = useState(1); + const [topP, setTopP] = useState(1); async function loadAssistants() { setLoading(true); @@ -67,56 +80,206 @@ export function Assistants() { loadAssistants(); }, []); - // Agrega aquí las funciones para manejar la creación, edición y eliminación, siguiendo el ejemplo de tu componente de Organizations + const models = [ + { + value: 'gpt-4-turbo', + label: 'gpt-4-turbo', + }, + { + value: 'gpt-4', + label: 'gpt-4', + }, + { + value: 'gpt-3.5-turbo-16k', + label: 'gpt-3.5-turbo-16k', + }, + { + 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 handleCreateAssistant = async () => { + // Aquí se incluirá la lógica para crear un nuevo asistente + // Por ejemplo: await postAssistant(authToken, { name: selectedAssistant.name }); + }; + + const handleFileSearchChange = (event: ChangeEvent) => { + setFileSearchEnabled(event.target.checked); + }; + + const handleCodeInterpreterChange = (event: ChangeEvent) => { + setCodeInterpreterEnabled(event.target.checked); + }; + + const handleJsonObjectChange = (event: ChangeEvent) => { + setJsonObjectEnabled(event.target.checked); + }; + + const handleTemperatureChange = (event: Event, newValue: number | number[]) => { + setTemperature(newValue as number); + }; + const handleTopPChange = (event: Event, newValue: number | number[]) => { + setTopP(newValue as number); + }; + const handleFilesButtonClick = (toolName: String)=> { + console.log(`Clicked on ${toolName} button`); + }; return ( - - - Assistants - - - {loading ? ( - Loading... - ) : ( - assistants.length === 0 ? ( - No assistants available. - ) : ( - - - - - Date - Name - Actions - - - - {assistants.map((assistant) => ( - - {assistant.createdAt} - {assistant.name} - - - - - - ))} - -
-
- ) - )} - {/* Add/Edit Assistant Dialog */} - {/* Delete Assistant Dialog */} - {/* Alert Snackbar */} -
- ); -} + + + {showCreatePanel && ( + + + + {selectedAssistant.id ? 'Edit Assistant' : 'Create Assistant'} + + + setSelectedAssistant({ ...selectedAssistant, name: e.target.value })} + margin="normal" + /> + + setSelectedAssistant({ ...selectedAssistant, model: e.target.value })} + margin="normal" + sx={{ display: 'block', mt: 3, mb: 3 }} + SelectProps={{ + native: true, + }} + helperText="Please select your model" + > + {models.map((option) => ( + + ))} + + + Tools + + } + label="File search" + sx={{ display: 'block', mt: 2, mb: 2 }} + /> + + } + label="Code interpreter" + sx={{ display: 'block', mt: 2, mb: 2 }} + /> + + Functions + + MODEL CONFIGURATION + + Response format + + } + label="JSON object" + sx={{ display: 'block', mt: 1, mb: 1 }} + /> + Temperature + + Top P + + + + + )} + {/* Vista principal */} + + + Assistants + + + {loading ? ( + Loading... + ) : ( + assistants.length === 0 ? ( + No assistants available. + ) : ( + + + + + Date + Name + Actions + + + + {assistants.map((assistant) => ( + + {assistant.createdAt} + {assistant.name} + + + + + + ))} + +
+
+ ) + )} + {/* Diálogos de edición y eliminación */} + {/* Snackbar de alerta */} +
+
+ ); + } diff --git a/server/web/src/xef Dashboard.html b/server/web/src/xef Dashboard.html new file mode 100644 index 000000000..e3f499e32 --- /dev/null +++ b/server/web/src/xef Dashboard.html @@ -0,0 +1,20 @@ + + + + + + + + + + + xef Dashboard + + +
+ + + From dd7aa9b688273b2c9f6413999779e32f3099eaac Mon Sep 17 00:00:00 2001 From: mccarrascog Date: Mon, 13 May 2024 13:36:18 +0200 Subject: [PATCH 4/5] wip-assistant-form --- .../src/main/resources/documents/ghibli.json | 279 +++++++ .../xef/server/http/routes/AssistantRoutes.kt | 87 ++ .../xef/server/http/routes/XefRoutes.kt | 1 + .../Pages/Assistants/Assistants.tsx | 777 ++++++++++++------ 4 files changed, 915 insertions(+), 229 deletions(-) create mode 100644 examples/src/main/resources/documents/ghibli.json create mode 100644 server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt diff --git a/examples/src/main/resources/documents/ghibli.json b/examples/src/main/resources/documents/ghibli.json new file mode 100644 index 000000000..6a9b0353c --- /dev/null +++ b/examples/src/main/resources/documents/ghibli.json @@ -0,0 +1,279 @@ +[ { +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/npOnzAbLh6VOIu3naU5QaEcTepo.jpg" , +"movie_banner": "https://image.tmdb.org/t/p/w533_and_h300_bestv2/3cyjYtLWCBE1uvWINHFsFnE8LUK.jpg" , +"description": "The orphan Sheeta inherited a mysterious crystal that links her to the mythical sky-kingdom of Laputa. With the help of resourceful Pazu and a rollicking band of sky pirates, she makes her way to the ruins of the once-great civilization. Sheeta and Pazu must outwit the evil Muska, who plans to use Laputa's science to make himself ruler of the world.", +"director": "Hayao Miyazaki", "producer": "Isao Takahata", "release_date": "1986", "running_time": "124", "rt_score": "95", +"people": [ "https://ghibliapi.vercel.app/people/598f7048-74ff-41e0-92ef-87dc1ad980a9", "https://ghibliapi.vercel.app/people/fe93adf2-2f3a-4ec4-9f68-5422f1b87c01", "https://ghibliapi.vercel.app/people/3bc0b41e-3569-4d20-ae73-2da329bf0786", "https://ghibliapi.vercel.app/people/40c005ce-3725-4f15-8409-3e1b1b14b583", "https://ghibliapi.vercel.app/people/5c83c12a-62d5-4e92-8672-33ac76ae1fa0", "https://ghibliapi.vercel.app/people/e08880d0-6938-44f3-b179-81947e7873fc", "https://ghibliapi.vercel.app/people/2a1dad70-802a-459d-8cc2-4ebd8821248b" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/4e09b023-f650-4747-9ab9-eacf14540cfb" ], +"url": "https://ghibliapi.vercel.app/films/2baf70d1-42bb-4437-b551-e5fed5a87abe" }, +{
"id": "12cfb892-aac0-4c5b-94af-521852e46d6a", "title": "Grave of the Fireflies",
"original_title": "火垂るの墓", "original_title_romanised": "Hotaru no haka", +"id": "2baf70d1-42bb-4437-b551-e5fed5a87abe", "title": "Castle in the Sky",
"original_title": "天空の城ラピュタ", "original_title_romanised": "Tenkū no shiro Rapyuta", "image": +"image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/qG3RYlIVpTYclR9TYIsy8p7m7AT.jpg", +"movie_banner": "https://image.tmdb.org/t/p/original/vkZSd0Lp8iCVBGpFH9L7LzLusjS.jpg", +"description": "In the latter part of World War II, a boy and his sister, orphaned when their mother is killed in the firebombing of Tokyo, are left to survive on their own in what remains of civilian life in Japan. The plot follows this boy and his sister as they do their best to survive in the Japanese countryside, battling hunger, prejudice, and pride in their own quiet, personal battle.", +"director": "Isao Takahata", "producer": "Toru Hara", "release_date": "1988", "running_time": "89", "rt_score": "97", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/12cfb892-aac0-4c5b-94af-521852e46d6a" }, +{
"id": "58611129-2dbc-4a81-a72f-77ddfc1b1b49", "title": "My Neighbor Totoro",
"original_title": "となりのトトロ", "original_title_romanised": "Tonari no Totoro", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/rtGDOeG9LzoerkDGZF9dnVeLppL.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/etqr6fOOCXQOgwrQXaKwenTSuzx.jpg", "description": "Two sisters move to the country with their father in order to be closer to +their hospitalized mother, and discover the surrounding trees are inhabited by Totoros, magical spirits of the forest. When the youngest runs away from home, the older sister seeks help from the spirits to find her.", +"director": "Hayao Miyazaki", "producer": "Hayao Miyazaki", "release_date": "1988", "running_time": "86", "rt_score": "93", +"people": [ "https://ghibliapi.vercel.app/people/986faac6-67e3-4fb8-a9ee-bad077c2e7fe", +"https://ghibliapi.vercel.app/people/d5df3c04-f355-4038-833c-83bd3502b6b9", "https://ghibliapi.vercel.app/people/3031caa8-eb1a-41c6-ab93-dd091b541e11", "https://ghibliapi.vercel.app/people/87b68b97-3774-495b-bf80-495a5f3e672d", "https://ghibliapi.vercel.app/people/d39deecb-2bd0-4770-8b45-485f26e1381f", "https://ghibliapi.vercel.app/people/591524bc-04fe-4e60-8d61-2425e42ffb2a", "https://ghibliapi.vercel.app/people/c491755a-407d-4d6e-b58a-240ec78b5061", "https://ghibliapi.vercel.app/people/f467e18e-3694-409f-bdb3-be891ade1106", "https://ghibliapi.vercel.app/people/08ffbce4-7f94-476a-95bc-76d3c3969c19", "https://ghibliapi.vercel.app/people/0f8ef701-b4c7-4f15-bd15-368c7fe38d0a" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2", "https://ghibliapi.vercel.app/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3", "https://ghibliapi.vercel.app/species/74b7f547-1577-4430-806c-c358c8b6bcf5" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49" }, +{
"id": "ea660b10-85c4-4ae3-8a5f-41cea3648e3e", "title": "Kiki's Delivery Service",
"original_title": "魔女の宅急便", "original_title_romanised": "Majo no takkyūbin", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/7nO5DUMnGUuXrA4r2h6ESOKQRrx.jp g", +"movie_banner": "https://image.tmdb.org/t/p/original/h5pAEVma835u8xoE60kmLVopLct.jpg", +"description": "A young witch, on her mandatory year of independent life, finds fitting into a new community difficult while she supports herself by running an air courier service.", +"director": "Hayao Miyazaki", "producer": "Hayao Miyazaki", "release_date": "1989", "running_time": "102", "rt_score": "96", +"people": [ "https://ghibliapi.vercel.app/people/2409052a-9029-4e8d-bfaf-70fd82c8e48d", "https://ghibliapi.vercel.app/people/7151abc6-1a9e-4e6a-9711-ddb50ea572ec", "https://ghibliapi.vercel.app/people/1c1a8054-3a34-4185-bfcf-e8011506f09a", "https://ghibliapi.vercel.app/people/bc838920-7849-43ea-bfb8-7d5e98dc20b6", "https://ghibliapi.vercel.app/people/33f5fea9-c21b-490b-90e0-c4051c372826", "https://ghibliapi.vercel.app/people/d1de1c0e-3fcd-4cef-94eb-bb95cc2314aa" +], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2", "https://ghibliapi.vercel.app/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e" }, +{
"id": "4e236f34-b981-41c3-8c65-f8c9000b94e7", "title": "Only Yesterday",
"original_title": "おもひでぽろぽろ", "original_title_romanised": "Omoide poro poro", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/xjJU6rwzLX7Jk8HFQfVW6H5guMC.jpg" , +"movie_banner": "https://image.tmdb.org/t/p/w533_and_h300_bestv2/isCrlWWI4JrdLKAUAwFb5cjAsH4.jpg", +"description": "It’s 1982, and Taeko is 27 years old, unmarried, and has lived her whole life in Tokyo. She decides to visit her family in the countryside, and as the train travels through the night, memories flood back of her younger years: the first immature stirrings of romance, the onset of puberty, and the frustrations of math and boys. At the station she is met by young farmer Toshio, and the encounters with him begin to reconnect her to forgotten longings. In lyrical switches between the present and the past, Taeko contemplates the arc of her life, and wonders if she has been true to the dreams of her childhood self.", +"director": "Isao Takahata", "producer": "Toshio Suzuki", "release_date": "1991", "running_time": "118", "rt_score": "100", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/4e236f34-b981-41c3-8c65-f8c9000b94e7" }, +{
"id": "ebbb6b7c-945c-41ee-a792-de0e43191bd8", "title": "Porco Rosso",
"original_title": "紅の豚", "original_title_romanised": "Kurenai no buta", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/byKAndF6KQSDpGxp1mTr23jPbYp.jpg" , +"movie_banner": "https://image.tmdb.org/t/p/original/nAeCzilMRXvGaxiCpv63ZRVRVgh.jpg", +"description": "Porco Rosso, known in Japan as Crimson Pig (Kurenai no Buta) is the sixth animated film by Hayao Miyazaki and released in 1992. You're introduced to an Italian World War I fighter ace, now living as a freelance bounty hunter chasing 'air pirates' in the Adriatic Sea. He has been given a curse that changed his head to that of a pig. Once called Marco Pagot, he is now known to the world as 'Porco Rosso', Italian for 'Red Pig.'", +"director": "Hayao Miyazaki", "producer": "Toshio Suzuki", "release_date": "1992", "running_time": "93", "rt_score": "94", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/ebbb6b7c-945c-41ee-a792-de0e43191bd8" }, +{
"id": "1b67aa9a-2e4a-45af-ac98-64d6ad15b16c",
"title": "Pom Poko",
"original_title": "平成狸合戦ぽんぽこ", "original_title_romanised": "Heisei tanuki gassen Ponpoko", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/kowo9E1e1JcWLXj9cCvAOFZcy5n.jpg" , +"movie_banner": "https://image.tmdb.org/t/p/original/jScPd0u0jeo66l8gwDl7W9hDUnM.jpg", +"description": "As the human city development encroaches on the raccoon population's forest and meadow habitat, the raccoons find themselves faced with the very real possibility of extinction. In response, the raccoons engage in a desperate struggle to stop the construction and preserve their home.", +"director": "Isao Takahata", "producer": "Toshio Suzuki", "release_date": "1994", "running_time": "119", "rt_score": "78", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/1b67aa9a-2e4a-45af-ac98-64d6ad15b16c" }, +{
"id": "ff24da26-a969-4f0e-ba1e-a122ead6c6e3", "title": "Whisper of the Heart",
"original_title": "耳をすませば", "original_title_romanised": "Mimi wo sumaseba", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/5E3Hvbu0bg38ouYf6chGftVGqZ7.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/fRtaDgmj0CirvqFUG1XN48BDY1l.jpg",
"description": "Shizuku lives a simple life, dominated by her love for stories and writing. +One day she notices that all the library books she has have been previously checked out by the same person: 'Seiji Amasawa'. Curious as to who he is, Shizuku meets a boy her age whom she finds infuriating, but discovers to her shock that he is her 'Prince of Books'. As she grows closer to him, she realises that he merely read all those books to bring himself closer to her. The boy Seiji aspires to be a violin maker in Italy, and it is his dreams that make Shizuku realise that she has no clear path for her life. Knowing that her strength lies in writing, she tests her talents by writing a story about Baron, a cat statuette belonging to Seiji's grandfather.", +"director": "Yoshifumi Kondō", "producer": "Toshio Suzuki", "release_date": "1995", "running_time": "111", "rt_score": "91", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3" }, +{
"id": "0440483e-ca0e-4120-8c50-4c8cd9b965d6", "title": "Princess Mononoke",
"original_title": "もののけ姫", "original_title_romanised": "Mononoke hime", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/jHWmNr7m544fJ8eItsfNk8fs2Ed.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/6pTqSq0zYIWCsucJys8q5L92kUY.jpg",
"description": "Ashitaka, a prince of the disappearing Ainu tribe, is cursed by a demonized +boar god and must journey to the west to find a cure. Along the way, he encounters San, a young human woman fighting to protect the forest, and Lady Eboshi, who is trying to destroy it. Ashitaka must find a way to bring balance to this conflict.", +"director": "Hayao Miyazaki", "producer": "Toshio Suzuki", "release_date": "1997", "running_time": "134", "rt_score": "92", +"people": [ "https://ghibliapi.vercel.app/people/ba924631-068e-4436-b6de-f3283fa848f0", "https://ghibliapi.vercel.app/people/ebe40383-aad2-4208-90ab-698f00c581ab", "https://ghibliapi.vercel.app/people/030555b3-4c92-4fce-93fb-e70c3ae3df8b", "https://ghibliapi.vercel.app/people/ca568e87-4ce2-4afa-a6c5-51f4ae80a60b", "https://ghibliapi.vercel.app/people/e9356bb5-4d4a-4c93-aadc-c83e514bffe3", "https://ghibliapi.vercel.app/people/34277bec-7401-43fa-a00a-5aee64b45b08", "https://ghibliapi.vercel.app/people/91939012-90b9-46e5-a649-96b898073c82", "https://ghibliapi.vercel.app/people/20e3bd33-b35d-41e6-83a4-57ca7f028d38", "https://ghibliapi.vercel.app/people/8bccdc78-545b-49f4-a4c8-756163a38c91", "https://ghibliapi.vercel.app/people/116bfe1b-3ba8-4fa0-8f72-88537a493cb9" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2", "https://ghibliapi.vercel.app/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e", "https://ghibliapi.vercel.app/species/f25fa661-3073-414d-968a-ab062e3065f7" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6" }, +{
"id": "45204234-adfd-45cb-a505-a8e7a676b114",
"title": "My Neighbors the Yamadas",
"original_title": "ホーホケキョ となりの山田くん", "original_title_romanised": "Hōhokekyo tonari no Yamada-kun", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/wTGuHmMIBBgKakY80J1D52VvQKI.jp g", +"movie_banner": "https://image.tmdb.org/t/p/original/nDOsicEg4RHDq0t23JKGSb58z6u.jpg", +"description": "The Yamadas are a typical middle class Japanese family in urban Tokyo and this film shows us a variety of episodes of their lives. With tales that range from the humourous to the heartbreaking, we see this family cope with life's little conflicts, problems and joys in their own way.", +"director": "Isao Takahata", "producer": "Toshio Suzuki", "release_date": "1999", "running_time": "104", "rt_score": "75", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/45204234-adfd-45cb-a505-a8e7a676b114" }, +{
"id": "dc2e6bd1-8156-4886-adff-b39e6043af0c",
"title": "Spirited Away",
"original_title": "千と千尋の神隠し", "original_title_romanised": "Sen to Chihiro no kamikakushi", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/39wmItIWsg5sZMyRUHLkWBcuVCM.jp g", +"movie_banner": "https://image.tmdb.org/t/p/original/bSXfU4dwZyBA1vMmXvejdRXBvuF.jpg", +"description": "Spirited Away is an Oscar winning Japanese animated film about a ten year old girl who wanders away from her parents along a path that leads to a world ruled by +strange and unusual monster-like animals. Her parents have been changed into pigs along with others inside a bathhouse full of these creatures. Will she ever see the world how it once was?", +"director": "Hayao Miyazaki", "producer": "Toshio Suzuki", "release_date": "2001", "running_time": "124", "rt_score": "97", +"people": [ "https://ghibliapi.vercel.app/people/8228751c-bdc1-4b8d-a6eb-ca0eb909568f" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/dc2e6bd1-8156-4886-adff-b39e6043af0c" }, +{
"id": "90b72513-afd4-4570-84de-a56c312fdf81", "title": "The Cat Returns",
"original_title": "猫の恩返し", "original_title_romanised": "Neko no ongaeshi", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/avPMO5cnaGHgLaNiAIhy33WoQLm.jp g", +"movie_banner": "https://image.tmdb.org/t/p/original/d4BTZvckFTthyhGX27LZnWxl0tl.jpg", +"description": "Haru, a schoolgirl bored by her ordinary routine, saves the life of an unusual cat and suddenly her world is transformed beyond anything she ever imagined. The Cat King rewards her good deed with a flurry of presents, including a very shocking proposal of marriage to his son! Haru embarks on an unexpected journey to the Kingdom of Cats where her eyes are opened to a whole other world.", +"director": "Hiroyuki Morita", "producer": "Toshio Suzuki", "release_date": "2002", "running_time": "75", "rt_score": "89", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/90b72513-afd4-4570-84de-a56c312fdf81" }, +{
"id": "cd3d059c-09f4-4ff3-8d63-bc765a5184fa", "title": "Howl's Moving Castle",
"original_title": "ハウルの動く城", "original_title_romanised": "Hauru no ugoku shiro", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/TkTPELv4kC3u1lkloush8skOjE.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/hjlvbMKhQm7N8tYynr8yQ8GBmqe.jpg",
"description": "When Sophie, a shy young woman, is cursed with an old body by a spiteful +witch, her only chance of breaking the spell lies with a self-indulgent yet insecure young wizard and his companions in his legged, walking home.", +"director": "Hayao Miyazaki", "producer": "Toshio Suzuki", "release_date": "2004", "running_time": "119", "rt_score": "87", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/cd3d059c-09f4-4ff3-8d63-bc765a5184fa" }, +{
"id": "112c1e67-726f-40b1-ac17-6974127bb9b9", "title": "Tales from Earthsea",
"original_title": "ゲド戦記", "original_title_romanised": "Gedo senki", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/67yYwCPq7NbxSF6BIIXCMD34sY0.jpg ", +"movie_banner": "https://image.tmdb.org/t/p/original/j276noIGGmfi66EnCfewsL2OVTX.jpg", +"description": "Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans. Due to all these bizarre events, Ged, a wandering wizard, is investigating the cause. During his journey, he meets Prince Arren, a young distraught teenage boy. While Arren may look like a shy young teen, he has a severe dark side, which grants him strength, hatred, ruthlessness and has no mercy, especially when it comes to protecting Teru. For the witch Kumo this is a perfect opportunity. She can use the boy's 'fears' against the very one who would help him, Ged.", +"director": "Gorō Miyazaki", "producer": "Toshio Suzuki", "release_date": "2006", "running_time": "116", "rt_score": "41", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/112c1e67-726f-40b1-ac17-6974127bb9b9" }, +{
"id": "758bf02e-3122-46e0-884e-67cf83df1786", "title": "Ponyo",
"original_title": "崖の上のポニョ", "original_title_romanised": "Gake no ue no Ponyo", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/mikKSEdk5kLhflWXbp4S5mmHsDo.jpg ", +"movie_banner": "https://image.tmdb.org/t/p/original/6a1qZ1qat26mAIK3Lq8iYdGpyHm.jpg", +"description": "The son of a sailor, 5-year old Sosuke lives a quiet life on an oceanside cliff with his mother Lisa. One fateful day, he finds a beautiful goldfish trapped in a bottle on the beach and upon rescuing her, names her Ponyo. But she is no ordinary goldfish. The daughter of a masterful wizard and a sea goddess, Ponyo uses her father's magic to transform herself into a young girl and quickly falls in love with Sosuke, but the use of such powerful sorcery causes a dangerous imbalance in the world. As the moon steadily draws nearer to the earth and Ponyo's father sends the ocean's mighty waves to find his daughter, the two children embark on an adventure of a lifetime to save the world and fulfill Ponyo's dreams of becoming human.", +"director": "Hayao Miyazaki", "producer": "Toshio Suzuki", +"release_date": "2008", "running_time": "100", "rt_score": "92", "people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/758bf02e-3122-46e0-884e-67cf83df1786" }, +{
"id": "2de9426b-914a-4a06-a3a0-5e6d9d3886f6", "title": "Arrietty",
"original_title": "借りぐらしのアリエッティ", "original_title_romanised": "Karigurashi no Arietti", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/oc2OB2KDmSRDMelKEAA1n4YRQL0.j pg", +"movie_banner": "https://image.tmdb.org/t/p/original/7Z7WVzJsSReG8B0CaPk0bvWD7tK.jpg", +"description": "14-year-old Arrietty and the rest of the Clock family live in peaceful anonymity as they make their own home from items 'borrowed' from the house's human inhabitants. However, life changes for the Clocks when a human boy discovers Arrietty.", +"director": "Hiromasa Yonebayashi", "producer": "Toshio Suzuki", "release_date": "2010", "running_time": "94", +"rt_score": "95", "people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +],
"url": "https://ghibliapi.vercel.app/films/2de9426b-914a-4a06-a3a0-5e6d9d3886f6" +}, { +"id": "45db04e4-304a-4933-9823-33f389e8d74d", "title": "From Up on Poppy Hill",
"original_title": "コクリコ坂から", "original_title_romanised": "Kokuriko zaka kara", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/rRLYX4RZIyloHSJwvZKAhphAjiB.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/xtPBZYaWQMQxRpy7mkdk5n1bTxs.jpg", "description": "The story is set in 1963 in Yokohama. Kokuriko Manor sits on a hill +overlooking the harbour. A 16 year-old girl, Umi, lives in that house. Every morning she raises a signal flag facing the sea. The flag means “I pray for safe voyages”. A 17 year-old boy, Shun, always sees this flag from the sea as he rides a tugboat to school. Gradually the pair are drawn to each other but they are faced with a sudden trial. Even so, they keep going without running from facing the hardships of reality.", +"director": "Gorō Miyazaki", "producer": "Toshio Suzuki", "release_date": "2011", "running_time": "91", "rt_score": "83", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], "vehicles": [ +"https://ghibliapi.vercel.app/vehicles/" ], +"url": "https://ghibliapi.vercel.app/films/45db04e4-304a-4933-9823-33f389e8d74d" }, +{
"id": "67405111-37a5-438f-81cc-4666af60c800", "title": "The Wind Rises",
"original_title": "風立ちぬ", "original_title_romanised": "Kaze tachinu", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/jfwSexzlIzaOgxP9A8bTA6t8YYb.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/stM3jlD4nSJhlvR2DE7XnB0eN25.jpg",
"description": "A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi, +whose storied career includes the creation of the A-6M World War II fighter plane.", "director": "Hayao Miyazaki",
"producer": "Toshio Suzuki", +"release_date": "2013", "running_time": "126", "rt_score": "89", "people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/67405111-37a5-438f-81cc-4666af60c800" }, +{
"id": "578ae244-7750-4d9f-867b-f3cd3d6fecf4",
"title": "The Tale of the Princess Kaguya",
"original_title": "かぐや姫の物語", "original_title_romanised": "Kaguya-Hime no Monogatari", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/mWRQNlWXYYfd2z4FRm99MsgHgiA.j pg", +"movie_banner": "https://image.tmdb.org/t/p/original/lMaWlYThCSnsmW3usxWTpSuyZp1.jpg", +"description": "A bamboo cutter named Sanuki no Miyatsuko discovers a miniature girl inside a glowing bamboo shoot. Believing her to be a divine presence, he and his wife decide to raise her as their own, calling her 'Princess'.", +"director": "Isao Takahata", "producer": "Yoshiaki Nishimura", "release_date": "2013", "running_time": "137",
"rt_score": "100",
"people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +],
"url": "https://ghibliapi.vercel.app/films/578ae244-7750-4d9f-867b-f3cd3d6fecf4" +}, { +"id": "5fdfb320-2a02-49a7-94ff-5ca418cae602", "title": "When Marnie Was There", "original_title": "思い出のマーニー", "original_title_romanised": "Omoide no Marnie", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/vug1dvDI1tSa60Z8qjCuUE7ntkO.jpg", "movie_banner": +"https://image.tmdb.org/t/p/original/axUX7urQDwCGQ9qbgh2Yys7qY9J.jpg", "description": "The film follows Anna Sasaki living with her relatives in the seaside town. +Anna comes across a nearby abandoned mansion, where she meets Marnie, a mysterious girl who asks her to promise to keep their secrets from everyone. As the summer progresses, Anna spends more time with Marnie, and eventually Anna learns the truth about her family and foster care.", +"director": "Hiromasa Yonebayashi", "producer": "Yoshiaki Nishimura", "release_date": "2014", "running_time": "103", +"rt_score": "92", "people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/5fdfb320-2a02-49a7-94ff-5ca418cae602" }, +{
"id": "d868e6ec-c44a-405b-8fa6-f7f0f8cfb500",
"title": "The Red Turtle",
"original_title": "レッドタートル ある島の物語", "original_title_romanised": "Reddotātoru aru shima no monogatari", "image": +"https://image.tmdb.org/t/p/w600_and_h900_bestv2/wOBU3SLjQ9358Km9YWYasPZyebp.jp g", +"movie_banner": "https://image.tmdb.org/t/p/original/kjXdW5H3myRBmTMYgKayjphr2FA.jpg", +"description": "A man set adrift by a storm wakes up on a beach. He discovers that he is on a deserted island with plenty of fresh water, fruit and a dense bamboo forest. He builds a raft from bamboo and attempts to sail away, but his raft is destroyed by an unseen monster in the sea, forcing him back to the island. He tries again with another, larger raft, but is again +foiled by the creature. A third attempt again ends with the raft destroyed, but this time he is confronted by a giant red turtle, which stares at him, and forces him back to the island.", +"director": "Michaël Dudok de Wit", +"producer": "Toshio Suzuki, Isao Takahata, Vincent Maraval, Pascal Caucheteux, Grégoire Sorlat", +"release_date": "2016", "running_time": "80", "rt_score": "93", "people": [ +"https://ghibliapi.vercel.app/people/" ], +"species": [ "https://ghibliapi.vercel.app/species/" +], "locations": [ +"https://ghibliapi.vercel.app/locations/" ], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/d868e6ec-c44a-405b-8fa6-f7f0f8cfb500" }, +{
"id": "790e0028-a31c-4626-a694-86b7a8cada40", "title": "Earwig and the Witch",
"original_title": "アーヤと魔女", "original_title_romanised": "Āya to Majo", "image": +"https://www.themoviedb.org/t/p/w600_and_h900_bestv2/sJhFtY3eHuvvACaPpxpzdCLQqp Q.jpg", +"movie_banner": "https://www.themoviedb.org/t/p/original/qMxpGzmmnY1jLd4p7EhhoW43wWF.jpg", +"description": "An orphan girl, Earwig, is adopted by a witch and comes home to a spooky house filled with mystery and magic.", +"director": "Gorō Miyazaki", "producer": "Toshio Suzuki", "release_date": "2021", "running_time": "82", "rt_score": "30", +"people": [ "https://ghibliapi.vercel.app/people/" +], "species": [ +"https://ghibliapi.vercel.app/species/" ], +"locations": [ "https://ghibliapi.vercel.app/locations/" +], +"vehicles": [ "https://ghibliapi.vercel.app/vehicles/" +], +"url": "https://ghibliapi.vercel.app/films/790e0028-a31c-4626-a694-86b7a8cada40" } +] 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 new file mode 100644 index 000000000..02b64279d --- /dev/null +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/http/routes/AssistantRoutes.kt @@ -0,0 +1,87 @@ +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.xef.Config +import com.xebia.functional.xef.OpenAI +import com.xebia.functional.xef.server.models.Token +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Routing.assistantRoutes() { + 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) + call.respond(status = HttpStatusCode.Created, response) + } else { + call.respond( + HttpStatusCode.UnsupportedMediaType, + "Unsupported content type: $contentType" + ) + } + } catch (e: Exception) { + val trace = e.stackTraceToString() + call.respond(HttpStatusCode.BadRequest, "Invalid request: $trace") + } + } + + // 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) + val assistants = openAI.assistants + val assistantObject = assistants.createAssistant(request) + return assistantObject +} + +// suspend fun updateAssistant(token: String, request: AssistantRequest, id: Int): String { +// // Implement the logic for updating an assistant in OpenAI here +// } + +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() + + return listAssistants +} + +/*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 47b15f346..a4dcbfbb6 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,4 +12,5 @@ fun Routing.xefRoutes(logger: KLogger) { organizationRoutes(OrganizationRepositoryService(logger)) projectsRoutes(ProjectRepositoryService(logger)) tokensRoutes(TokenRepositoryService(logger)) + assistantRoutes() } diff --git a/server/web/src/components/Pages/Assistants/Assistants.tsx b/server/web/src/components/Pages/Assistants/Assistants.tsx index d6e58d6da..83e2a61b9 100644 --- a/server/web/src/components/Pages/Assistants/Assistants.tsx +++ b/server/web/src/components/Pages/Assistants/Assistants.tsx @@ -2,39 +2,33 @@ import { useContext, useEffect, useState, ChangeEvent } from "react"; import { useAuth } from "@/state/Auth"; import { LoadingContext } from "@/state/Loading"; -import { - getAssistants, - postAssistant, - putAssistant, - deleteAssistant, -} from "@/utils/api/assistants"; import { Alert, - Box, - Button, - Checkbox, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - FormControlLabel, - FormGroup, - MenuItem, - Grid, - Divider, - Paper, - Switch, - Snackbar, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - TextField, - Typography, - Slider + Box, + Button, + Checkbox, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + FormControlLabel, + FormGroup, + Grid, + Divider, + Paper, + Snackbar, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Slider, + MenuItem, + Switch } from "@mui/material"; type Assistant = { @@ -63,54 +57,16 @@ export function Assistants() { const [JsonObjectEnabled, setJsonObjectEnabled] = useState(false); const [temperature, setTemperature] = useState(1); const [topP, setTopP] = useState(1); + const [assistantCreated, setAssistantCreated] = useState(false); - async function loadAssistants() { - setLoading(true); - try { - const response = await getAssistants(auth.authToken); - setAssistants(response); - } catch (error) { - console.error('Error fetching assistants:', error); - setShowAlert('Failed to load assistants.'); - } - setLoading(false); - } - - useEffect(() => { - loadAssistants(); - }, []); - - const models = [ - { - value: 'gpt-4-turbo', - label: 'gpt-4-turbo', - }, - { - value: 'gpt-4', - label: 'gpt-4', - }, - { - value: 'gpt-3.5-turbo-16k', - label: 'gpt-3.5-turbo-16k', - }, - { - 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 [fileSearchSelectedFile, fileSearchSetSelectedFile] = useState([]); + const [fileSearchDialogOpen, setFileSearchDialogOpen] = useState(false); + const [codeInterpreterSelectedFile, codeInterpreterSetSelectedFile] = useState([]); + const [codeInterpreterDialogOpen, setCodeInterpreterDialogOpen] = useState(false); - const handleCreateAssistant = async () => { - // Aquí se incluirá la lógica para crear un nuevo asistente - // Por ejemplo: await postAssistant(authToken, { name: selectedAssistant.name }); - }; + const [fileSearchFiles, setFileSearchFiles] = useState<{ name: string; size: number; uploaded: string }[]>([]); + const [attachedFilesFileSearch, setAttachedFilesFileSearch] = useState<{ name: string; size: string; uploaded: string }[]>([]); + const [attachedFilesCodeInterpreter, setAttachedFilesCodeInterpreter] = useState<{ name: string; size: string; uploaded: string }[]>([]); const handleFileSearchChange = (event: ChangeEvent) => { setFileSearchEnabled(event.target.checked); @@ -121,165 +77,528 @@ export function Assistants() { }; const handleJsonObjectChange = (event: ChangeEvent) => { - setJsonObjectEnabled(event.target.checked); - }; + setJsonObjectEnabled(event.target.checked); + }; const handleTemperatureChange = (event: Event, newValue: number | number[]) => { setTemperature(newValue as number); }; + const handleTopPChange = (event: Event, newValue: number | number[]) => { - setTopP(newValue as number); - }; - const handleFilesButtonClick = (toolName: String)=> { - console.log(`Clicked on ${toolName} button`); + setTopP(newValue as number); + }; + + const handleTemperatureInputChange = (event: React.ChangeEvent) => { + const value = parseFloat(event.target.value); + if (!isNaN(value)) { + setTemperature(value); + } + }; + + const handleTopPInputChange = (event: React.ChangeEvent) => { + const value = parseFloat(event.target.value); + if (!isNaN(value)) { + setTopP(value); + } + }; + + const handleFileSearchDialogClose = () => { + setFileSearchDialogOpen(false); + setAttachedFilesFileSearch(fileSearchSelectedFile); + }; + + const handleCodeInterpreterDialogClose = () => { + setCodeInterpreterDialogOpen(false); + setAttachedFilesCodeInterpreter(codeInterpreterSelectedFile); + }; + + const handleFileSearchButtonClick = () => { + setFileSearchDialogOpen(true); + }; + + const handleCodeInterpreterButtonClick = () => { + setCodeInterpreterDialogOpen(true); + }; + + const handleFileSearchInputChange = (event: ChangeEvent) => { + const files = Array.from(event.target.files); + const newFiles = files.map((file) => ({ + name: file.name, + size: `${Math.round(file.size / 1024)} KB`, + uploaded: new Date().toLocaleString() + })); + fileSearchSetSelectedFile((prevFiles) => [...prevFiles, ...newFiles]); + }; + + const handleCodeInterpreterInputChange = (event: ChangeEvent) => { + const files = Array.from(event.target.files); + const newFiles = files.map((file) => ({ + name: file.name, + size: `${Math.round(file.size / 1024)} KB`, + uploaded: new Date().toLocaleString() + })); + codeInterpreterSetSelectedFile((prevFiles) => [...prevFiles, ...newFiles]); + }; + + const handleDeleteFile = (index: number) => { + const updatedFiles = fileSearchSelectedFile.filter((_, i) => i !== index); + fileSearchSetSelectedFile(updatedFiles); + }; + + const openFileSelector = (handleInputChange) => { + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.onchange = (event) => { + const files = Array.from(event.target.files); + handleInputChange(event); }; + input.click(); + }; + + 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-4-turbo', label: 'gpt-4-turbo' }, + { value: 'gpt-4', label: 'gpt-4' }, + { value: 'gpt-3.5-turbo-16k', label: 'gpt-3.5-turbo-16k' }, + { 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 = { + minWidth: '500px', + textAlign: 'left', + }; return ( - - - {showCreatePanel && ( - - - - {selectedAssistant.id ? 'Edit Assistant' : 'Create Assistant'} - - - setSelectedAssistant({ ...selectedAssistant, name: e.target.value })} - margin="normal" - /> - - setSelectedAssistant({ ...selectedAssistant, model: e.target.value })} - margin="normal" - sx={{ display: 'block', mt: 3, mb: 3 }} - SelectProps={{ - native: true, - }} - helperText="Please select your model" - > - {models.map((option) => ( - - ))} - - - Tools - - } - label="File search" - sx={{ display: 'block', mt: 2, mb: 2 }} - /> - - } - label="Code interpreter" - sx={{ display: 'block', mt: 2, mb: 2 }} - /> - - Functions - - MODEL CONFIGURATION - - Response format - - } - label="JSON object" - sx={{ display: 'block', mt: 1, mb: 1 }} - /> - Temperature - - Top P - - - - - )} - {/* Vista principal */} - - - Assistants - - - {loading ? ( - Loading... - ) : ( - assistants.length === 0 ? ( - No assistants available. - ) : ( - - - - - Date - Name - Actions - - - - {assistants.map((assistant) => ( - - {assistant.createdAt} - {assistant.name} - - - - - + + + + + {/* Container of Assistants */} + +
+ Assistants + {loading ? ( + Loading... + ) : ( + assistants.length === 0 && !assistantCreated ? ( + No assistants available. + ) : ( + +
+ + + Date + Name + Actions + + + + {assistants.map((assistant) => ( + + {assistant.createdAt} + {assistant.name} + + + + + + ))} + +
+
+ ) + )} + + + + + + {/* Container of Create Assistant */} + +
+ + {/* Creation panel */} + {showCreatePanel && ( + + + + {selectedAssistant.id ? 'Edit Assistant' : 'Create Assistant'} + + + setSelectedAssistant({ ...selectedAssistant, name: e.target.value })} + margin="normal" + /> + + setSelectedAssistant({ ...selectedAssistant, model: e.target.value })} + margin="normal" + sx={{ display: 'block', mt: 3, mb: 3 }} + SelectProps={{ + native: true, + }} + helperText="Please select your model" + > + {models.map((option) => ( + ))} - - - - ) - )} - {/* Diálogos de edición y eliminación */} - {/* Snackbar de alerta */} - + + Tools + +
+ } + label="File search" + sx={{ flex: '1', mt: 2, mb: 2 }} + /> + +
+ {fileSearchDialogOpen && ( + { + e.preventDefault(); + }} + onDragEnter={(e) => { + e.preventDefault(); + }} + onDrop={(e) => { + e.preventDefault(); + const files = Array.from(e.dataTransfer.files); + handleFileSearchInputChange({ target: { files } }); + }} + > + Attach files to file search + + + {fileSearchSelectedFile.length === 0 ? ( + <> + Drag your files here or{" "} + { + e.stopPropagation(); + openFileSelector(handleFileSearchInputChange); + }} + onMouseEnter={(e) => { + e.target.style.color = "darkblue"; + }} + onMouseLeave={(e) => { + e.target.style.color = "blue"; + }} + > + click to upload + + + Information in attached files will be available to this assistant. + + + ) : ( + <> + )} + + + {fileSearchSelectedFile.length > 0 && ( + + + + + File + Size + Uploaded + + + + {fileSearchSelectedFile.map((file, index) => ( + + {file.name} + {file.size} + {file.uploaded} + + + + + ))} + +
+
+ )} +
+ + + + {fileSearchSelectedFile.length > 0 && ( + + )} + + + +
+ + )} + {attachedFilesFileSearch.map((file, index) => ( + + File {index + 1}: {file.name} + + ))} + +
+ } + label="Code interpreter" + sx={{ display: 'block', mt: 2, mb: 2 }} + /> + +
+ + {codeInterpreterDialogOpen && ( + { + e.preventDefault(); + }} + onDragEnter={(e) => { + e.preventDefault(); + }} + onDrop={(e) => { + e.preventDefault(); + const files = Array.from(e.dataTransfer.files); + handleCodeInterpreterInputChange({ target: { files } }); + }} + > + Attach files to file search + + + {codeInterpreterSelectedFile.length === 0 ? ( + <> + Drag your files here or{" "} + { + e.stopPropagation(); + openFileSelector(handleCodeInterpreterInputChange); + }} + onMouseEnter={(e) => { + e.target.style.color = "darkblue"; + }} + onMouseLeave={(e) => { + e.target.style.color = "blue"; + }} + > + click to upload + + + Information in attached files will be available to this assistant. + + + ) : ( + <> + )} + + + {codeInterpreterSelectedFile.length > 0 && ( + + + + + File + Size + Uploaded + + + + {codeInterpreterSelectedFile.map((file, index) => ( + + {file.name} + {file.size} + {file.uploaded} + + + + + ))} + +
+
+ )} +
+ + + + {codeInterpreterSelectedFile.length > 0 && ( + + )} + + + +
+ )} + {attachedFilesCodeInterpreter.map((file, index) => ( + + File {index + 1}: {file.name} + + ))} + +
+ Functions + +
+ + +
+ MODEL CONFIGURATION + + Response format + + } + label="JSON object" + sx={{ display: 'block', mt: 1, mb: 3 }} + /> +
+ +
+
+ Temperature + +
+ +
+ Top P + +
+ +
+ +
+ +
+ +
+ )} +
+
- ); - } + + + +
+ ); + } \ No newline at end of file From 4f0e0b79d09ba52ff570a5ac8d10175c8250ae1e Mon Sep 17 00:00:00 2001 From: mccarrascog Date: Mon, 13 May 2024 16:09:13 +0200 Subject: [PATCH 5/5] wip-createAssistant-endpoint-success --- examples/build.gradle.kts | 1 + server/build.gradle.kts | 2 ++ .../com/xebia/functional/xef/server/Server.kt | 2 +- .../xef/server/http/routes/AssistantRoutes.kt | 7 ++--- server/src/main/resources/logback.xml | 26 +++++++++++++++++++ 5 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 server/src/main/resources/logback.xml diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index b29a285e2..6ea94bb67 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(libs.jmf) implementation(libs.mp3.wav.converter) api(libs.ktor.client) + } spotless { diff --git a/server/build.gradle.kts b/server/build.gradle.kts index b9398e1e7..bf972c119 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -56,6 +56,8 @@ dependencies { testImplementation(libs.kotest.testcontainers) testImplementation(libs.testcontainers.postgresql) testRuntimeOnly(libs.kotest.junit5) + implementation(libs.logback) + implementation(libs.klogging) } spotless { diff --git a/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt b/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt index 2566c167f..92aecf7ee 100644 --- a/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt +++ b/server/src/main/kotlin/com/xebia/functional/xef/server/Server.kt @@ -59,7 +59,7 @@ object Server { requestTimeout = 0 // disabled } install(Auth) - install(Logging) { level = LogLevel.INFO } + install(Logging) { level = LogLevel.ALL } install(ClientContentNegotiation) } 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 02b64279d..179c1e99c 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 @@ -5,6 +5,7 @@ import com.xebia.functional.openai.generated.model.CreateAssistantRequest import com.xebia.functional.openai.generated.model.ListAssistantsResponse 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.ktor.http.* import io.ktor.server.application.* @@ -63,10 +64,10 @@ fun Routing.assistantRoutes() { suspend fun createAssistant(token: Token, request: CreateAssistantRequest): AssistantObject { val openAIConfig = Config(token = token.value) - val openAI = OpenAI(openAIConfig) + val openAI = OpenAI(openAIConfig, logRequests = true) val assistants = openAI.assistants - val assistantObject = assistants.createAssistant(request) - return assistantObject + val assistant = Assistant(request) + return assistant.get() } // suspend fun updateAssistant(token: String, request: AssistantRequest, id: Int): String { diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml new file mode 100644 index 000000000..9a90533b5 --- /dev/null +++ b/server/src/main/resources/logback.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n + + + + + + + + + + + + + + +