mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-09-11 14:36:35 +03:00
feat: 🎸 marketplace (#1657)
added explore button and removed unused feature openai key # Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate):
This commit is contained in:
parent
b062573f00
commit
d955e31f50
@ -6,12 +6,10 @@ from repository.files.upload_file import DocumentSerializable
|
||||
|
||||
|
||||
@shared_task
|
||||
def create_embedding_for_document(
|
||||
brain_id, doc_with_metadata, user_openai_api_key, file_sha1
|
||||
):
|
||||
def create_embedding_for_document(brain_id, doc_with_metadata, file_sha1):
|
||||
neurons = Neurons()
|
||||
doc = DocumentSerializable.from_json(doc_with_metadata)
|
||||
created_vector = neurons.create_vector(doc, user_openai_api_key)
|
||||
created_vector = neurons.create_vector(doc)
|
||||
database = get_supabase_db()
|
||||
database.set_file_sha_from_metadata(file_sha1)
|
||||
|
||||
|
@ -56,9 +56,7 @@ else:
|
||||
def process_file_and_notify(
|
||||
file_name: str,
|
||||
file_original_name: str,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
openai_api_key,
|
||||
notification_id=None,
|
||||
):
|
||||
supabase_client = get_supabase_client()
|
||||
@ -81,9 +79,7 @@ def process_file_and_notify(
|
||||
message = loop.run_until_complete(
|
||||
filter_file(
|
||||
file=file_instance,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=openai_api_key,
|
||||
original_file_name=file_original_name,
|
||||
)
|
||||
)
|
||||
@ -112,9 +108,7 @@ def process_file_and_notify(
|
||||
@celery.task(name="process_crawl_and_notify")
|
||||
def process_crawl_and_notify(
|
||||
crawl_website_url,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
openai_api_key,
|
||||
notification_id=None,
|
||||
):
|
||||
crawl_website = CrawlWebsite(url=crawl_website_url)
|
||||
@ -136,9 +130,7 @@ def process_crawl_and_notify(
|
||||
message = loop.run_until_complete(
|
||||
filter_file(
|
||||
file=file_instance,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=openai_api_key,
|
||||
original_file_name=crawl_website_url,
|
||||
)
|
||||
)
|
||||
@ -147,9 +139,7 @@ def process_crawl_and_notify(
|
||||
message = loop.run_until_complete(
|
||||
process_github(
|
||||
repo=crawl_website.url,
|
||||
enable_summarization="false",
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=openai_api_key,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -63,7 +63,6 @@ class QABaseBrainPicking(BaseModel):
|
||||
chat_id: str = None # pyright: ignore reportPrivateUsage=none
|
||||
brain_id: str = None # pyright: ignore reportPrivateUsage=none
|
||||
max_tokens: int = 256
|
||||
user_openai_api_key: str = None # pyright: ignore reportPrivateUsage=none
|
||||
streaming: bool = False
|
||||
|
||||
openai_api_key: str = None # pyright: ignore reportPrivateUsage=none
|
||||
@ -71,13 +70,6 @@ class QABaseBrainPicking(BaseModel):
|
||||
AsyncIteratorCallbackHandler
|
||||
] = None # pyright: ignore reportPrivateUsage=none
|
||||
|
||||
def _determine_api_key(self, openai_api_key, user_openai_api_key):
|
||||
"""If user provided an API key, use it."""
|
||||
if user_openai_api_key is not None:
|
||||
return user_openai_api_key
|
||||
else:
|
||||
return openai_api_key
|
||||
|
||||
def _determine_streaming(self, model: str, streaming: bool) -> bool:
|
||||
"""If the model name allows for streaming and streaming is declared, set streaming to True."""
|
||||
return streaming
|
||||
|
@ -32,20 +32,12 @@ class HeadlessQA(BaseModel):
|
||||
model: str
|
||||
temperature: float = 0.0
|
||||
max_tokens: int = 2000
|
||||
user_openai_api_key: Optional[str] = None
|
||||
openai_api_key: Optional[str] = None
|
||||
streaming: bool = False
|
||||
chat_id: str
|
||||
callbacks: Optional[List[AsyncIteratorCallbackHandler]] = None
|
||||
prompt_id: Optional[UUID] = None
|
||||
|
||||
def _determine_api_key(self, openai_api_key, user_openai_api_key):
|
||||
"""If user provided an API key, use it."""
|
||||
if user_openai_api_key is not None:
|
||||
return user_openai_api_key
|
||||
else:
|
||||
return openai_api_key
|
||||
|
||||
def _determine_streaming(self, streaming: bool) -> bool:
|
||||
"""If the model name allows for streaming and streaming is declared, set streaming to True."""
|
||||
return streaming
|
||||
@ -61,11 +53,6 @@ class HeadlessQA(BaseModel):
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
print("in HeadlessQA")
|
||||
|
||||
self.openai_api_key = self._determine_api_key(
|
||||
self.openai_api_key, self.user_openai_api_key
|
||||
)
|
||||
self.streaming = self._determine_streaming(self.streaming)
|
||||
self.callbacks = self._determine_callback_array(self.streaming)
|
||||
|
||||
|
@ -20,7 +20,6 @@ class BrainEntity(BaseModel):
|
||||
temperature: Optional[float]
|
||||
model: Optional[str]
|
||||
max_tokens: Optional[int]
|
||||
openai_api_key: Optional[str]
|
||||
status: Optional[str]
|
||||
prompt_id: Optional[UUID]
|
||||
last_update: str
|
||||
|
@ -19,7 +19,6 @@ class Brain(BaseModel):
|
||||
model: Optional[str] = None
|
||||
temperature: Optional[float] = 0.0
|
||||
max_tokens: Optional[int] = 256
|
||||
openai_api_key: Optional[str] = None
|
||||
files: List[Any] = []
|
||||
prompt_id: Optional[UUID] = None
|
||||
|
||||
|
@ -25,7 +25,6 @@ class CreateBrainProperties(BaseModel, extra=Extra.forbid):
|
||||
model: Optional[str]
|
||||
temperature: Optional[float] = 0.0
|
||||
max_tokens: Optional[int] = 256
|
||||
openai_api_key: Optional[str] = None
|
||||
prompt_id: Optional[UUID] = None
|
||||
brain_type: Optional[BrainType] = BrainType.DOC
|
||||
brain_definition: Optional[CreateApiBrainDefinition]
|
||||
@ -44,7 +43,6 @@ class BrainUpdatableProperties(BaseModel):
|
||||
temperature: Optional[float]
|
||||
model: Optional[str]
|
||||
max_tokens: Optional[int]
|
||||
openai_api_key: Optional[str]
|
||||
status: Optional[str]
|
||||
prompt_id: Optional[UUID]
|
||||
|
||||
|
@ -11,24 +11,27 @@ class BrainRateLimiting(BaseSettings):
|
||||
|
||||
class BrainSettings(BaseSettings):
|
||||
openai_api_key: str
|
||||
anthropic_api_key: str
|
||||
supabase_url: str
|
||||
supabase_service_key: str
|
||||
pg_database_url: str = "not implemented"
|
||||
resend_api_key: str = "null"
|
||||
resend_email_address: str = "brain@mail.quivr.app"
|
||||
|
||||
|
||||
class ContactsSettings(BaseSettings):
|
||||
resend_contact_sales_from: str = "null"
|
||||
resend_contact_sales_to: str = "null"
|
||||
|
||||
|
||||
class LLMSettings(BaseSettings):
|
||||
private: bool = False
|
||||
model_path: str = "./local_models/ggml-gpt4all-j-v1.3-groovy.bin"
|
||||
|
||||
|
||||
class ResendSettings(BaseSettings):
|
||||
resend_api_key: str = "null"
|
||||
|
||||
|
||||
def get_supabase_client() -> Client:
|
||||
settings = BrainSettings() # pyright: ignore reportPrivateUsage=none
|
||||
supabase_client: Client = create_client(
|
||||
|
@ -33,7 +33,6 @@ async def get_user_endpoint(
|
||||
user_daily_usage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
user_settings = user_daily_usage.get_user_settings()
|
||||
max_brain_size = user_settings.get("max_brain_size", 1000000000)
|
||||
|
@ -7,4 +7,3 @@ from pydantic import BaseModel
|
||||
class UserIdentity(BaseModel):
|
||||
id: UUID
|
||||
email: Optional[str] = None
|
||||
openai_api_key: Optional[str] = None
|
||||
|
@ -2,7 +2,6 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
|
||||
from langchain.embeddings.openai import OpenAIEmbeddings
|
||||
from logger import get_logger
|
||||
from models.settings import get_documents_vector_store, get_embeddings, get_supabase_db
|
||||
from pydantic import BaseModel
|
||||
@ -12,14 +11,11 @@ logger = get_logger(__name__)
|
||||
|
||||
# TODO: Create interface for embeddings and implement it for Supabase and OpenAI (current Quivr)
|
||||
class Neurons(BaseModel):
|
||||
def create_vector(self, doc, user_openai_api_key=None):
|
||||
def create_vector(self, doc):
|
||||
documents_vector_store = get_documents_vector_store()
|
||||
logger.info("Creating vector for document")
|
||||
logger.info(f"Document: {doc}")
|
||||
if user_openai_api_key:
|
||||
documents_vector_store._embedding = OpenAIEmbeddings(
|
||||
openai_api_key=user_openai_api_key
|
||||
) # pyright: ignore reportPrivateUsage=none
|
||||
|
||||
try:
|
||||
sids = documents_vector_store.add_documents([doc])
|
||||
if sids and len(sids) > 0:
|
||||
|
@ -11,9 +11,7 @@ from packages.files.file import compute_sha1_from_content
|
||||
|
||||
async def process_audio(
|
||||
file: File,
|
||||
enable_summarization: bool,
|
||||
user,
|
||||
user_openai_api_key,
|
||||
):
|
||||
temp_filename = None
|
||||
file_sha = ""
|
||||
@ -21,11 +19,6 @@ async def process_audio(
|
||||
file_meta_name = f"audiotranscript_{dateshort}.txt"
|
||||
documents_vector_store = get_documents_vector_store()
|
||||
|
||||
# use this for whisper
|
||||
os.environ.get("OPENAI_API_KEY")
|
||||
if user_openai_api_key:
|
||||
pass
|
||||
|
||||
try:
|
||||
upload_file = file.file
|
||||
with tempfile.NamedTemporaryFile(
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
async def process_python(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
async def process_python(file: File, brain_id):
|
||||
return await process_file(
|
||||
file=file,
|
||||
loader_class=PythonLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -8,9 +8,7 @@ from repository.files.upload_file import DocumentSerializable
|
||||
async def process_file(
|
||||
file: File,
|
||||
loader_class,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
user_openai_api_key,
|
||||
):
|
||||
dateshort = time.strftime("%Y%m%d")
|
||||
|
||||
@ -24,14 +22,13 @@ async def process_file(
|
||||
"chunk_size": file.chunk_size,
|
||||
"chunk_overlap": file.chunk_overlap,
|
||||
"date": dateshort,
|
||||
"summarization": "true" if enable_summarization else "false",
|
||||
}
|
||||
doc_with_metadata = DocumentSerializable(
|
||||
page_content=doc.page_content, metadata=metadata
|
||||
)
|
||||
|
||||
create_embedding_for_document.delay(
|
||||
brain_id, doc_with_metadata.to_json(), user_openai_api_key, file.file_sha1
|
||||
brain_id, doc_with_metadata.to_json(), file.file_sha1
|
||||
)
|
||||
|
||||
return "Hello World!"
|
||||
|
@ -6,14 +6,10 @@ from .common import process_file
|
||||
|
||||
def process_csv(
|
||||
file: File,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
user_openai_api_key,
|
||||
):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=CSVLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_docx(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_docx(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=Docx2txtLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_epub(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_epub(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredEPubLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -11,9 +11,7 @@ from packages.files.file import compute_sha1_from_content
|
||||
|
||||
async def process_github(
|
||||
repo,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
user_openai_api_key,
|
||||
):
|
||||
random_dir_name = os.urandom(16).hex()
|
||||
dateshort = time.strftime("%Y%m%d")
|
||||
@ -54,7 +52,6 @@ async def process_github(
|
||||
"chunk_size": chunk_size,
|
||||
"chunk_overlap": chunk_overlap,
|
||||
"date": dateshort,
|
||||
"summarization": "true" if enable_summarization else "false",
|
||||
}
|
||||
doc_with_metadata = Document(page_content=doc.page_content, metadata=metadata)
|
||||
|
||||
@ -66,9 +63,7 @@ async def process_github(
|
||||
|
||||
if not file_exists:
|
||||
neurons = Neurons()
|
||||
created_vector = neurons.create_vector(
|
||||
doc_with_metadata, user_openai_api_key
|
||||
)
|
||||
created_vector = neurons.create_vector(doc_with_metadata)
|
||||
|
||||
file_exists_in_brain = file.file_already_exists_in_brain(brain_id)
|
||||
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_html(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_html(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredHTMLLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_markdown(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_markdown(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredMarkdownLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_ipnyb(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_ipnyb(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=NotebookLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_odt(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_odt(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredPDFLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_pdf(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_pdf(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredPDFLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,9 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
def process_powerpoint(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
def process_powerpoint(file: File, brain_id):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredPowerPointLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -6,14 +6,10 @@ from .common import process_file
|
||||
|
||||
def process_telegram(
|
||||
file: File,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
user_openai_api_key,
|
||||
):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=TelegramChatFileLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -4,11 +4,12 @@ from models import File
|
||||
from .common import process_file
|
||||
|
||||
|
||||
async def process_txt(file: File, enable_summarization, brain_id, user_openai_api_key):
|
||||
async def process_txt(
|
||||
file: File,
|
||||
brain_id,
|
||||
):
|
||||
return await process_file(
|
||||
file=file,
|
||||
loader_class=TextLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -6,14 +6,10 @@ from .common import process_file
|
||||
|
||||
def process_xlsx(
|
||||
file: File,
|
||||
enable_summarization,
|
||||
brain_id,
|
||||
user_openai_api_key,
|
||||
):
|
||||
return process_file(
|
||||
file=file,
|
||||
loader_class=UnstructuredExcelLoader,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
)
|
||||
|
@ -49,9 +49,7 @@ def create_response(message, type):
|
||||
# TODO: Move filter_file to a file service to avoid circular imports from models/files.py for File class
|
||||
async def filter_file(
|
||||
file,
|
||||
enable_summarization: bool,
|
||||
brain_id,
|
||||
openai_api_key,
|
||||
original_file_name=None,
|
||||
):
|
||||
await file.compute_file_sha1()
|
||||
@ -85,9 +83,7 @@ async def filter_file(
|
||||
try:
|
||||
await file_processors[file.file_extension](
|
||||
file=file,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=openai_api_key,
|
||||
)
|
||||
return create_response(
|
||||
f"✅ {using_file_name} has been uploaded to brain {brain.name}.", # pyright: ignore reportPrivateUsage=none
|
||||
|
@ -89,7 +89,6 @@ async def create_new_brain(
|
||||
user_usage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
user_settings = user_usage.get_user_settings()
|
||||
|
||||
|
@ -39,7 +39,6 @@ class BrainfulChat(ChatInterface):
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
user_openai_api_key,
|
||||
streaming,
|
||||
prompt_id,
|
||||
user_id,
|
||||
@ -59,7 +58,6 @@ class BrainfulChat(ChatInterface):
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
streaming=streaming,
|
||||
prompt_id=prompt_id,
|
||||
)
|
||||
@ -70,7 +68,6 @@ class BrainfulChat(ChatInterface):
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature,
|
||||
brain_id=brain_id,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
streaming=streaming,
|
||||
prompt_id=prompt_id,
|
||||
user_id=user_id,
|
||||
|
@ -20,7 +20,6 @@ class BrainlessChat(ChatInterface):
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
user_openai_api_key,
|
||||
streaming,
|
||||
prompt_id,
|
||||
user_id,
|
||||
@ -30,7 +29,6 @@ class BrainlessChat(ChatInterface):
|
||||
model=model,
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature,
|
||||
user_openai_api_key=user_openai_api_key,
|
||||
streaming=streaming,
|
||||
prompt_id=prompt_id,
|
||||
)
|
||||
|
@ -18,7 +18,6 @@ class ChatInterface(ABC):
|
||||
model,
|
||||
max_tokens,
|
||||
temperature,
|
||||
user_openai_api_key,
|
||||
streaming,
|
||||
prompt_id,
|
||||
user_id,
|
||||
|
@ -38,21 +38,18 @@ def delete_chat_from_db(supabase_db: SupabaseDB, chat_id):
|
||||
def check_user_requests_limit(
|
||||
user: UserIdentity,
|
||||
):
|
||||
userDailyUsage = UserUsage(
|
||||
id=user.id, email=user.email, openai_api_key=user.openai_api_key
|
||||
)
|
||||
userDailyUsage = UserUsage(id=user.id, email=user.email)
|
||||
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
|
||||
date = time.strftime("%Y%m%d")
|
||||
userDailyUsage.handle_increment_user_request_count(date)
|
||||
|
||||
if user.openai_api_key is None:
|
||||
daily_chat_credit = userSettings.get("daily_chat_credit", 0)
|
||||
if int(userDailyUsage.daily_requests_count) >= int(daily_chat_credit):
|
||||
raise HTTPException(
|
||||
status_code=429, # pyright: ignore reportPrivateUsage=none
|
||||
detail="You have reached the maximum number of requests for today.", # pyright: ignore reportPrivateUsage=none
|
||||
)
|
||||
daily_chat_credit = userSettings.get("daily_chat_credit", 0)
|
||||
if int(userDailyUsage.daily_requests_count) >= int(daily_chat_credit):
|
||||
raise HTTPException(
|
||||
status_code=429, # pyright: ignore reportPrivateUsage=none
|
||||
detail="You have reached the maximum number of requests for today.", # pyright: ignore reportPrivateUsage=none
|
||||
)
|
||||
else:
|
||||
pass
|
||||
|
@ -133,22 +133,16 @@ async def create_question_handler(
|
||||
|
||||
chat_instance.validate_authorization(user_id=current_user.id, brain_id=brain_id)
|
||||
|
||||
current_user.openai_api_key = request.headers.get("Openai-Api-Key")
|
||||
brain = Brain(id=brain_id)
|
||||
brain_details: BrainEntity | None = None
|
||||
|
||||
userDailyUsage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
is_model_ok = (brain_details or chat_question).model in userSettings.get("models", ["gpt-3.5-turbo"]) # type: ignore
|
||||
|
||||
if not current_user.openai_api_key:
|
||||
current_user.openai_api_key = chat_instance.get_openai_api_key(
|
||||
brain_id=brain_id, user_id=current_user.id
|
||||
)
|
||||
# Retrieve chat model (temperature, max_tokens, model)
|
||||
if (
|
||||
not chat_question.model
|
||||
@ -171,7 +165,6 @@ async def create_question_handler(
|
||||
max_tokens=chat_question.max_tokens,
|
||||
temperature=chat_question.temperature,
|
||||
brain_id=str(brain_id),
|
||||
user_openai_api_key=current_user.openai_api_key, # pyright: ignore reportPrivateUsage=none
|
||||
streaming=False,
|
||||
prompt_id=chat_question.prompt_id,
|
||||
user_id=current_user.id,
|
||||
@ -207,22 +200,15 @@ async def create_stream_question_handler(
|
||||
chat_instance.validate_authorization(user_id=current_user.id, brain_id=brain_id)
|
||||
|
||||
# Retrieve user's OpenAI API key
|
||||
current_user.openai_api_key = request.headers.get("Openai-Api-Key")
|
||||
brain = Brain(id=brain_id)
|
||||
brain_details: BrainEntity | None = None
|
||||
userDailyUsage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
|
||||
if not current_user.openai_api_key:
|
||||
current_user.openai_api_key = chat_instance.get_openai_api_key(
|
||||
brain_id=brain_id, user_id=current_user.id
|
||||
)
|
||||
|
||||
# Retrieve chat model (temperature, max_tokens, model)
|
||||
if (
|
||||
not chat_question.model
|
||||
@ -247,7 +233,6 @@ async def create_stream_question_handler(
|
||||
model=(brain_details or chat_question).model if is_model_ok else "gpt-3.5-turbo", # type: ignore
|
||||
max_tokens=(brain_details or chat_question).max_tokens, # type: ignore
|
||||
temperature=(brain_details or chat_question).temperature, # type: ignore
|
||||
user_openai_api_key=current_user.openai_api_key, # pyright: ignore reportPrivateUsage=none
|
||||
streaming=True,
|
||||
prompt_id=chat_question.prompt_id,
|
||||
brain_id=str(brain_id),
|
||||
|
@ -30,7 +30,6 @@ async def crawl_endpoint(
|
||||
crawl_website: CrawlWebsite,
|
||||
brain_id: UUID = Query(..., description="The ID of the brain"),
|
||||
chat_id: Optional[UUID] = Query(None, description="The ID of the chat"),
|
||||
enable_summarization: bool = False,
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
):
|
||||
"""
|
||||
@ -43,7 +42,6 @@ async def crawl_endpoint(
|
||||
userDailyUsage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
|
||||
@ -81,9 +79,7 @@ async def crawl_endpoint(
|
||||
|
||||
process_crawl_and_notify.delay(
|
||||
crawl_website_url=crawl_website.url,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=request.headers.get("Openai-Api-Key", None),
|
||||
notification_id=crawl_notification.id,
|
||||
)
|
||||
|
||||
|
@ -11,9 +11,7 @@ from models.databases.supabase.knowledge import CreateKnowledgeProperties
|
||||
from models.databases.supabase.notifications import CreateNotificationProperties
|
||||
from models.notifications import NotificationsStatusEnum
|
||||
from modules.user.entity.user_identity import UserIdentity
|
||||
from modules.user.repository import get_user_identity
|
||||
from packages.files.file import convert_bytes, get_file_size
|
||||
from repository.brain import get_brain_details
|
||||
from repository.files.upload_file import upload_file_storage
|
||||
from repository.knowledge.add_knowledge import add_knowledge
|
||||
from repository.notification.add_notification import add_notification
|
||||
@ -37,7 +35,6 @@ async def upload_file(
|
||||
uploadFile: UploadFile,
|
||||
brain_id: UUID = Query(..., description="The ID of the brain"),
|
||||
chat_id: Optional[UUID] = Query(None, description="The ID of the chat"),
|
||||
enable_summarization: bool = False,
|
||||
current_user: UserIdentity = Depends(get_current_user),
|
||||
):
|
||||
validate_brain_authorization(
|
||||
@ -47,7 +44,6 @@ async def upload_file(
|
||||
userDailyUsage = UserUsage(
|
||||
id=current_user.id,
|
||||
email=current_user.email,
|
||||
openai_api_key=current_user.openai_api_key,
|
||||
)
|
||||
userSettings = userDailyUsage.get_user_settings()
|
||||
|
||||
@ -72,13 +68,6 @@ async def upload_file(
|
||||
status=NotificationsStatusEnum.Pending,
|
||||
)
|
||||
)
|
||||
openai_api_key = request.headers.get("Openai-Api-Key", None)
|
||||
if openai_api_key is None:
|
||||
brain_details = get_brain_details(brain_id)
|
||||
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
|
||||
|
||||
file_content = await uploadFile.read()
|
||||
filename_with_brain_id = str(brain_id) + "/" + str(uploadFile.filename)
|
||||
@ -112,9 +101,7 @@ async def upload_file(
|
||||
process_file_and_notify.delay(
|
||||
file_name=filename_with_brain_id,
|
||||
file_original_name=uploadFile.filename,
|
||||
enable_summarization=enable_summarization,
|
||||
brain_id=brain_id,
|
||||
openai_api_key=openai_api_key,
|
||||
notification_id=upload_notification.id if upload_notification else None,
|
||||
)
|
||||
return {"message": "File processing has started."}
|
||||
|
@ -32,4 +32,3 @@ def test_get_user_identity(client, api_key):
|
||||
print(user_identity)
|
||||
assert "id" in user_identity
|
||||
assert "email" in user_identity
|
||||
assert "openai_api_key" in user_identity
|
||||
|
@ -2,6 +2,7 @@ version: "3"
|
||||
|
||||
services:
|
||||
frontend:
|
||||
image: frontend-dev
|
||||
env_file:
|
||||
- ./frontend/.env
|
||||
build:
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { UUID } from "crypto";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { defineMaxTokens } from "@/lib/helpers/defineMaxTokens";
|
||||
import { SaveButton } from "@/shared/SaveButton";
|
||||
|
||||
@ -15,20 +14,13 @@ type ModelSelectionProps = {
|
||||
};
|
||||
|
||||
export const ModelSelection = (props: ModelSelectionProps): JSX.Element => {
|
||||
const { model, maxTokens, temperature, register } = useBrainFormState();
|
||||
const { model, maxTokens, register } = useBrainFormState();
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
const { handleSubmit, hasEditRights, accessibleModels } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
label={t("openAiKeyLabel", { ns: "config" })}
|
||||
placeholder={t("openAiKeyPlaceholder", { ns: "config" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
disabled={!hasEditRights}
|
||||
{...register("openAiKey")}
|
||||
/>
|
||||
|
||||
<fieldset className="w-full flex flex-col mt-2">
|
||||
<label className="flex-1 text-sm" htmlFor="model">
|
||||
{t("modelLabel", { ns: "config" })}
|
||||
@ -50,21 +42,6 @@ export const ModelSelection = (props: ModelSelectionProps): JSX.Element => {
|
||||
))}
|
||||
</select>
|
||||
</fieldset>
|
||||
<fieldset className="w-full flex mt-4">
|
||||
<label className="flex-1" htmlFor="temp">
|
||||
{t("temperature", { ns: "config" })}: {temperature}
|
||||
</label>
|
||||
<input
|
||||
id="temp"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={temperature}
|
||||
disabled={!hasEditRights}
|
||||
{...register("temperature")}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="w-full flex mt-4">
|
||||
<label className="flex-1" htmlFor="tokens">
|
||||
{t("maxTokens", { ns: "config" })}: {maxTokens}
|
||||
|
@ -54,10 +54,7 @@ export const useBrainFormState = () => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (brainKey === "openai_api_key") {
|
||||
setValue("openAiKey", brain["openai_api_key"] ?? "");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// @ts-expect-error bad type inference from typescript
|
||||
// eslint-disable-next-line
|
||||
|
@ -105,7 +105,6 @@ export const usePrompt = (props: UsePromptProps) => {
|
||||
const {
|
||||
prompt,
|
||||
maxTokens: max_tokens,
|
||||
openAiKey: openai_api_key,
|
||||
...otherConfigs
|
||||
} = getValues();
|
||||
|
||||
@ -138,7 +137,6 @@ export const usePrompt = (props: UsePromptProps) => {
|
||||
await updateBrain(brainId, {
|
||||
...otherConfigs,
|
||||
max_tokens,
|
||||
openai_api_key,
|
||||
});
|
||||
refetchBrain();
|
||||
} else {
|
||||
@ -146,7 +144,6 @@ export const usePrompt = (props: UsePromptProps) => {
|
||||
updateBrain(brainId, {
|
||||
...otherConfigs,
|
||||
max_tokens,
|
||||
openai_api_key,
|
||||
}),
|
||||
promptHandler(),
|
||||
]);
|
||||
|
@ -13,7 +13,6 @@ import { useUserData } from "@/lib/hooks/useUserData";
|
||||
|
||||
import { useBrainFormState } from "./useBrainFormState";
|
||||
import { checkBrainName } from "../utils/checkBrainName";
|
||||
import { checkOpenAiKey } from "../utils/checkOpenAiKey";
|
||||
|
||||
type UseSettingsTabProps = {
|
||||
brainId: UUID;
|
||||
@ -99,10 +98,9 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
if (!hasChanges && checkDirty) {
|
||||
return;
|
||||
}
|
||||
const { name, openAiKey: openai_api_key } = getValues();
|
||||
const { name} = getValues();
|
||||
|
||||
checkBrainName(name, publish, t);
|
||||
await checkOpenAiKey(openai_api_key, publish, t);
|
||||
|
||||
try {
|
||||
setIsUpdating(true);
|
||||
@ -111,7 +109,6 @@ export const useSettingsTab = ({ brainId }: UseSettingsTabProps) => {
|
||||
await updateBrain(brainId, {
|
||||
...otherConfigs,
|
||||
max_tokens,
|
||||
openai_api_key,
|
||||
prompt_id:
|
||||
otherConfigs["prompt_id"] !== ""
|
||||
? otherConfigs["prompt_id"]
|
||||
|
@ -1,26 +0,0 @@
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
import { ToastData } from "@/lib/components/ui/Toast/domain/types";
|
||||
|
||||
import { validateOpenAIKey } from "./validateOpenAIKey";
|
||||
|
||||
export const checkOpenAiKey = async (
|
||||
openai_api_key: string | undefined,
|
||||
publish: (toast: ToastData) => void,
|
||||
t: TFunction<["translation", "brain", "config"]>
|
||||
): Promise<void> => {
|
||||
if (
|
||||
openai_api_key !== undefined &&
|
||||
openai_api_key !== "" &&
|
||||
!(await validateOpenAIKey(
|
||||
openai_api_key,
|
||||
{
|
||||
badApiKeyError: t("incorrectApiKey", { ns: "config" }),
|
||||
invalidApiKeyError: t("invalidApiKeyError", { ns: "config" }),
|
||||
},
|
||||
publish
|
||||
))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { ToastData } from "@/lib/components/ui/Toast/domain/types";
|
||||
import { getAxiosErrorParams } from "@/lib/helpers/getAxiosErrorParams";
|
||||
|
||||
export const getOpenAIKeyValidationStatusCode = async (
|
||||
key: string
|
||||
): Promise<number> => {
|
||||
const url = "https://api.openai.com/v1/chat/completions";
|
||||
const headers = {
|
||||
Authorization: `Bearer ${key}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const data = JSON.stringify({
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Hello!",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await axios.post(url, data, { headers });
|
||||
|
||||
return 200;
|
||||
} catch (error) {
|
||||
return getAxiosErrorParams(error)?.status ?? 400;
|
||||
}
|
||||
};
|
||||
|
||||
type ErrorMessages = {
|
||||
badApiKeyError: string;
|
||||
invalidApiKeyError: string;
|
||||
};
|
||||
|
||||
export const validateOpenAIKey = async (
|
||||
openai_api_key: string | undefined,
|
||||
errorMessages: ErrorMessages,
|
||||
publish: (toast: ToastData) => void
|
||||
): Promise<boolean> => {
|
||||
if (openai_api_key !== undefined) {
|
||||
const keyValidationStatusCode = await getOpenAIKeyValidationStatusCode(
|
||||
openai_api_key
|
||||
);
|
||||
|
||||
if (keyValidationStatusCode !== 200) {
|
||||
if (keyValidationStatusCode === 401) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: errorMessages.badApiKeyError,
|
||||
});
|
||||
}
|
||||
|
||||
if (keyValidationStatusCode === 429) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: errorMessages.invalidApiKeyError,
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
@ -13,7 +13,6 @@ export const ConfigModal = (): JSX.Element => {
|
||||
isConfigModalOpen,
|
||||
setIsConfigModalOpen,
|
||||
register,
|
||||
temperature,
|
||||
maxTokens,
|
||||
model,
|
||||
accessibleModels,
|
||||
@ -53,21 +52,6 @@ export const ConfigModal = (): JSX.Element => {
|
||||
))}
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset className="w-full flex mt-4">
|
||||
<label className="flex-1" htmlFor="temp">
|
||||
Temperature: {temperature}
|
||||
</label>
|
||||
<input
|
||||
id="temp"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={temperature}
|
||||
{...register("temperature")}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset className="w-full flex mt-4">
|
||||
<label className="flex-1" htmlFor="tokens">
|
||||
Max tokens: {maxTokens}
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
getChatsConfigFromLocalStorage,
|
||||
saveChatsConfigInLocalStorage,
|
||||
} from "@/lib/api/chat/chat.local";
|
||||
import { USER_DATA_KEY, USER_IDENTITY_DATA_KEY } from "@/lib/api/user/config";
|
||||
import { USER_DATA_KEY } from "@/lib/api/user/config";
|
||||
import { useUserApi } from "@/lib/api/user/useUserApi";
|
||||
import { defaultBrainConfig } from "@/lib/config/defaultBrainConfig";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
@ -22,16 +22,13 @@ export const useConfigModal = () => {
|
||||
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
|
||||
const { getBrain } = useBrainApi();
|
||||
const { currentBrainId } = useBrainContext();
|
||||
const { getUser, getUserIdentity } = useUserApi();
|
||||
const { getUser } = useUserApi();
|
||||
|
||||
const { data: userData } = useQuery({
|
||||
queryKey: [USER_DATA_KEY],
|
||||
queryFn: getUser,
|
||||
});
|
||||
const { data: userIdentityData } = useQuery({
|
||||
queryKey: [USER_IDENTITY_DATA_KEY],
|
||||
queryFn: getUserIdentity,
|
||||
});
|
||||
|
||||
|
||||
const { register, watch, setValue } = useForm<ChatConfig>({
|
||||
defaultValues: {
|
||||
@ -46,7 +43,6 @@ export const useConfigModal = () => {
|
||||
const maxTokens = watch("maxTokens");
|
||||
|
||||
const accessibleModels = getAccessibleModels({
|
||||
openAiKey: userIdentityData?.openai_api_key,
|
||||
userData,
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,7 @@ export const ChatsList = (): JSX.Element => {
|
||||
const { shouldDisplayWelcomeChat } = useOnboarding();
|
||||
|
||||
return (
|
||||
<Sidebar showButtons={["myBrains", "upgradeToPlus", "user"]}>
|
||||
<Sidebar showButtons={["myBrains","marketplace", "upgradeToPlus", "user"]}>
|
||||
<div className="flex flex-col flex-1 h-full" data-testid="chats-list">
|
||||
<div className="pt-2">
|
||||
<NewChatButton />
|
||||
|
@ -2,11 +2,10 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaCopy, FaInfoCircle } from "react-icons/fa";
|
||||
import { FaCopy } from "react-icons/fa";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import copyToClipboard from "@/lib/helpers/copyToClipboard";
|
||||
|
||||
import { useApiKeyConfig } from "./hooks/useApiKeyConfig";
|
||||
|
||||
@ -15,13 +14,7 @@ export const ApiKeyConfig = (): JSX.Element => {
|
||||
apiKey,
|
||||
handleCopyClick,
|
||||
handleCreateClick,
|
||||
openAiApiKey,
|
||||
setOpenAiApiKey,
|
||||
changeOpenAiApiKey,
|
||||
changeOpenAiApiKeyRequestPending,
|
||||
userIdentity,
|
||||
removeOpenAiApiKey,
|
||||
hasOpenAiApiKey,
|
||||
|
||||
} = useApiKeyConfig();
|
||||
const { t } = useTranslation(["config"]);
|
||||
|
||||
@ -47,71 +40,7 @@ export const ApiKeyConfig = (): JSX.Element => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr className="my-8" />
|
||||
|
||||
<div>
|
||||
<h3 className="font-semibold mb-2">OpenAI {t("apiKey")}</h3>
|
||||
<form
|
||||
className="mb-4"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
void changeOpenAiApiKey();
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Field
|
||||
name="openAiApiKey"
|
||||
placeholder="Open AI Key"
|
||||
className="w-full"
|
||||
value={openAiApiKey ?? ""}
|
||||
data-testid="open-ai-api-key-input"
|
||||
onChange={(e) => setOpenAiApiKey(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
hidden={!hasOpenAiApiKey}
|
||||
data-testid="copy-openai-api-key-button"
|
||||
onClick={() => void copyToClipboard(openAiApiKey)}
|
||||
type="button"
|
||||
>
|
||||
<FaCopy />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-row justify-between">
|
||||
{hasOpenAiApiKey && (
|
||||
<Button
|
||||
isLoading={changeOpenAiApiKeyRequestPending}
|
||||
variant="secondary"
|
||||
onClick={() => void removeOpenAiApiKey()}
|
||||
>
|
||||
Remove Key
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
data-testid="save-open-ai-api-key"
|
||||
isLoading={changeOpenAiApiKeyRequestPending}
|
||||
disabled={openAiApiKey === userIdentity?.openai_api_key}
|
||||
>
|
||||
Save Key
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="flex space-x-2 bg-sky-100 dark:bg-gray-900 border border-sky-200 dark:border-gray-700 px-4 py-3 rounded relative max-w-md">
|
||||
<div className="text-xl font-semibold text-sky-600">
|
||||
<FaInfoCircle />
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
We will store your OpenAI API key, but we will not use it for any
|
||||
other purpose. However,{" "}
|
||||
<strong>we have not implemented any encryption logic yet</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -24,8 +24,6 @@ describe("ApiKeyConfig", () => {
|
||||
it("should render ApiConfig Component", () => {
|
||||
const { getByTestId } = render(<ApiKeyConfig />);
|
||||
expect(getByTestId("create-new-key")).toBeDefined();
|
||||
expect(getByTestId("open-ai-api-key-input")).toBeDefined();
|
||||
expect(getByTestId("save-open-ai-api-key")).toBeDefined();
|
||||
});
|
||||
|
||||
it("renders 'Create New Key' button when apiKey is empty", () => {
|
||||
|
@ -1,9 +1,7 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { validateOpenAIKey } from "@/app/brains-management/[brainId]/components/BrainManagementTabs/components/SettingsTab/utils/validateOpenAIKey";
|
||||
import { useAuthApi } from "@/lib/api/auth/useAuthApi";
|
||||
import { USER_IDENTITY_DATA_KEY } from "@/lib/api/user/config";
|
||||
import { useUserApi } from "@/lib/api/user/useUserApi";
|
||||
@ -26,7 +24,6 @@ export const useApiKeyConfig = () => {
|
||||
const { createApiKey } = useAuthApi();
|
||||
const { publish } = useToast();
|
||||
const [userIdentity, setUserIdentity] = useState<UserIdentity>();
|
||||
const { t } = useTranslation(["config"]);
|
||||
const queryClient = useQueryClient();
|
||||
const { data: userData } = useQuery({
|
||||
queryKey: [USER_IDENTITY_DATA_KEY],
|
||||
@ -66,25 +63,9 @@ export const useApiKeyConfig = () => {
|
||||
try {
|
||||
setChangeOpenAiApiKeyRequestPending(true);
|
||||
|
||||
if (
|
||||
openAiApiKey !== undefined &&
|
||||
openAiApiKey !== null &&
|
||||
!(await validateOpenAIKey(
|
||||
openAiApiKey,
|
||||
{
|
||||
badApiKeyError: t("incorrectApiKey", { ns: "config" }),
|
||||
invalidApiKeyError: t("invalidApiKeyError", { ns: "config" }),
|
||||
},
|
||||
publish
|
||||
))
|
||||
) {
|
||||
setChangeOpenAiApiKeyRequestPending(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await updateUserIdentity({
|
||||
openai_api_key: openAiApiKey,
|
||||
});
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [USER_IDENTITY_DATA_KEY],
|
||||
@ -105,7 +86,6 @@ export const useApiKeyConfig = () => {
|
||||
try {
|
||||
setChangeOpenAiApiKeyRequestPending(true);
|
||||
await updateUserIdentity({
|
||||
openai_api_key: null,
|
||||
});
|
||||
|
||||
publish({
|
||||
@ -123,16 +103,7 @@ export const useApiKeyConfig = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userIdentity?.openai_api_key !== undefined) {
|
||||
setOpenAiApiKey(userIdentity.openai_api_key);
|
||||
}
|
||||
}, [userIdentity]);
|
||||
|
||||
const hasOpenAiApiKey =
|
||||
userIdentity?.openai_api_key !== null &&
|
||||
userIdentity?.openai_api_key !== undefined &&
|
||||
userIdentity.openai_api_key !== "";
|
||||
|
||||
return {
|
||||
handleCreateClick,
|
||||
@ -144,6 +115,5 @@ export const useApiKeyConfig = () => {
|
||||
changeOpenAiApiKeyRequestPending,
|
||||
userIdentity,
|
||||
removeOpenAiApiKey,
|
||||
hasOpenAiApiKey,
|
||||
};
|
||||
};
|
||||
|
@ -69,7 +69,6 @@ describe("useBrainApi", () => {
|
||||
model: "gpt-3.5-turbo",
|
||||
temperature: 0.0,
|
||||
max_tokens: 256,
|
||||
openai_api_key: "123",
|
||||
};
|
||||
|
||||
await createBrain(brain);
|
||||
@ -220,7 +219,6 @@ describe("useBrainApi", () => {
|
||||
model: "gpt-3.5-turbo",
|
||||
temperature: 0.0,
|
||||
max_tokens: 256,
|
||||
openai_api_key: "123",
|
||||
};
|
||||
await updateBrain(brainId, brain);
|
||||
expect(axiosPutMock).toHaveBeenCalledTimes(1);
|
||||
|
@ -42,7 +42,6 @@ export type CreateBrainInput = {
|
||||
model?: Model;
|
||||
temperature?: number;
|
||||
max_tokens?: number;
|
||||
openai_api_key?: string;
|
||||
prompt_id?: string | null;
|
||||
brain_type?: BrainType;
|
||||
brain_definition?: ApiBrainDefinition;
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { useUserApi } from "../useUserApi";
|
||||
import { UserIdentityUpdatableProperties } from "../user";
|
||||
|
||||
const axiosPutMock = vi.fn(() => ({}));
|
||||
const axiosGetMock = vi.fn(() => ({}));
|
||||
|
||||
vi.mock("@/lib/hooks", () => ({
|
||||
useAxios: () => ({
|
||||
axiosInstance: {
|
||||
put: axiosPutMock,
|
||||
get: axiosGetMock,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("useUserApi", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
it("should call updateUserIdentity with the correct parameters", async () => {
|
||||
const {
|
||||
result: {
|
||||
current: { updateUserIdentity },
|
||||
},
|
||||
} = renderHook(() => useUserApi());
|
||||
const userUpdatableProperties: UserIdentityUpdatableProperties = {
|
||||
openai_api_key: "sk-xxx",
|
||||
};
|
||||
await updateUserIdentity(userUpdatableProperties);
|
||||
|
||||
expect(axiosPutMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosPutMock).toHaveBeenCalledWith(
|
||||
`/user/identity`,
|
||||
userUpdatableProperties
|
||||
);
|
||||
});
|
||||
it("should call getUserIdentity with the correct parameters", async () => {
|
||||
const {
|
||||
result: {
|
||||
current: { getUserIdentity },
|
||||
},
|
||||
} = renderHook(() => useUserApi());
|
||||
await getUserIdentity();
|
||||
|
||||
expect(axiosGetMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosGetMock).toHaveBeenCalledWith(`/user/identity`);
|
||||
});
|
||||
it("should call getUser with the correct parameters", async () => {
|
||||
const {
|
||||
result: {
|
||||
current: { getUser },
|
||||
},
|
||||
} = renderHook(() => useUserApi());
|
||||
await getUser();
|
||||
|
||||
expect(axiosGetMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosGetMock).toHaveBeenCalledWith(`/user`);
|
||||
});
|
||||
});
|
@ -8,7 +8,6 @@ export type UserIdentityUpdatableProperties = {
|
||||
};
|
||||
|
||||
export type UserIdentity = {
|
||||
openai_api_key?: string | null;
|
||||
user_id: UUID;
|
||||
};
|
||||
|
||||
|
@ -115,13 +115,7 @@ export const AddBrainConfig = ({
|
||||
<ApiRequestDefinition />
|
||||
</>
|
||||
)}
|
||||
<Field
|
||||
label={t("openAiKeyLabel", { ns: "config" })}
|
||||
placeholder={t("openAiKeyPlaceholder", { ns: "config" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
{...register("openai_api_key")}
|
||||
/>
|
||||
|
||||
|
||||
<fieldset className="w-full flex flex-col">
|
||||
<label className="flex-1 text-sm" htmlFor="model">
|
||||
|
@ -47,7 +47,6 @@ export const useAddBrainConfig = () => {
|
||||
formState: { dirtyFields },
|
||||
} = useFormContext<CreateBrainProps>();
|
||||
|
||||
const openAiKey = watch("openai_api_key");
|
||||
const model = watch("model");
|
||||
const temperature = watch("temperature");
|
||||
const maxTokens = watch("max_tokens");
|
||||
@ -55,7 +54,6 @@ export const useAddBrainConfig = () => {
|
||||
const brainType = watch("brain_type");
|
||||
|
||||
const accessibleModels = getAccessibleModels({
|
||||
openAiKey,
|
||||
userData,
|
||||
});
|
||||
|
||||
@ -109,7 +107,6 @@ export const useAddBrainConfig = () => {
|
||||
description,
|
||||
max_tokens: maxTokens,
|
||||
model,
|
||||
openai_api_key: openAiKey,
|
||||
temperature,
|
||||
prompt_id,
|
||||
status,
|
||||
|
@ -2,10 +2,11 @@ import { Fragment } from "react";
|
||||
|
||||
import { BrainManagementButton } from "@/lib/components/Sidebar/components/SidebarFooter/components/BrainManagementButton";
|
||||
|
||||
import { MarketPlaceButton } from "./components/MarketplaceButton";
|
||||
import { UpgradeToPlus } from "./components/UpgradeToPlus";
|
||||
import { UserButton } from "./components/UserButton";
|
||||
|
||||
export type SidebarFooterButtons = "myBrains" | "user" | "upgradeToPlus";
|
||||
export type SidebarFooterButtons = "myBrains" | "user" | "upgradeToPlus" | "marketplace";
|
||||
|
||||
type SidebarFooterProps = {
|
||||
showButtons: SidebarFooterButtons[];
|
||||
@ -16,6 +17,7 @@ export const SidebarFooter = ({
|
||||
}: SidebarFooterProps): JSX.Element => {
|
||||
const buttons = {
|
||||
myBrains: <BrainManagementButton />,
|
||||
marketplace: <MarketPlaceButton />,
|
||||
upgradeToPlus: <UpgradeToPlus />,
|
||||
user: <UserButton />,
|
||||
};
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PiDotsNineBold } from "react-icons/pi";
|
||||
|
||||
|
||||
import { SidebarFooterButton } from "./SidebarFooterButton";
|
||||
|
||||
export const MarketPlaceButton = (): JSX.Element => {
|
||||
const { t } = useTranslation("brain");
|
||||
|
||||
return (
|
||||
<SidebarFooterButton
|
||||
href={`/brains-management/library`}
|
||||
icon={<PiDotsNineBold className="w-8 h-8" />}
|
||||
label={t("brain_library_button_label")}
|
||||
data-testid="brain_library_button_label"
|
||||
/>
|
||||
);
|
||||
};
|
@ -5,7 +5,6 @@ export const addBrainDefaultValues: CreateBrainInput = {
|
||||
model: "gpt-3.5-turbo",
|
||||
temperature: 0,
|
||||
max_tokens: 500,
|
||||
openai_api_key: undefined,
|
||||
prompt_id: undefined,
|
||||
status: "private",
|
||||
name: "",
|
||||
|
@ -17,7 +17,6 @@ export type Brain = {
|
||||
model?: Model | null;
|
||||
max_tokens?: number;
|
||||
temperature?: number;
|
||||
openai_api_key?: string | null;
|
||||
description?: string;
|
||||
prompt_id?: string | null;
|
||||
brain_type?: BrainType;
|
||||
|
@ -34,7 +34,7 @@
|
||||
"set_brain_status_to_public_modal_description": "Every Quivr user will be able to:<br/>- Subscribe to your brain in the 'brains library'.<br/>- Use this brain and check the prompt and model configurations.<br/><br/>They won't have access to your uploaded files and people section.",
|
||||
"confirm_set_brain_status_to_public": "Yes, set as public",
|
||||
"cancel_set_brain_status_to_public": "No, keep it private",
|
||||
"brain_library_button_label": "Brains library",
|
||||
"brain_library_button_label": "Explore",
|
||||
"brain_management_button_label": "Brains management",
|
||||
"public_brains_search_bar_placeholder": "Search public brains",
|
||||
"public_brain_subscribe_button_label": "Subscribe",
|
||||
@ -48,9 +48,9 @@
|
||||
"empty_brain_description": "No description",
|
||||
"myBrains": "My Brains",
|
||||
"knowledge_source_doc": "Documents",
|
||||
"knowledge_source_api": "API",
|
||||
"knowledge_source_api": "App (Through API)",
|
||||
"knowledge_source_label": "Knowledge source",
|
||||
"api_brain":{
|
||||
"api_brain": {
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"type": "Type",
|
||||
@ -58,4 +58,4 @@
|
||||
"addRow": "Add row",
|
||||
"value": "Value"
|
||||
}
|
||||
}
|
||||
}
|
@ -8,10 +8,10 @@
|
||||
"openAiKeyLabel": "Open AI Key",
|
||||
"openAiKeyPlaceholder": "sk-xxx",
|
||||
"modelLabel": "Model",
|
||||
"anthropicKeyPlaceholder":"Anthropic API Key",
|
||||
"anthropicKeyLabel":"Anthropic API Key",
|
||||
"anthropicKeyPlaceholder": "Anthropic API Key",
|
||||
"anthropicKeyLabel": "Anthropic API Key",
|
||||
"temperature": "Temperature",
|
||||
"maxTokens": "Max tokens",
|
||||
"maxTokens": "Maximum Words per Response",
|
||||
"accountSection": "Your Account",
|
||||
"signedInAs": "Signed In as",
|
||||
"backendSection": "Backend config",
|
||||
@ -33,7 +33,7 @@
|
||||
"removePrompt": "Remove prompt",
|
||||
"newAPIKey": "Create New Key",
|
||||
"configSaved": "Config saved",
|
||||
"configReset": "Config reseted",
|
||||
"configReset": "Config reseted",
|
||||
"invalidOpenAiKey": "Invalid OpenAI Key",
|
||||
"error.createApiKey": "Error creating API Key",
|
||||
"error.copy": "Failed to copy",
|
||||
@ -50,4 +50,4 @@
|
||||
"incorrectApiKey": "Incorrect API Key",
|
||||
"invalidApiKeyError": "Invalid API Key",
|
||||
"apiKey": "API Key"
|
||||
}
|
||||
}
|
@ -34,7 +34,7 @@
|
||||
"set_brain_status_to_public_modal_description": "Chaque utilisateur de Quivr pourra :<br/>- S'abonner à votre cerveau dans la 'bibliothèque des cerveaux'.<br/>- Utiliser ce cerveau et vérifier les configurations de prompts et de modèles.<br/><br/>Ils n'auront pas accès à vos fichiers téléchargés et à la section des personnes.",
|
||||
"confirm_set_brain_status_to_public": "Oui, définir comme public",
|
||||
"cancel_set_brain_status_to_public": "Non, le garder privé",
|
||||
"brain_library_button_label": "Bibliothèque des cerveaux",
|
||||
"brain_library_button_label": "Explore",
|
||||
"public_brains_search_bar_placeholder": "Rechercher des cerveaux publics",
|
||||
"public_brain_subscribe_button_label": "S'abonner",
|
||||
"public_brain_subscription_success_message": "Vous vous êtes abonné avec succès au cerveau",
|
||||
@ -50,7 +50,7 @@
|
||||
"knowledge_source_doc": "Documents",
|
||||
"knowledge_source_api": "API",
|
||||
"knowledge_source_label": "Source de connaissance",
|
||||
"api_brain":{
|
||||
"api_brain": {
|
||||
"name": "Nom",
|
||||
"description": "Description",
|
||||
"type": "Type",
|
||||
@ -58,4 +58,4 @@
|
||||
"addRow": "Ajouter une ligne",
|
||||
"value": "Valeur"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user