From edcbb30e97535013b61d5a94bb4204d030cba2f2 Mon Sep 17 00:00:00 2001 From: Mamadou DICKO <63923024+mamadoudicko@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:25:02 +0200 Subject: [PATCH] fix: bugs (#818) * feat: add chat config modal * feat: save chat config in localStorage * feat: remove * feat: overwrite chat config with brain * fix(SettingsPage): upload payload keys * fix: update default brain marker logic * feat: set new created brain as current selected brain --- backend/core/models/brains.py | 9 +- .../resend_invitation_email.py | 4 +- backend/core/routes/brain_routes.py | 13 ++- backend/core/routes/chat_routes.py | 62 +++++++++- backend/core/routes/subscription_routes.py | 4 +- backend/core/routes/upload_routes.py | 4 +- .../SettingsTab/hooks/useSettingsTab.ts | 39 +++++-- .../ChatInput/__tests__/ChatInput.test.tsx | 78 +++++++++++-- .../ChatInput/components/ConfigButton.tsx | 19 --- .../components/ConfigModal/ConfigModal.tsx | 101 ++++++++++++++++ .../ConfigModal/hooks/useConfigModal.ts | 109 ++++++++++++++++++ .../ChatInput/components/ConfigModal/index.ts | 1 + .../[chatId]/components/ChatInput/index.tsx | 8 +- frontend/app/chat/[chatId]/hooks/useChat.ts | 29 ++--- frontend/app/chat/[chatId]/types/index.ts | 6 +- frontend/app/config/__tests__/page.test.tsx | 73 ------------ .../components/ApiKeyConfig/ApiKeyConfig.tsx | 49 -------- .../__tests__/ApiKeyConfig.test.tsx | 52 --------- .../hooks/__tests__/useApiKeyConfig.test.ts | 67 ----------- .../ApiKeyConfig/hooks/useApiKeyConfig.ts | 45 -------- .../BackendConfig/BackendConfig.tsx | 56 --------- .../__tests__/BackendConfig.test.tsx | 24 ---- frontend/app/config/components/ConfigForm.tsx | 64 ---------- .../app/config/components/ConfigTitle.tsx | 10 -- .../app/config/components/ModelConfig.tsx | 102 ---------------- .../config/components/UserAccountSection.tsx | 36 ------ frontend/app/config/components/index.tsx | 3 - frontend/app/config/hooks/useConfig.ts | 84 -------------- frontend/app/config/page.tsx | 27 ----- frontend/lib/api/chat/chat.local.ts | 20 ++++ .../AddBrainModal/hooks/useAddBrainModal.ts | 24 ++-- .../components/BrainManagementButton.tsx | 13 ++- .../NavBar/components/NavItems/index.tsx | 12 +- frontend/lib/context/ChatProvider/types.ts | 8 ++ 34 files changed, 453 insertions(+), 802 deletions(-) delete mode 100644 frontend/app/chat/[chatId]/components/ChatInput/components/ConfigButton.tsx create mode 100644 frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/ConfigModal.tsx create mode 100644 frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/hooks/useConfigModal.ts create mode 100644 frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/index.ts delete mode 100644 frontend/app/config/__tests__/page.test.tsx delete mode 100644 frontend/app/config/components/ApiKeyConfig/ApiKeyConfig.tsx delete mode 100644 frontend/app/config/components/ApiKeyConfig/__tests__/ApiKeyConfig.test.tsx delete mode 100644 frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts delete mode 100644 frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts delete mode 100644 frontend/app/config/components/BackendConfig/BackendConfig.tsx delete mode 100644 frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx delete mode 100644 frontend/app/config/components/ConfigForm.tsx delete mode 100644 frontend/app/config/components/ConfigTitle.tsx delete mode 100644 frontend/app/config/components/ModelConfig.tsx delete mode 100644 frontend/app/config/components/UserAccountSection.tsx delete mode 100644 frontend/app/config/components/index.tsx delete mode 100644 frontend/app/config/hooks/useConfig.ts delete mode 100644 frontend/app/config/page.tsx create mode 100644 frontend/lib/api/chat/chat.local.ts diff --git a/backend/core/models/brains.py b/backend/core/models/brains.py index 475ded951..5a7e99413 100644 --- a/backend/core/models/brains.py +++ b/backend/core/models/brains.py @@ -2,11 +2,12 @@ from typing import Any, List, Optional from uuid import UUID from logger import get_logger -from models.settings import BrainRateLimiting, CommonsDep, common_dependencies -from models.users import User from pydantic import BaseModel from utils.vectors import get_unique_files_from_vector_ids +from models.settings import BrainRateLimiting, CommonsDep, common_dependencies +from models.users import User + logger = get_logger(__name__) @@ -111,7 +112,9 @@ class Brain(BaseModel): .filter("brain_id", "eq", self.id) .execute() ) - return response.data + if response.data == []: + return None + return response.data[0] def delete_brain(self, user_id): results = ( diff --git a/backend/core/repository/brain_subscription/resend_invitation_email.py b/backend/core/repository/brain_subscription/resend_invitation_email.py index 1d29a5b80..050aba5bd 100644 --- a/backend/core/repository/brain_subscription/resend_invitation_email.py +++ b/backend/core/repository/brain_subscription/resend_invitation_email.py @@ -20,7 +20,9 @@ def resend_invitation_email( brain_url = get_brain_url(origin, brain_subscription.brain_id) invitation_brain_client = Brain(id=brain_subscription.brain_id) - invitation_brain = invitation_brain_client.get_brain_details()[0] + invitation_brain = invitation_brain_client.get_brain_details() + if invitation_brain is None: + raise Exception("Brain not found") brain_name = invitation_brain["name"] html_body = f""" diff --git a/backend/core/routes/brain_routes.py b/backend/core/routes/brain_routes.py index de17b69c0..220613e30 100644 --- a/backend/core/routes/brain_routes.py +++ b/backend/core/routes/brain_routes.py @@ -73,15 +73,16 @@ async def get_brain_endpoint( history, which includes the brain messages exchanged in the brain. """ brain = Brain(id=brain_id) - brains = brain.get_brain_details() - if len(brains) > 0: - return brains[0] - else: - return HTTPException( + + brain_details = brain.get_brain_details() + if brain_details is None: + raise HTTPException( status_code=404, - detail="Brain not found", + detail="Brain details not found", ) + return brain_details + # create new brain @brain_router.post("/brains/", dependencies=[Depends(AuthBearer())], tags=["Brain"]) diff --git a/backend/core/routes/chat_routes.py b/backend/core/routes/chat_routes.py index 0991fddef..ee6510138 100644 --- a/backend/core/routes/chat_routes.py +++ b/backend/core/routes/chat_routes.py @@ -9,7 +9,7 @@ from auth import AuthBearer, get_current_user from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import StreamingResponse from llm.openai import OpenAIBrainPicking -from models.brains import get_default_user_brain_or_create_new +from models.brains import Brain, get_default_user_brain_or_create_new from models.chat import Chat, ChatHistory from models.chats import ChatQuestion from models.settings import LLMSettings, common_dependencies @@ -19,12 +19,12 @@ from repository.chat.get_chat_by_id import get_chat_by_id from repository.chat.get_chat_history import get_chat_history from repository.chat.get_user_chats import get_user_chats from repository.chat.update_chat import ChatUpdatableProperties, update_chat +from repository.user_identity.get_user_identity import get_user_identity chat_router = APIRouter() class NullableUUID(UUID): - @classmethod def __get_validators__(cls): yield cls.validate @@ -180,10 +180,38 @@ async def create_question_handler( | None = Query(..., description="The ID of the brain"), current_user: User = Depends(get_current_user), ) -> ChatHistory: + """ + Add a new question to the chat. + """ + # Retrieve user's OpenAI API key current_user.user_openai_api_key = request.headers.get("Openai-Api-Key") + brain = Brain(id=brain_id) + + if not current_user.user_openai_api_key: + brain_details = brain.get_brain_details() + if brain_details: + current_user.user_openai_api_key = brain_details["openai_api_key"] + + if not current_user.user_openai_api_key: + user_identity = get_user_identity(current_user.id) + + if user_identity is not None: + current_user.user_openai_api_key = user_identity.openai_api_key + + # Retrieve chat model (temperature, max_tokens, model) + if ( + not chat_question.model + or not chat_question.temperature + or not chat_question.max_tokens + ): + # TODO: create ChatConfig class (pick config from brain or user or chat) and use it here + chat_question.model = chat_question.model or brain.model or "gpt-3.5-turbo-0613" + chat_question.temperature = chat_question.temperature or brain.temperature or 0 + chat_question.max_tokens = chat_question.max_tokens or brain.max_tokens or 256 + try: check_user_limit(current_user) - llm_settings = LLMSettings() + LLMSettings() if not brain_id: brain_id = get_default_user_brain_or_create_new(current_user).id @@ -227,14 +255,38 @@ async def create_stream_question_handler( ) -> StreamingResponse: # TODO: check if the user has access to the brain + # Retrieve user's OpenAI API key + current_user.user_openai_api_key = request.headers.get("Openai-Api-Key") + brain = Brain(id=brain_id) + + if not current_user.user_openai_api_key: + brain_details = brain.get_brain_details() + if brain_details: + current_user.user_openai_api_key = brain_details["openai_api_key"] + + if not current_user.user_openai_api_key: + user_identity = get_user_identity(current_user.id) + + if user_identity is not None: + current_user.user_openai_api_key = user_identity.openai_api_key + + # Retrieve chat model (temperature, max_tokens, model) + if ( + not chat_question.model + or not chat_question.temperature + or not chat_question.max_tokens + ): + # TODO: create ChatConfig class (pick config from brain or user or chat) and use it here + chat_question.model = chat_question.model or brain.model or "gpt-3.5-turbo-0613" + chat_question.temperature = chat_question.temperature or brain.temperature or 0 + chat_question.max_tokens = chat_question.max_tokens or brain.max_tokens or 256 + try: - user_openai_api_key = request.headers.get("Openai-Api-Key") logger.info(f"Streaming request for {chat_question.model}") check_user_limit(current_user) if not brain_id: brain_id = get_default_user_brain_or_create_new(current_user).id - gpt_answer_generator = OpenAIBrainPicking( chat_id=str(chat_id), model=chat_question.model, diff --git a/backend/core/routes/subscription_routes.py b/backend/core/routes/subscription_routes.py index f264ac411..16efb5c63 100644 --- a/backend/core/routes/subscription_routes.py +++ b/backend/core/routes/subscription_routes.py @@ -173,13 +173,13 @@ def get_user_invitation(brain_id: UUID, current_user: User = Depends(get_current brain = Brain(id=brain_id) brain_details = brain.get_brain_details() - if len(brain_details) == 0: + if brain_details is None: raise HTTPException( status_code=404, detail="Brain not found while trying to get invitation", ) - return {"name": brain_details[0]["name"], "rights": invitation["rights"]} + return {"name": brain_details["name"], "rights": invitation["rights"]} @subscription_router.post( diff --git a/backend/core/routes/upload_routes.py b/backend/core/routes/upload_routes.py index 4a9a181c5..42d087126 100644 --- a/backend/core/routes/upload_routes.py +++ b/backend/core/routes/upload_routes.py @@ -62,7 +62,9 @@ async def upload_file( else: openai_api_key = request.headers.get("Openai-Api-Key", None) if openai_api_key is None: - openai_api_key = brain.get_brain_details()["openai_api_key"] + brain_details = brain.get_brain_details() + if brain_details: + openai_api_key = brain_details["openai_api_key"] if openai_api_key is None: openai_api_key = get_user_identity(current_user.id).openai_api_key diff --git a/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts b/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts index 2ed26de30..4526fa909 100644 --- a/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts +++ b/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/hooks/useSettingsTab.ts @@ -8,7 +8,6 @@ import { useForm } from "react-hook-form"; import { useBrainApi } from "@/lib/api/brain/useBrainApi"; import { useBrainConfig } from "@/lib/context/BrainConfigProvider"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { useBrainProvider } from "@/lib/context/BrainProvider/hooks/useBrainProvider"; import { Brain } from "@/lib/context/BrainProvider/types"; import { defineMaxTokens } from "@/lib/helpers/defineMexTokens"; import { useToast } from "@/lib/hooks"; @@ -25,7 +24,8 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => { const formRef = useRef(null); const { setAsDefaultBrain, getBrain, updateBrain } = useBrainApi(); const { config } = useBrainConfig(); - const { fetchAllBrains, fetchDefaultBrain } = useBrainContext(); + const { fetchAllBrains, fetchDefaultBrain, defaultBrainId } = + useBrainContext(); const defaultValues = { ...config, @@ -58,15 +58,21 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => { return; } - if (brainKey === "max_tokens") { - if (brain["max_tokens"] !== undefined) { - setValue("maxTokens", brain["max_tokens"]); - } - } else { - // @ts-expect-error bad type inference from typescript - // eslint-disable-next-line - setValue(key, brain[key]); + if (brainKey === "max_tokens" && brain["max_tokens"] !== undefined) { + setValue("maxTokens", brain["max_tokens"]); + continue; } + + if ( + brainKey === "openai_api_key" && + brain["openai_api_key"] !== undefined + ) { + setValue("openAiKey", brain["openai_api_key"]); + continue; + } + // @ts-expect-error bad type inference from typescript + // eslint-disable-next-line + setValue(key, brain[key]); } }; void fetchBrain(); @@ -131,7 +137,17 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => { try { setIsUpdating(true); - await updateBrain(brainId, getValues()); + const { + maxTokens: max_tokens, + openAiKey: openai_api_key, + ...otherConfigs + } = getValues(); + + await updateBrain(brainId, { + ...otherConfigs, + max_tokens, + openai_api_key, + }); publish({ variant: "success", @@ -160,7 +176,6 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => { setIsUpdating(false); } }; - const { defaultBrainId } = useBrainProvider(); const isDefaultBrain = defaultBrainId === brainId; useEffect(() => { diff --git a/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx b/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx index 5578b6a12..c67837586 100644 --- a/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx +++ b/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx @@ -1,10 +1,31 @@ +/* eslint-disable max-lines */ import { fireEvent, render } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { BrainProvider } from "@/lib/context"; +import { BrainConfigProvider } from "@/lib/context/BrainConfigProvider"; + import { ChatInput } from "../index"; const addQuestionMock = vi.fn((...params: unknown[]) => ({ params })); +vi.mock("@/lib/hooks", async () => { + const actual = await vi.importActual( + "@/lib/hooks" + ); + + return { + ...actual, + useAxios: () => ({ + axiosInstance: { + get: vi.fn(() => ({ + data: {}, + })), + }, + }), + }; +}); + vi.mock("@/app/chat/[chatId]/hooks/useChat", () => ({ useChat: () => ({ addQuestion: (...params: unknown[]) => addQuestionMock(...params), @@ -12,14 +33,28 @@ vi.mock("@/app/chat/[chatId]/hooks/useChat", () => ({ }), })); -afterEach(() => { - addQuestionMock.mockClear(); -}); +const mockUseSupabase = vi.fn(() => ({ + session: {}, +})); + +vi.mock("@/lib/context/SupabaseProvider", () => ({ + useSupabase: () => mockUseSupabase(), +})); describe("ChatInput", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + it("should render correctly", () => { // Rendering the ChatInput component - const { getByTestId } = render(); + const { getByTestId } = render( + + + + + + ); const chatInputForm = getByTestId("chat-input-form"); expect(chatInputForm).toBeDefined(); @@ -30,15 +65,18 @@ describe("ChatInput", () => { const submitButton = getByTestId("submit-button"); expect(submitButton).toBeDefined(); - const configButton = getByTestId("config-button"); - expect(configButton).toBeDefined(); - const micButton = getByTestId("mic-button"); expect(micButton).toBeDefined(); }); it("should not call addQuestion on form submit when message is empty", () => { - const { getByTestId } = render(); + const { getByTestId } = render( + + + + + + ); const chatInputForm = getByTestId("chat-input-form"); fireEvent.submit(chatInputForm); @@ -47,7 +85,13 @@ describe("ChatInput", () => { }); it("should call addQuestion once on form submit when message is not empty", () => { - const { getByTestId } = render(); + const { getByTestId } = render( + + + + + + ); const chatInput = getByTestId("chat-input"); fireEvent.change(chatInput, { target: { value: "Test question" } }); const chatInputForm = getByTestId("chat-input-form"); @@ -65,7 +109,13 @@ describe("ChatInput", () => { // Mocking the addQuestion function // Rendering the ChatInput component with the mock function - const { getByTestId } = render(); + const { getByTestId } = render( + + + + + + ); const chatInput = getByTestId("chat-input"); fireEvent.change(chatInput, { target: { value: "Another test question" } }); @@ -80,7 +130,13 @@ describe("ChatInput", () => { }); it('should not submit a question when "Enter" key is pressed with shift', () => { - const { getByTestId } = render(); + const { getByTestId } = render( + + + + + + ); const inputElement = getByTestId("chat-input"); diff --git a/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigButton.tsx b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigButton.tsx deleted file mode 100644 index 5ef6c170a..000000000 --- a/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigButton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; -import Link from "next/link"; -import { MdSettings } from "react-icons/md"; - -import Button from "@/lib/components/ui/Button"; - -export const ConfigButton = (): JSX.Element => { - return ( - - - - ); -}; diff --git a/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/ConfigModal.tsx b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/ConfigModal.tsx new file mode 100644 index 000000000..c8c71ab35 --- /dev/null +++ b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/ConfigModal.tsx @@ -0,0 +1,101 @@ +/* eslint-disable max-lines */ +import { MdCheck, MdSettings } from "react-icons/md"; + +import Button from "@/lib/components/ui/Button"; +import { Modal } from "@/lib/components/ui/Modal"; +import { models } from "@/lib/context/BrainConfigProvider/types"; +import { defineMaxTokens } from "@/lib/helpers/defineMexTokens"; + +import { useConfigModal } from "./hooks/useConfigModal"; + +export const ConfigModal = ({ chatId }: { chatId?: string }): JSX.Element => { + const { + handleSubmit, + isConfigModalOpen, + setIsConfigModalOpen, + register, + temperature, + maxTokens, + model, + } = useConfigModal(chatId); + + if (chatId === undefined) { + return
; + } + + return ( + + + + } + title="Chat configuration" + desc="Adjust your chat settings" + isOpen={isConfigModalOpen} + setOpen={setIsConfigModalOpen} + CloseTrigger={
} + > +
{ + void handleSubmit(e); + setIsConfigModalOpen(false); + }} + className="mt-10 flex flex-col items-center gap-2" + > +
+ + +
+ +
+ + +
+
+ + +
+ + +
+ + ); +}; diff --git a/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/hooks/useConfigModal.ts b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/hooks/useConfigModal.ts new file mode 100644 index 000000000..506a7bc6e --- /dev/null +++ b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/hooks/useConfigModal.ts @@ -0,0 +1,109 @@ +/* eslint-disable max-lines */ +import { FormEvent, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; + +import { useBrainApi } from "@/lib/api/brain/useBrainApi"; +import { + getChatConfigFromLocalStorage, + saveChatConfigInLocalStorage, +} from "@/lib/api/chat/chat.local"; +import { useBrainConfig } from "@/lib/context/BrainConfigProvider"; +import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; +import { ChatConfig } from "@/lib/context/ChatProvider/types"; +import { defineMaxTokens } from "@/lib/helpers/defineMexTokens"; +import { useToast } from "@/lib/hooks"; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const useConfigModal = (chatId?: string) => { + const { publish } = useToast(); + const [isConfigModalOpen, setIsConfigModalOpen] = useState(false); + const { config } = useBrainConfig(); + const { getBrain } = useBrainApi(); + const { currentBrain } = useBrainContext(); + + const defaultValues: ChatConfig = {}; + + const { register, watch, setValue } = useForm({ + defaultValues, + }); + + const model = watch("model"); + const temperature = watch("temperature"); + const maxTokens = watch("maxTokens"); + + useEffect(() => { + const fetchChatConfig = async () => { + if (chatId === undefined) { + return; + } + + const chatConfig = getChatConfigFromLocalStorage(chatId); + if (chatConfig !== undefined) { + setValue("model", chatConfig.model); + setValue("temperature", chatConfig.temperature); + setValue("maxTokens", chatConfig.maxTokens); + } else { + if (currentBrain === undefined) { + return; + } + + const relatedBrainConfig = await getBrain(currentBrain.id); + if (relatedBrainConfig === undefined) { + return; + } + setValue("model", relatedBrainConfig.model ?? config.model); + setValue( + "temperature", + relatedBrainConfig.temperature ?? config.temperature + ); + setValue( + "maxTokens", + relatedBrainConfig.max_tokens ?? config.maxTokens + ); + } + }; + void fetchChatConfig(); + }, []); + + useEffect(() => { + if (maxTokens === undefined || model === undefined) { + return; + } + + setValue("maxTokens", Math.min(maxTokens, defineMaxTokens(model))); + }, [maxTokens, model, setValue]); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (chatId === undefined) { + return; + } + try { + saveChatConfigInLocalStorage(chatId, { + maxTokens, + model, + temperature, + }); + + publish({ + variant: "success", + text: "Chat config successfully updated", + }); + } catch (err) { + publish({ + variant: "danger", + text: "An error occured while updating chat config", + }); + } + }; + + return { + isConfigModalOpen, + setIsConfigModalOpen, + handleSubmit, + register, + model, + temperature, + maxTokens, + }; +}; diff --git a/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/index.ts b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/index.ts new file mode 100644 index 000000000..4c07388b5 --- /dev/null +++ b/frontend/app/chat/[chatId]/components/ChatInput/components/ConfigModal/index.ts @@ -0,0 +1 @@ +export * from "./ConfigModal"; diff --git a/frontend/app/chat/[chatId]/components/ChatInput/index.tsx b/frontend/app/chat/[chatId]/components/ChatInput/index.tsx index f9dbb73fd..05972650c 100644 --- a/frontend/app/chat/[chatId]/components/ChatInput/index.tsx +++ b/frontend/app/chat/[chatId]/components/ChatInput/index.tsx @@ -4,12 +4,12 @@ import Button from "@/lib/components/ui/Button"; import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; import { useState } from "react"; -import { ConfigButton } from "./components/ConfigButton"; +import { ConfigModal } from "./components/ConfigModal"; import { MicButton } from "./components/MicButton/MicButton"; export const ChatInput = (): JSX.Element => { - const [message, setMessage] = useState(""); // for optimistic updates - const { addQuestion, generatingAnswer } = useChat(); + const [message, setMessage] = useState(""); + const { addQuestion, generatingAnswer, chatId } = useChat(); const submitQuestion = () => { if (message.length === 0) return; @@ -52,7 +52,7 @@ export const ChatInput = (): JSX.Element => {
- +
); diff --git a/frontend/app/chat/[chatId]/hooks/useChat.ts b/frontend/app/chat/[chatId]/hooks/useChat.ts index c3c23473b..637465b40 100644 --- a/frontend/app/chat/[chatId]/hooks/useChat.ts +++ b/frontend/app/chat/[chatId]/hooks/useChat.ts @@ -3,20 +3,15 @@ import { AxiosError } from "axios"; import { useParams } from "next/navigation"; import { useEffect, useState } from "react"; - +import { getChatConfigFromLocalStorage } from "@/lib/api/chat/chat.local"; import { useChatApi } from "@/lib/api/chat/useChatApi"; -import { useBrainConfig } from "@/lib/context/BrainConfigProvider/hooks/useBrainConfig"; import { useChatContext } from "@/lib/context/ChatProvider/hooks/useChatContext"; import { useToast } from "@/lib/hooks"; import { useEventTracking } from "@/services/analytics/useEventTracking"; - import { useQuestion } from "./useQuestion"; import { ChatQuestion } from "../types"; - - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useChat = () => { const { track } = useEventTracking(); @@ -25,9 +20,7 @@ export const useChat = () => { params?.chatId as string | undefined ); const [generatingAnswer, setGeneratingAnswer] = useState(false); - const { - config: { maxTokens, model, temperature }, - } = useBrainConfig(); + const { history, setHistory } = useChatContext(); const { publish } = useToast(); const { createChat, getHistory } = useChatApi(); @@ -51,13 +44,6 @@ export const useChat = () => { }, [chatId, setHistory]); const addQuestion = async (question: string, callback?: () => void) => { - const chatQuestion: ChatQuestion = { - model, - question, - temperature, - max_tokens: maxTokens, - }; - try { setGeneratingAnswer(true); @@ -72,10 +58,16 @@ export const useChat = () => { } void track("QUESTION_ASKED"); + const chatConfig = getChatConfigFromLocalStorage(currentChatId); + + const chatQuestion: ChatQuestion = { + model: chatConfig?.model, + question, + temperature: chatConfig?.temperature, + max_tokens: chatConfig?.maxTokens, + }; - await addStreamQuestion(currentChatId, chatQuestion); - callback?.(); } catch (error) { @@ -103,5 +95,6 @@ export const useChat = () => { history, addQuestion, generatingAnswer, + chatId, }; }; diff --git a/frontend/app/chat/[chatId]/types/index.ts b/frontend/app/chat/[chatId]/types/index.ts index 37d9f0d32..89477d133 100644 --- a/frontend/app/chat/[chatId]/types/index.ts +++ b/frontend/app/chat/[chatId]/types/index.ts @@ -1,10 +1,10 @@ import { UUID } from "crypto"; export type ChatQuestion = { - model: string; + model?: string; question?: string; - temperature: number; - max_tokens: number; + temperature?: number; + max_tokens?: number; }; export type ChatHistory = { chat_id: string; diff --git a/frontend/app/config/__tests__/page.test.tsx b/frontend/app/config/__tests__/page.test.tsx deleted file mode 100644 index c4bc6667e..000000000 --- a/frontend/app/config/__tests__/page.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { render } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import { BrainConfigProvider } from "@/lib/context/BrainConfigProvider"; - -import ConfigPage from "../page"; - -// Mocks ConfirmButton as JSX.Element - -const ConfirmFormMock = vi.fn<[], JSX.Element>(() =>
); - -const ConfirmTitleMock = vi.fn(() =>
); -const ApiKeyConfig = vi.fn(() =>
); - -const redirectMock = vi.fn((props: unknown) => ({ props })); - -const useSupabaseMock = vi.fn(() => ({ - session: null, -})); - -vi.mock("next/navigation", () => ({ - redirect: (props: unknown) => redirectMock(props), - useRouter: vi.fn(() => ({ - redirect: redirectMock, - })), -})); - -vi.mock("@/lib/context/SupabaseProvider", () => ({ - useSupabase: () => useSupabaseMock(), -})); - -describe("ConfigPage", () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("should redirect to /login if session is null", () => { - render( - - - - ); - - expect(redirectMock).toHaveBeenCalledTimes(1); - expect(redirectMock).toHaveBeenCalledWith("/login"); - }); - - it("should render config page if user is connected", () => { - useSupabaseMock.mockReturnValue({ - // @ts-ignore we don't actually need parameters - session: { - user: {}, - }, - }); - - vi.mock("../components", () => ({ - ConfigForm: () => ConfirmFormMock(), - ConfigTitle: () => ConfirmTitleMock(), - ApiKeyConfig: () => ApiKeyConfig(), - })); - - render( - - - - ); - - expect(redirectMock).not.toHaveBeenCalled(); - expect(ConfirmTitleMock).toHaveBeenCalledTimes(1); - expect(ConfirmFormMock).toHaveBeenCalledTimes(1); - expect(ApiKeyConfig).toHaveBeenCalledTimes(1); - }); -}); diff --git a/frontend/app/config/components/ApiKeyConfig/ApiKeyConfig.tsx b/frontend/app/config/components/ApiKeyConfig/ApiKeyConfig.tsx deleted file mode 100644 index 776c47074..000000000 --- a/frontend/app/config/components/ApiKeyConfig/ApiKeyConfig.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import Button from "@/lib/components/ui/Button"; - -import { useApiKeyConfig } from "./hooks/useApiKeyConfig"; - -export const ApiKeyConfig = (): JSX.Element => { - const { - apiKey, - handleCopyClick, - - handleCreateClick, - } = useApiKeyConfig(); - - return ( - <> -
-

- API Key Config -

-
-
-
- {apiKey === "" && ( - - )} -
- {apiKey !== "" && ( -
- {apiKey} - -
- )} -
- - ); -}; diff --git a/frontend/app/config/components/ApiKeyConfig/__tests__/ApiKeyConfig.test.tsx b/frontend/app/config/components/ApiKeyConfig/__tests__/ApiKeyConfig.test.tsx deleted file mode 100644 index e720c558c..000000000 --- a/frontend/app/config/components/ApiKeyConfig/__tests__/ApiKeyConfig.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { fireEvent, render } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import { ApiKeyConfig } from "../ApiKeyConfig"; - -const handleCreateClickMock = vi.fn(() => ({})); -const handleCopyClickMock = vi.fn(() => ({})); - -const useApiKeyConfigMock = vi.fn(() => ({ - apiKey: "", - handleCreateClick: () => handleCreateClickMock(), - handleCopyClick: () => handleCopyClickMock(), -})); - -vi.mock("../hooks/useApiKeyConfig", () => ({ - useApiKeyConfig: () => useApiKeyConfigMock(), -})); - -describe("ApiKeyConfig", () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("should render ApiConfig Component", () => { - const { getByText } = render(); - expect(getByText("API Key Config")).toBeDefined(); - }); - - it("renders 'Create New Key' button when apiKey is empty", () => { - const { getByTestId } = render(); - - const createButton = getByTestId("create-new-key"); - expect(createButton).toBeDefined(); - - fireEvent.click(createButton); - expect(handleCreateClickMock).toHaveBeenCalledTimes(1); - expect(handleCreateClickMock).toHaveBeenCalledWith(); - }); - it('renders "Copy" button when apiKey is not empty', () => { - useApiKeyConfigMock.mockReturnValue({ - apiKey: "123456789", - handleCreateClick: () => handleCreateClickMock(), - handleCopyClick: () => handleCopyClickMock(), - }); - - const { getByTestId } = render(); - const copyButton = getByTestId("copy-api-key-button"); - expect(copyButton).toBeDefined(); - fireEvent.click(copyButton); - expect(handleCopyClickMock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts b/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts deleted file mode 100644 index ae755f11f..000000000 --- a/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { act, renderHook } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; - -import { useApiKeyConfig } from "../useApiKeyConfig"; - -const createApiKeyMock = vi.fn(() => "dummyApiKey"); -const trackMock = vi.fn((props: unknown) => ({ props })); - -const useAuthApiMock = vi.fn(() => ({ - createApiKey: () => createApiKeyMock(), -})); - -const useEventTrackingMock = vi.fn(() => ({ - track: (props: unknown) => trackMock(props), -})); - -vi.mock("@/lib/api/auth/useAuthApi", () => ({ - useAuthApi: () => useAuthApiMock(), -})); -vi.mock("@/services/analytics/useEventTracking", () => ({ - useEventTracking: () => useEventTrackingMock(), -})); - -describe("useApiKeyConfig", () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("should set the apiKey when handleCreateClick is called", async () => { - const { result } = renderHook(() => useApiKeyConfig()); - - await act(async () => { - await result.current.handleCreateClick(); - }); - - expect(createApiKeyMock).toHaveBeenCalledTimes(1); - expect(trackMock).toHaveBeenCalledWith("CREATE_API_KEY"); - expect(result.current.apiKey).toBe("dummyApiKey"); - }); - - it("should call copyToClipboard when handleCopyClick is called with a non-empty apiKey", () => { - vi.mock("react", async () => { - const actual = await vi.importActual("react"); - - return { - ...actual, - useState: () => ["dummyApiKey", vi.fn()], - }; - }); - //@ts-ignore - clipboard is not actually readonly - global.navigator.clipboard = { - writeText: vi.fn(), - }; - - const { result } = renderHook(() => useApiKeyConfig()); - - act(() => result.current.handleCopyClick()); - - expect(trackMock).toHaveBeenCalledTimes(1); - expect(trackMock).toHaveBeenCalledWith("COPY_API_KEY"); - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(global.navigator.clipboard.writeText).toHaveBeenCalledWith( - "dummyApiKey" - ); - }); -}); diff --git a/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts b/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts deleted file mode 100644 index 204c7f3e4..000000000 --- a/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useState } from "react"; - -import { useAuthApi } from "@/lib/api/auth/useAuthApi"; -import { useEventTracking } from "@/services/analytics/useEventTracking"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useApiKeyConfig = () => { - const [apiKey, setApiKey] = useState(""); - const [openAiApiKey, setOpenAiApiKey] = useState(""); - const { track } = useEventTracking(); - const { createApiKey } = useAuthApi(); - - const handleCreateClick = async () => { - try { - void track("CREATE_API_KEY"); - const createdApiKey = await createApiKey(); - setApiKey(createdApiKey); - } catch (error) { - console.error("Error creating API key: ", error); - } - }; - - const copyToClipboard = async (text: string) => { - try { - void track("COPY_API_KEY"); - await navigator.clipboard.writeText(text); - } catch (err) { - console.error("Failed to copy:", err); - } - }; - - const handleCopyClick = () => { - if (apiKey !== "") { - void copyToClipboard(apiKey); - } - }; - - return { - handleCreateClick, - apiKey, - handleCopyClick, - openAiApiKey, - setOpenAiApiKey, - }; -}; diff --git a/frontend/app/config/components/BackendConfig/BackendConfig.tsx b/frontend/app/config/components/BackendConfig/BackendConfig.tsx deleted file mode 100644 index dff3c3ee8..000000000 --- a/frontend/app/config/components/BackendConfig/BackendConfig.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable */ -"use client"; - -import { UseFormRegister } from "react-hook-form"; - -import Field from "@/lib/components/ui/Field"; -import { BrainConfig } from "@/lib/context/BrainConfigProvider/types"; - -interface BackendConfigProps { - register: UseFormRegister; -} - -export const BackendConfig = ({ - register, -}: BackendConfigProps): JSX.Element => { - return ( - <> -
-

- Backend config -

-
- - - - - - ); -}; diff --git a/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx b/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx deleted file mode 100644 index 481fe7499..000000000 --- a/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { render } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { BackendConfig } from "../BackendConfig"; - -const registerMock = vi.fn(() => void 0); - -describe("BackendConfig", () => { - it("renders the component with fields and labels", () => { - //@ts-expect-error we don't need registerMock to return all `register` keys - const { getByText } = render(); - - expect(getByText("Backend config")).toBeDefined(); - expect(getByText("Backend URL")).toBeDefined(); - expect(getByText("Supabase URL")).toBeDefined(); - expect(getByText("Supabase key")).toBeDefined(); - expect(getByText("Keep in local")).toBeDefined(); - expect(getByText("Keep in local")).toBeDefined(); - expect(registerMock).toHaveBeenCalledWith("backendUrl"); - expect(registerMock).toHaveBeenCalledWith("supabaseUrl"); - expect(registerMock).toHaveBeenCalledWith("supabaseKey"); - expect(registerMock).toHaveBeenCalledWith("backendUrl"); - }); -}); diff --git a/frontend/app/config/components/ConfigForm.tsx b/frontend/app/config/components/ConfigForm.tsx deleted file mode 100644 index fc499954e..000000000 --- a/frontend/app/config/components/ConfigForm.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable */ -"use client"; - -import { useRouter } from "next/navigation"; - -import Button from "@/lib/components/ui/Button"; - -import { useConfig } from "../hooks/useConfig"; -import { BackendConfig } from "./BackendConfig/BackendConfig"; -import { ModelConfig } from "./ModelConfig"; -import { UserAccountSection } from "./UserAccountSection"; - -export const ConfigForm = (): JSX.Element => { - const { - handleSubmit, - isDirty, - maxTokens, - openAiKey, - saveConfig, - register, - temperature, - model, - resetBrainConfig, - } = useConfig(); - - const router = useRouter(); - - const handleDoneClick = () => { - if (isDirty) { - saveConfig(); - } - router.back(); - }; - - return ( -
- - -
- - -
- - - ); -}; diff --git a/frontend/app/config/components/ConfigTitle.tsx b/frontend/app/config/components/ConfigTitle.tsx deleted file mode 100644 index d42fb08bf..000000000 --- a/frontend/app/config/components/ConfigTitle.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export const ConfigTitle = (): JSX.Element => { - return ( -
-

Configuration

-

- Here, you can choose your model, set your credentials... -

-
- ); -}; diff --git a/frontend/app/config/components/ModelConfig.tsx b/frontend/app/config/components/ModelConfig.tsx deleted file mode 100644 index 5a690f548..000000000 --- a/frontend/app/config/components/ModelConfig.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable */ -"use client"; - -import { UseFormRegister } from "react-hook-form"; - -import Field from "@/lib/components/ui/Field"; -import { - BrainConfig, - Model, - PaidModels, - anthropicModels, - models, - paidModels, -} from "@/lib/context/BrainConfigProvider/types"; -import { defineMaxTokens } from "@/lib/helpers/defineMexTokens"; - -interface ModelConfigProps { - register: UseFormRegister; - model: Model | PaidModels; - openAiKey: string | undefined; - temperature: number; - maxTokens: number; -} - -export const ModelConfig = ({ - register, - model, - openAiKey, - temperature, - maxTokens, -}: ModelConfigProps): JSX.Element => { - - - return ( - <> -
-

- Model config -

-
- -
- - -
- {(anthropicModels as readonly string[]).includes(model) && ( - - )} -
- - -
-
- - -
- - ); -}; diff --git a/frontend/app/config/components/UserAccountSection.tsx b/frontend/app/config/components/UserAccountSection.tsx deleted file mode 100644 index 7a0348798..000000000 --- a/frontend/app/config/components/UserAccountSection.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable */ -"use client"; - -import Link from "next/link"; - -import Button from "@/lib/components/ui/Button"; -import { useSupabase } from "@/lib/context/SupabaseProvider"; - -export const UserAccountSection = (): JSX.Element => { - const { session } = useSupabase(); - - if (session === null) { - return <>; - } - - return ( - <> -
-

- Your Account -

-
-
- - Signed In as: {session.user.email} - - - - - {/* TODO: add functionality to change password */} -
- - ); -}; diff --git a/frontend/app/config/components/index.tsx b/frontend/app/config/components/index.tsx deleted file mode 100644 index 234359958..000000000 --- a/frontend/app/config/components/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./ApiKeyConfig/ApiKeyConfig"; -export * from "./ConfigForm"; -export * from "./ConfigTitle"; diff --git a/frontend/app/config/hooks/useConfig.ts b/frontend/app/config/hooks/useConfig.ts deleted file mode 100644 index f24cc6b66..000000000 --- a/frontend/app/config/hooks/useConfig.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable */ -import { useEffect } from "react"; -import { useForm } from "react-hook-form"; - -import { useBrainConfig } from "@/lib/context/BrainConfigProvider/hooks/useBrainConfig"; -import { useToast } from "@/lib/hooks/useToast"; - -export const useConfig = () => { - const { config, updateConfig, resetConfig } = useBrainConfig(); - const { publish } = useToast(); - const { - register, - handleSubmit, - watch, - getValues, - reset, - formState: { isDirty }, - setError, - } = useForm({ - defaultValues: config, - }); - - const model = watch("model"); - const temperature = watch("temperature"); - const maxTokens = watch("maxTokens"); - const openAiKey = watch("openAiKey"); - - useEffect(() => { - reset(config); - }, [config, reset]); - - const saveConfig = () => { - const values = getValues(); - - if (!validateConfig()) { - return; - } - - updateConfig(values); - publish({ - text: "Config saved", - variant: "success", - }); - }; - - const resetBrainConfig = () => { - resetConfig(); - publish({ - text: "Config reset", - variant: "success", - }); - }; - - const openAiKeyPattern = /^sk-[a-zA-Z0-9]{45,50}$/; - - const validateConfig = (): boolean => { - const { openAiKey } = getValues(); - - const isKeyEmpty = openAiKey === "" || openAiKey === undefined; - if (isKeyEmpty || openAiKeyPattern.test(openAiKey)) { - return true; - } - - publish({ - text: "Invalid OpenAI Key", - variant: "danger", - }); - setError("openAiKey", { type: "pattern", message: "Invalid OpenAI Key" }); - - return false; - }; - - return { - handleSubmit, - saveConfig, - maxTokens, - openAiKey, - temperature, - isDirty, - register, - model, - resetBrainConfig, - }; -}; diff --git a/frontend/app/config/page.tsx b/frontend/app/config/page.tsx deleted file mode 100644 index ff533365d..000000000 --- a/frontend/app/config/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable */ -"use client"; - -import { useSupabase } from "@/lib/context/SupabaseProvider"; -import { redirectToLogin } from "@/lib/router/redirectToLogin"; -import { ApiKeyConfig, ConfigForm, ConfigTitle } from "./components"; - -// TODO: Use states instead of NEXTJS router to open and close modal -const ConfigPage = (): JSX.Element => { - const { session } = useSupabase(); - - if (session === null) { - redirectToLogin(); - } - - return ( -
-
- - - -
-
- ); -}; - -export default ConfigPage; diff --git a/frontend/lib/api/chat/chat.local.ts b/frontend/lib/api/chat/chat.local.ts new file mode 100644 index 000000000..d5cf34ae4 --- /dev/null +++ b/frontend/lib/api/chat/chat.local.ts @@ -0,0 +1,20 @@ +import { ChatConfig } from "@/lib/context/ChatProvider/types"; + +export const saveChatConfigInLocalStorage = ( + chatId: string, + chatConfig: ChatConfig +): void => { + localStorage.setItem(`chat-config-${chatId}`, JSON.stringify(chatConfig)); +}; + +export const getChatConfigFromLocalStorage = ( + chatId: string +): ChatConfig | undefined => { + const config = localStorage.getItem(`chat-config-${chatId}`); + + if (config === null) { + return undefined; + } + + return JSON.parse(config) as ChatConfig; +}; diff --git a/frontend/lib/components/AddBrainModal/hooks/useAddBrainModal.ts b/frontend/lib/components/AddBrainModal/hooks/useAddBrainModal.ts index 4dd25703a..eea6223e3 100644 --- a/frontend/lib/components/AddBrainModal/hooks/useAddBrainModal.ts +++ b/frontend/lib/components/AddBrainModal/hooks/useAddBrainModal.ts @@ -13,7 +13,7 @@ import { useToast } from "@/lib/hooks"; export const useAddBrainModal = () => { const [isPending, setIsPending] = useState(false); const { publish } = useToast(); - const { createBrain } = useBrainContext(); + const { createBrain, setActiveBrain } = useBrainContext(); const { setAsDefaultBrain } = useBrainApi(); const [isShareModalOpen, setIsShareModalOpen] = useState(false); const { config } = useBrainConfig(); @@ -56,15 +56,21 @@ export const useAddBrainModal = () => { temperature, }); - if (setDefault) { - if (createdBrainId === undefined) { - publish({ - variant: "danger", - text: "Error occurred while creating a brain", - }); + if (createdBrainId === undefined) { + publish({ + variant: "danger", + text: "Error occurred while creating a brain", + }); - return; - } + return; + } + + setActiveBrain({ + id: createdBrainId, + name, + }); + + if (setDefault) { await setAsDefaultBrain(createdBrainId); } diff --git a/frontend/lib/components/NavBar/components/NavItems/components/BrainManagementButton.tsx b/frontend/lib/components/NavBar/components/NavItems/components/BrainManagementButton.tsx index f262a6360..59c15a7f8 100644 --- a/frontend/lib/components/NavBar/components/NavItems/components/BrainManagementButton.tsx +++ b/frontend/lib/components/NavBar/components/NavItems/components/BrainManagementButton.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; -import { FaBrain } from "react-icons/fa"; import { MdSettings } from "react-icons/md"; +import Button from "@/lib/components/ui/Button"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; export const BrainManagementButton = (): JSX.Element => { @@ -9,10 +9,13 @@ export const BrainManagementButton = (): JSX.Element => { return ( - + ); }; diff --git a/frontend/lib/components/NavBar/components/NavItems/index.tsx b/frontend/lib/components/NavBar/components/NavItems/index.tsx index 31f4d822c..fd2d3b931 100644 --- a/frontend/lib/components/NavBar/components/NavItems/index.tsx +++ b/frontend/lib/components/NavBar/components/NavItems/index.tsx @@ -1,9 +1,8 @@ "use client"; import Link from "next/link"; import { Dispatch, HTMLAttributes, SetStateAction } from "react"; -import { MdPerson, MdSettings } from "react-icons/md"; +import { MdPerson } from "react-icons/md"; -import Button from "@/lib/components/ui/Button"; import { useSupabase } from "@/lib/context/SupabaseProvider"; import { cn } from "@/lib/utils"; @@ -63,15 +62,6 @@ export const NavItems = ({ - - - )} {!isUserLoggedIn && } diff --git a/frontend/lib/context/ChatProvider/types.ts b/frontend/lib/context/ChatProvider/types.ts index 16740f0d9..6bcc9d94a 100644 --- a/frontend/lib/context/ChatProvider/types.ts +++ b/frontend/lib/context/ChatProvider/types.ts @@ -1,5 +1,13 @@ import { ChatHistory } from "@/app/chat/[chatId]/types"; +import { Model } from "../BrainConfigProvider/types"; + +export type ChatConfig = { + model?: Model; + temperature?: number; + maxTokens?: number; +}; + export type ChatContextProps = { history: ChatHistory[]; setHistory: (history: ChatHistory[]) => void;