diff --git a/backend/Dockerfile b/backend/Dockerfile index e976d45d3..5668a033d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -27,6 +27,6 @@ ENV PYTHONPATH=/app COPY . /app/ # Default number of workers -ENV FASTAPI_WORKERS=1 +ENV WEB_CONCURRENCY=4 -CMD ["sh", "-c", "fastapi run app/api_server.py --host 0.0.0.0 --port 80 --workers ${FASTAPI_WORKERS}"] +CMD ["sh", "-c", "fastapi run app/api_server.py --host 0.0.0.0 --port 80 --workers ${WEB_CONCURRENCY}"] diff --git a/backend/Makefile b/backend/Makefile index 6bb14b7ee..063acf775 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -25,8 +25,12 @@ test: @uv run pytest -v tests/ dev_backend: - @echo "Running development backend server..." - @uv run fastapi dev app/api_server.py --host 0.0.0.0 --port 5001 + @echo "Running backend server in development mode..." + @uv run fastapi dev app/api_server.py --host 127.0.0.1 --port 5001 + +run_backend: + @echo "Running backend server..." + @uv run fastapi run app/api_server.py --host 0.0.0.0 --port 5001 --workers 4 dev_celery_flower: @echo "Running Celery Flower..." diff --git a/backend/app/api/admin_routes/embedding_model/models.py b/backend/app/api/admin_routes/embedding_model/models.py index 7968251d4..7da3ae41e 100644 --- a/backend/app/api/admin_routes/embedding_model/models.py +++ b/backend/app/api/admin_routes/embedding_model/models.py @@ -27,8 +27,7 @@ def vector_dimension_must_gt_1(cls, v: int) -> int: class EmbeddingModelUpdate(BaseModel): name: Optional[str] = None config: Optional[dict | list] = None - credentials: Optional[Any] = None - is_default: Optional[bool] = False + credentials: Optional[str | dict] = None class EmbeddingModelItem(BaseModel): diff --git a/backend/app/api/admin_routes/embedding_model/routes.py b/backend/app/api/admin_routes/embedding_model/routes.py index 63dcc2b07..bcc474ba7 100644 --- a/backend/app/api/admin_routes/embedding_model/routes.py +++ b/backend/app/api/admin_routes/embedding_model/routes.py @@ -1,4 +1,3 @@ -import logging from typing import List from fastapi import APIRouter, Depends @@ -12,43 +11,29 @@ EmbeddingModelCreate, ) from app.api.deps import CurrentSuperuserDep, SessionDep -from app.exceptions import EmbeddingModelNotFound, InternalServerError -from app.repositories.embedding_model import embed_model_repo +from app.repositories.embedding_model import embedding_model_repo from app.rag.embeddings.provider import ( EmbeddingProviderOption, embedding_provider_options, ) from app.rag.embeddings.resolver import resolve_embed_model +from app.logger import logger router = APIRouter() -logger = logging.getLogger(__name__) -@router.get("/admin/embedding-models/provider/options") +@router.get("/admin/embedding-models/providers/options") def list_embedding_model_provider_options( user: CurrentSuperuserDep, ) -> List[EmbeddingProviderOption]: return embedding_provider_options -@router.get("/admin/embedding-models/options", deprecated=True) -def get_embedding_model_options( - user: CurrentSuperuserDep, -) -> List[EmbeddingProviderOption]: - return embedding_provider_options - - -@router.post("/admin/embedding-models") -def create_embedding_model( - session: SessionDep, - user: CurrentSuperuserDep, - create: EmbeddingModelCreate, -) -> EmbeddingModelDetail: - try: - return embed_model_repo.create(session, create) - except Exception as e: - logger.exception(e) - raise InternalServerError() +@router.get("/admin/embedding-models") +def list_embedding_models( + db_session: SessionDep, user: CurrentSuperuserDep, params: Params = Depends() +) -> Page[EmbeddingModelItem]: + return embedding_model_repo.paginate(db_session, params) @router.post("/admin/embedding-models/test") @@ -72,60 +57,50 @@ def test_embedding_model( success = True error = "" except Exception as e: + logger.info(f"Failed to test embedding model: {e}") success = False error = str(e) return EmbeddingModelTestResult(success=success, error=error) -@router.get("/admin/embedding-models") -def list_embedding_models( - session: SessionDep, user: CurrentSuperuserDep, params: Params = Depends() -) -> Page[EmbeddingModelItem]: - return embed_model_repo.paginate(session, params) +@router.post("/admin/embedding-models") +def create_embedding_model( + db_session: SessionDep, + user: CurrentSuperuserDep, + create: EmbeddingModelCreate, +) -> EmbeddingModelDetail: + return embedding_model_repo.create(db_session, create) @router.get("/admin/embedding-models/{model_id}") def get_embedding_model_detail( - session: SessionDep, user: CurrentSuperuserDep, model_id: int + db_session: SessionDep, user: CurrentSuperuserDep, model_id: int ) -> EmbeddingModelDetail: - try: - return embed_model_repo.must_get(session, model_id) - except EmbeddingModelNotFound as e: - raise e - except Exception as e: - logger.exception(e) - raise InternalServerError() + return embedding_model_repo.must_get(db_session, model_id) @router.put("/admin/embedding-models/{model_id}") def update_embedding_model( - session: SessionDep, + db_session: SessionDep, user: CurrentSuperuserDep, model_id: int, update: EmbeddingModelUpdate, ) -> EmbeddingModelDetail: - try: - embed_model = embed_model_repo.must_get(session, model_id) - embed_model_repo.update(session, embed_model, update) - return embed_model - except EmbeddingModelNotFound as e: - raise e - except Exception as e: - logger.exception(e) - raise InternalServerError() + embed_model = embedding_model_repo.must_get(db_session, model_id) + return embedding_model_repo.update(db_session, embed_model, update) + + +@router.delete("/admin/embedding-models/{model_id}") +def delete_embedding_model( + db_session: SessionDep, user: CurrentSuperuserDep, model_id: int +) -> None: + embedding_model = embedding_model_repo.must_get(db_session, model_id) + embedding_model_repo.delete(db_session, embedding_model) @router.put("/admin/embedding-models/{model_id}/set_default") def set_default_embedding_model( - session: SessionDep, user: CurrentSuperuserDep, model_id: int + db_session: SessionDep, user: CurrentSuperuserDep, model_id: int ) -> EmbeddingModelDetail: - try: - embed_model = embed_model_repo.must_get(session, model_id) - embed_model_repo.set_default_model(session, model_id) - session.refresh(embed_model) - return embed_model - except EmbeddingModelNotFound as e: - raise e - except Exception as e: - logger.exception(e) - raise InternalServerError() + embed_model = embedding_model_repo.must_get(db_session, model_id) + return embedding_model_repo.set_default(db_session, embed_model) diff --git a/backend/app/api/admin_routes/knowledge_base/routes.py b/backend/app/api/admin_routes/knowledge_base/routes.py index be6dba546..a1903ca68 100644 --- a/backend/app/api/admin_routes/knowledge_base/routes.py +++ b/backend/app/api/admin_routes/knowledge_base/routes.py @@ -24,7 +24,7 @@ KnowledgeBase, ) from app.repositories import ( - embed_model_repo, + embedding_model_repo, llm_repo, data_source_repo, knowledge_base_repo, @@ -67,7 +67,9 @@ def create_knowledge_base( create.llm_id = llm_repo.must_get_default(session).id if not create.embedding_model_id: - create.embedding_model_id = embed_model_repo.must_get_default(session).id + create.embedding_model_id = embedding_model_repo.must_get_default( + session + ).id knowledge_base = KnowledgeBase( name=create.name, diff --git a/backend/app/api/admin_routes/llm/routes.py b/backend/app/api/admin_routes/llm/routes.py index fce07b886..ceb98eb2b 100644 --- a/backend/app/api/admin_routes/llm/routes.py +++ b/backend/app/api/admin_routes/llm/routes.py @@ -4,11 +4,10 @@ from fastapi_pagination import Page, Params from llama_index.core.base.llms.types import ChatMessage from pydantic import BaseModel -from sqlalchemy import update from app.api.deps import CurrentSuperuserDep, SessionDep from app.logger import logger -from app.models import AdminLLM, ChatEngine, KnowledgeBase, LLM, LLMUpdate +from app.models import AdminLLM, LLM, LLMUpdate from app.rag.llms.provider import LLMProviderOption, llm_provider_options from app.rag.llms.resolver import resolve_llm from app.repositories.llm import llm_repo @@ -17,7 +16,7 @@ router = APIRouter() -@router.get("/admin/llms/provider/options") +@router.get("/admin/llms/providers/options") def list_llm_provider_options(user: CurrentSuperuserDep) -> List[LLMProviderOption]: return llm_provider_options @@ -31,15 +30,6 @@ def list_llms( return llm_repo.paginate(db_session, params) -@router.post("/admin/llms") -def create_llm( - db_session: SessionDep, - user: CurrentSuperuserDep, - llm: LLM, -) -> AdminLLM: - return llm_repo.create(db_session, llm) - - class LLMTestResult(BaseModel): success: bool error: str = "" @@ -72,12 +62,21 @@ def test_llm( success = True error = "" except Exception as e: - logger.error(f"Failed to test LLM: {e}") + logger.info(f"Failed to test LLM: {e}") success = False error = str(e) return LLMTestResult(success=success, error=error) +@router.post("/admin/llms") +def create_llm( + db_session: SessionDep, + user: CurrentSuperuserDep, + llm: LLM, +) -> AdminLLM: + return llm_repo.create(db_session, llm) + + @router.get("/admin/llms/{llm_id}") def get_llm( db_session: SessionDep, @@ -103,26 +102,9 @@ def delete_llm( db_session: SessionDep, user: CurrentSuperuserDep, llm_id: int, -) -> AdminLLM: +) -> None: llm = llm_repo.must_get(db_session, llm_id) - - # TODO: Support to specify a new LLM to replace the current LLM. - db_session.exec( - update(ChatEngine).where(ChatEngine.llm_id == llm_id).values(llm_id=None) - ) - db_session.exec( - update(ChatEngine) - .where(ChatEngine.fast_llm_id == llm_id) - .values(fast_llm_id=None) - ) - db_session.exec( - update(KnowledgeBase).where(KnowledgeBase.llm_id == llm_id).values(llm_id=None) - ) - - # TODO: Should using soft deletion. - db_session.delete(llm) - db_session.commit() - return llm + llm_repo.delete(db_session, llm) @router.put("/admin/llms/{llm_id}/set_default") diff --git a/backend/app/api/admin_routes/reranker_model/routes.py b/backend/app/api/admin_routes/reranker_model/routes.py index 078d561c9..dff214852 100644 --- a/backend/app/api/admin_routes/reranker_model/routes.py +++ b/backend/app/api/admin_routes/reranker_model/routes.py @@ -1,36 +1,43 @@ -import logging from typing import List from fastapi import Depends, APIRouter from fastapi_pagination import Params, Page - +from pydantic import BaseModel from llama_index.core.schema import NodeWithScore, TextNode -from sqlalchemy import update from app.api.admin_routes.llm.routes import LLMTestResult from app.api.deps import CurrentSuperuserDep, SessionDep -from app.exceptions import RerankerModelNotFound, InternalServerError -from app.models import RerankerModel, AdminRerankerModel, ChatEngine +from app.models import RerankerModel, AdminRerankerModel +from app.models.reranker_model import RerankerModelUpdate from app.repositories.reranker_model import reranker_model_repo from app.rag.rerankers.provider import RerankerProviderOption, reranker_provider_options from app.rag.rerankers.resolver import resolve_reranker +from app.logger import logger + + router = APIRouter() -logger = logging.getLogger(__name__) -@router.get("/admin/reranker-models/provider/options") +@router.get("/admin/reranker-models/providers/options") def list_reranker_model_provider_options( user: CurrentSuperuserDep, ) -> List[RerankerProviderOption]: return reranker_provider_options -@router.get("/admin/reranker-models/options", deprecated=True) -def get_reranker_model_options( +@router.get("/admin/reranker-models") +def list_reranker_models( + db_session: SessionDep, user: CurrentSuperuserDep, -) -> List[RerankerProviderOption]: - return reranker_provider_options + params: Params = Depends(), +) -> Page[AdminRerankerModel]: + return reranker_model_repo.paginate(db_session, params) + + +class RerankerModelTestResult(BaseModel): + success: bool + error: str = "" @router.post("/admin/reranker-models/test") @@ -46,7 +53,7 @@ def test_reranker_model( config=db_reranker_model.config, credentials=db_reranker_model.credentials, ) - reranker.postprocess_nodes( + reranked_nodes = reranker.postprocess_nodes( nodes=[ NodeWithScore( node=TextNode( @@ -56,7 +63,7 @@ def test_reranker_model( ), NodeWithScore( node=TextNode( - text="TiDB is compatible with MySQL protocol.", + text="TiKV is a distributed key-value storage engine.", ), score=0.6, ), @@ -69,79 +76,59 @@ def test_reranker_model( ], query_str="What is TiDB?", ) + if len(reranked_nodes) != 2: + raise ValueError("expected 2 nodes, but got %d", len(reranked_nodes)) success = True error = "" except Exception as e: + logger.info(f"Failed to test reranker model: {e}") success = False error = str(e) - return LLMTestResult(success=success, error=error) - - -@router.get("/admin/reranker-models") -def list_reranker_models( - session: SessionDep, - user: CurrentSuperuserDep, - params: Params = Depends(), -) -> Page[AdminRerankerModel]: - return reranker_model_repo.paginate(session, params) + return RerankerModelTestResult(success=success, error=error) @router.post("/admin/reranker-models") def create_reranker_model( - reranker_model: RerankerModel, - session: SessionDep, + db_session: SessionDep, user: CurrentSuperuserDep, + reranker_model: RerankerModel, ) -> AdminRerankerModel: - return reranker_model_repo.create(session, reranker_model) + return reranker_model_repo.create(db_session, reranker_model) -@router.get("/admin/reranker-models/{reranker_model_id}") -def get_reranker_model_detail( - reranker_model_id: int, - session: SessionDep, +@router.get("/admin/reranker-models/{model_id}") +def get_reranker_model( + db_session: SessionDep, user: CurrentSuperuserDep, + model_id: int, ) -> AdminRerankerModel: - try: - return reranker_model_repo.must_get(session, reranker_model_id) - except RerankerModelNotFound as e: - raise e - except Exception as e: - logger.exception(e) - raise InternalServerError() + return reranker_model_repo.must_get(db_session, model_id) -@router.delete("/admin/reranker-models/{reranker_model_id}") -def delete_reranker_model( - reranker_model_id: int, - session: SessionDep, +@router.put("/admin/reranker-models/{model_id}") +def update_reranker_model( + db_session: SessionDep, user: CurrentSuperuserDep, -): - reranker_model = reranker_model_repo.must_get(session, reranker_model_id) - - # FIXME: Should be replaced with a new reranker or prohibit users from operating, - # If the current reranker is used by a Chat Engine or Knowledge Base. + model_id: int, + model_update: RerankerModelUpdate, +) -> AdminRerankerModel: + reranker_model = reranker_model_repo.must_get(db_session, model_id) + return reranker_model_repo.update(db_session, reranker_model, model_update) - session.exec( - update(ChatEngine) - .where(ChatEngine.reranker_id == reranker_model_id) - .values(reranker_id=None) - ) - session.delete(reranker_model) - session.commit() +@router.delete("/admin/reranker-models/{model_id}") +def delete_reranker_model( + db_session: SessionDep, + user: CurrentSuperuserDep, + model_id: int, +) -> None: + reranker_model = reranker_model_repo.must_get(db_session, model_id) + reranker_model_repo.delete(db_session, reranker_model) -@router.put("/admin/reranker-models/{reranker_model_id}/set_default") +@router.put("/admin/reranker-models/{model_id}/set_default") def set_default_reranker_model( - session: SessionDep, user: CurrentSuperuserDep, reranker_model_id: int + db_session: SessionDep, user: CurrentSuperuserDep, model_id: int ) -> AdminRerankerModel: - try: - reranker_model = reranker_model_repo.must_get(session, reranker_model_id) - reranker_model_repo.set_default_model(session, reranker_model_id) - session.refresh(reranker_model) - return reranker_model - except RerankerModelNotFound as e: - raise e - except Exception as e: - logger.exception(e) - raise InternalServerError() + reranker_model = reranker_model_repo.must_get(db_session, model_id) + return reranker_model_repo.set_default(db_session, reranker_model) diff --git a/backend/app/models/llm.py b/backend/app/models/llm.py index b88803593..20337328b 100644 --- a/backend/app/models/llm.py +++ b/backend/app/models/llm.py @@ -27,4 +27,4 @@ class AdminLLM(BaseLLM): class LLMUpdate(BaseModel): name: Optional[str] = None config: Optional[dict] = None - credentials: Optional[dict] = None + credentials: Optional[str | dict] = None diff --git a/backend/app/models/reranker_model.py b/backend/app/models/reranker_model.py index 1e305013d..a337df6f1 100644 --- a/backend/app/models/reranker_model.py +++ b/backend/app/models/reranker_model.py @@ -4,6 +4,7 @@ from .base import UpdatableBaseModel, AESEncryptedColumn from app.rag.rerankers.provider import RerankerProvider +from pydantic import BaseModel class BaseRerankerModel(UpdatableBaseModel): @@ -24,3 +25,10 @@ class RerankerModel(BaseRerankerModel, table=True): class AdminRerankerModel(BaseRerankerModel): id: int + + +class RerankerModelUpdate(BaseModel): + name: Optional[str] = None + config: Optional[dict | list] = None + credentials: Optional[str | dict] = None + top_n: Optional[int] = None diff --git a/backend/app/rag/chat/chat_service.py b/backend/app/rag/chat/chat_service.py index e471fe193..56204d8f5 100644 --- a/backend/app/rag/chat/chat_service.py +++ b/backend/app/rag/chat/chat_service.py @@ -42,7 +42,7 @@ ChatMessageSate, ) from app.repositories import chat_engine_repo -from app.repositories.embedding_model import embed_model_repo +from app.repositories.embedding_model import embedding_model_repo from app.repositories.llm import llm_repo from app.site_settings import SiteSetting from app.utils.jinja2 import get_prompt_by_jinja2_template @@ -209,7 +209,7 @@ def check_rag_required_config(session: Session) -> RequiredConfigStatus: missing, the RAG application can not complete its work. """ has_default_llm = llm_repo.has_default(session) - has_default_embedding_model = embed_model_repo.has_default(session) + has_default_embedding_model = embedding_model_repo.has_default(session) has_default_chat_engine = chat_engine_repo.has_default(session) has_knowledge_base = session.scalar(select(func.count(DBKnowledgeBase.id))) > 0 diff --git a/backend/app/rag/embeddings/resolver.py b/backend/app/rag/embeddings/resolver.py index 4ac1a1d95..1e4faba54 100644 --- a/backend/app/rag/embeddings/resolver.py +++ b/backend/app/rag/embeddings/resolver.py @@ -13,7 +13,7 @@ from app.rag.embeddings.open_like.openai_like_embedding import OpenAILikeEmbedding from app.rag.embeddings.local.local_embedding import LocalEmbedding -from app.repositories.embedding_model import embed_model_repo +from app.repositories.embedding_model import embedding_model_repo from app.rag.embeddings.provider import EmbeddingProvider @@ -84,7 +84,7 @@ def resolve_embed_model( def get_default_embed_model(session: Session) -> Optional[BaseEmbedding]: - db_embed_model = embed_model_repo.get_default(session) + db_embed_model = embedding_model_repo.get_default(session) if not db_embed_model: return None return resolve_embed_model( @@ -96,7 +96,7 @@ def get_default_embed_model(session: Session) -> Optional[BaseEmbedding]: def must_get_default_embed_model(session: Session) -> BaseEmbedding: - db_embed_model = embed_model_repo.must_get_default(session) + db_embed_model = embedding_model_repo.must_get_default(session) return resolve_embed_model( db_embed_model.provider, db_embed_model.model, diff --git a/backend/app/repositories/__init__.py b/backend/app/repositories/__init__.py index 62c781ea0..cf6157290 100644 --- a/backend/app/repositories/__init__.py +++ b/backend/app/repositories/__init__.py @@ -8,4 +8,4 @@ from .knowledge_base import knowledge_base_repo from .feedback import feedback_repo from .llm import llm_repo -from .embedding_model import embed_model_repo +from .embedding_model import embedding_model_repo diff --git a/backend/app/repositories/embedding_model.py b/backend/app/repositories/embedding_model.py index 12131bc93..a8094c9fd 100644 --- a/backend/app/repositories/embedding_model.py +++ b/backend/app/repositories/embedding_model.py @@ -1,4 +1,4 @@ -from typing import Type +from typing import Optional, Type from fastapi_pagination import Params, Page from fastapi_pagination.ext.sqlmodel import paginate @@ -11,20 +11,44 @@ ) from app.exceptions import DefaultEmbeddingModelNotFound, EmbeddingModelNotFound from app.models import EmbeddingModel +from app.models.knowledge_base import KnowledgeBase from app.repositories.base_repo import BaseRepo class EmbeddingModelRepo(BaseRepo): model_cls = EmbeddingModel + def paginate( + self, session: Session, params: Params | None = Params() + ) -> Page[EmbeddingModel]: + query = select(EmbeddingModel) + # Make sure the default model is always on top. + query = query.order_by( + EmbeddingModel.is_default.desc(), EmbeddingModel.created_at.desc() + ) + return paginate(session, query, params) + + def get(self, session: Session, model_id: int) -> Optional[EmbeddingModel]: + return session.get(EmbeddingModel, model_id) + + def must_get(self, session: Session, model_id: int) -> Type[EmbeddingModel]: + db_embed_model = self.get(session, model_id) + if db_embed_model is None: + raise EmbeddingModelNotFound(model_id) + return db_embed_model + + def exists_any_model(self, session: Session) -> bool: + stmt = select(EmbeddingModel).with_for_update().limit(1) + return session.exec(stmt).one_or_none() is not None + def create(self, session: Session, create: EmbeddingModelCreate): - # If there is currently no model, the first model is - # automatically set as the default model. + # If there is currently no model, the first model will be + # set as the default model. if not self.exists_any_model(session): create.is_default = True if create.is_default: - self.unset_default_model(session) + self._unset_default(session) embed_model = EmbeddingModel( name=create.name, @@ -41,32 +65,12 @@ def create(self, session: Session, create: EmbeddingModelCreate): return embed_model - def exists_any_model(self, session: Session) -> bool: - stmt = select(EmbeddingModel).with_for_update().limit(1) - return session.exec(stmt).one_or_none() is not None - - def must_get(self, session: Session, model_id: int) -> Type[EmbeddingModel]: - db_embed_model = self.get(session, model_id) - if db_embed_model is None: - raise EmbeddingModelNotFound(model_id) - return db_embed_model - - def paginate( - self, session: Session, params: Params | None = Params() - ) -> Page[EmbeddingModel]: - query = select(EmbeddingModel).order_by(EmbeddingModel.created_at.desc()) - return paginate(session, query, params) - def update( self, session: Session, embed_model: EmbeddingModel, partial_update: EmbeddingModelUpdate, ) -> EmbeddingModel: - set_default = partial_update.is_default - if set_default: - self.unset_default_model(session) - for field, value in partial_update.model_dump(exclude_unset=True).items(): setattr(embed_model, field, value) flag_modified(embed_model, field) @@ -75,6 +79,17 @@ def update( session.refresh(embed_model) return embed_model + def delete(self, session: Session, model: EmbeddingModel): + # TODO: Support to specify a new embedding model to replace the current embedding model. + session.exec( + update(KnowledgeBase) + .where(KnowledgeBase.embedding_model_id == model.id) + .values(embedding_model_id=None) + ) + + session.delete(model) + session.commit() + # Default model def get_default(self, session: Session) -> Type[EmbeddingModel]: @@ -90,21 +105,20 @@ def must_get_default(self, session: Session) -> Type[EmbeddingModel]: raise DefaultEmbeddingModelNotFound() return embed_model - def unset_default_model(self, session: Session): + def _unset_default(self, session: Session): session.exec( update(EmbeddingModel) .values(is_default=False) .where(EmbeddingModel.is_default == True) ) - def set_default_model(self, session: Session, new_default_model_id: int): - self.unset_default_model(session) - session.exec( - update(EmbeddingModel) - .values(is_default=True) - .where(EmbeddingModel.id == new_default_model_id) - ) + def set_default(self, session: Session, model: EmbeddingModel): + self._unset_default(session) + model.is_default = True + flag_modified(model, "is_default") session.commit() + session.refresh(model) + return model -embed_model_repo = EmbeddingModelRepo() +embedding_model_repo = EmbeddingModelRepo() diff --git a/backend/app/repositories/llm.py b/backend/app/repositories/llm.py index cf348a728..e006a234b 100644 --- a/backend/app/repositories/llm.py +++ b/backend/app/repositories/llm.py @@ -9,6 +9,8 @@ from app.exceptions import DefaultLLMNotFound, LLMNotFound from app.models import LLM, LLMUpdate +from app.models.chat_engine import ChatEngine +from app.models.knowledge_base import KnowledgeBase from app.repositories.base_repo import BaseRepo @@ -30,12 +32,19 @@ def must_get(self, session: Session, llm_id: int) -> LLM: raise LLMNotFound(llm_id) return db_llm + def exists_any_model(self, session: Session) -> bool: + stmt = select(LLM).with_for_update().limit(1) + return session.exec(stmt).one_or_none() is not None + def create(self, session: Session, llm: LLM) -> LLM: # If there is no exiting model, the first model is # automatically set as the default model. if not self.exists_any_model(session): llm.is_default = True + if llm.is_default: + self._unset_default(session) + llm.id = None session.add(llm) session.commit() @@ -43,9 +52,14 @@ def create(self, session: Session, llm: LLM) -> LLM: return llm - def exists_any_model(self, session: Session) -> bool: - stmt = select(LLM).with_for_update().limit(1) - return session.exec(stmt).one_or_none() is not None + def update(self, session: Session, llm: LLM, llm_update: LLMUpdate) -> LLM: + for field, value in llm_update.model_dump(exclude_unset=True).items(): + setattr(llm, field, value) + flag_modified(llm, field) + + session.commit() + session.refresh(llm) + return llm # Default model @@ -78,15 +92,24 @@ def set_default(self, session: Session, llm: LLM) -> LLM: session.refresh(llm) return llm - def update(self, session: Session, llm: LLM, llm_update: LLMUpdate) -> LLM: - for field, value in llm_update.model_dump(exclude_unset=True).items(): - setattr(llm, field, value) - flag_modified(llm, field) + def delete(self, session: Session, llm: LLM): + # TODO: Support to specify a new LLM to replace the current LLM. + session.exec( + update(ChatEngine).where(ChatEngine.llm_id == llm.id).values(llm_id=None) + ) + session.exec( + update(ChatEngine) + .where(ChatEngine.fast_llm_id == llm.id) + .values(fast_llm_id=None) + ) + session.exec( + update(KnowledgeBase) + .where(KnowledgeBase.llm_id == llm.id) + .values(llm_id=None) + ) - session.add(llm) + session.delete(llm) session.commit() - session.refresh(llm) - return llm llm_repo = LLMRepo() diff --git a/backend/app/repositories/reranker_model.py b/backend/app/repositories/reranker_model.py index 1b11e468f..c1ea89509 100644 --- a/backend/app/repositories/reranker_model.py +++ b/backend/app/repositories/reranker_model.py @@ -1,12 +1,15 @@ -from typing import Type, Optional +from typing import Optional from fastapi_pagination import Params, Page from fastapi_pagination.ext.sqlmodel import paginate from sqlalchemy import update -from sqlmodel import select, Session +from sqlalchemy.orm.attributes import flag_modified +from sqlmodel import Session, select from app.exceptions import RerankerModelNotFound, DefaultRerankerModelNotFound from app.models import RerankerModel +from app.models.chat_engine import ChatEngine +from app.models.reranker_model import RerankerModelUpdate from app.repositories.base_repo import BaseRepo @@ -23,23 +26,27 @@ def paginate( ) return paginate(session, query, params) - def get(self, session: Session, reranker_model_id: int) -> Optional[RerankerModel]: - return session.get(RerankerModel, reranker_model_id) + def get(self, session: Session, model_id: int) -> Optional[RerankerModel]: + return session.get(RerankerModel, model_id) - def must_get(self, session: Session, reranker_model_id: int) -> Type[RerankerModel]: - db_reranker_model = self.get(session, reranker_model_id) - if db_reranker_model is None: - raise RerankerModelNotFound(reranker_model_id) - return db_reranker_model + def must_get(self, session: Session, model_id: int) -> RerankerModel: + db_model = self.get(session, model_id) + if db_model is None: + raise RerankerModelNotFound(model_id) + return db_model + + def exists_any_model(self, session: Session) -> bool: + stmt = select(RerankerModel).with_for_update().limit(1) + return session.exec(stmt).one_or_none() is not None def create(self, session: Session, reranker_model: RerankerModel) -> RerankerModel: - # If there is no exiting model, the first model is - # automatically set as the default model. + # If there is no exiting model, the first model will be + # set as the default model. if not self.exists_any_model(session): reranker_model.is_default = True if reranker_model.is_default: - self.unset_default_model(session) + self.unset_default(session) reranker_model.id = None session.add(reranker_model) @@ -48,9 +55,30 @@ def create(self, session: Session, reranker_model: RerankerModel) -> RerankerMod return reranker_model - def exists_any_model(self, session: Session) -> bool: - stmt = select(RerankerModel).with_for_update().limit(1) - return session.exec(stmt).one_or_none() is not None + def update( + self, + session: Session, + reranker_model: RerankerModel, + model_update: RerankerModelUpdate, + ) -> RerankerModel: + for field, value in model_update.model_dump(exclude_unset=True).items(): + setattr(reranker_model, field, value) + flag_modified(reranker_model, field) + + session.commit() + session.refresh(reranker_model) + return reranker_model + + def delete(self, db_session: Session, reranker_model: RerankerModel): + # TODO: Support to specify a new reranker model to replace the current reranker model. + db_session.exec( + update(ChatEngine) + .where(ChatEngine.reranker_id == reranker_model.id) + .values(reranker_id=None) + ) + + db_session.delete(reranker_model) + db_session.commit() # Default model @@ -67,17 +95,16 @@ def must_get_default(self, session: Session) -> RerankerModel: raise DefaultRerankerModelNotFound() return db_reranker_model - def unset_default_model(self, session: Session): + def unset_default(self, session: Session): session.exec(update(RerankerModel).values(is_default=False)) - def set_default_model(self, session: Session, new_default_model_id: int): - self.unset_default_model(session) - session.exec( - update(RerankerModel) - .values(is_default=True) - .where(RerankerModel.id == new_default_model_id) - ) + def set_default(self, session: Session, model: RerankerModel): + self.unset_default(session) + model.is_default = True + flag_modified(model, "is_default") session.commit() + session.refresh(model) + return model reranker_model_repo = RerankerModelRepo() diff --git a/e2e/tests/api.spec.ts b/e2e/tests/api.spec.ts index 1f2ba6170..f8cda4a85 100644 --- a/e2e/tests/api.spec.ts +++ b/e2e/tests/api.spec.ts @@ -70,13 +70,13 @@ test.describe('API', () => { await expectGetOkStep('/api/v1/admin/feedbacks'); await expectGetOkStep('/api/v1/admin/llms'); - await expectGetOkStep('/api/v1/admin/llms/provider/options'); + await expectGetOkStep('/api/v1/admin/llms/providers/options'); await expectGetOkStep('/api/v1/admin/embedding-models'); - await expectGetOkStep('/api/v1/admin/embedding-models/options'); + await expectGetOkStep('/api/v1/admin/embedding-models/providers/options'); await expectGetOkStep('/api/v1/admin/reranker-models'); - await expectGetOkStep('/api/v1/admin/reranker-models/options'); + await expectGetOkStep('/api/v1/admin/reranker-models/providers/options'); await expectGetOkStep('/api/v1/admin/retrieve/documents?chat_engine=1&question=what%20is%20tidb&chat_engine=default&top_k=5'); await expectGetOkStep('/api/v1/admin/embedding_retrieve?chat_engine=1&question=what%20is%20tidb&chat_engine=default&top_k=5'); diff --git a/frontend/app/src/api/embedding-models.ts b/frontend/app/src/api/embedding-models.ts index a094e320f..9f0887fbb 100644 --- a/frontend/app/src/api/embedding-models.ts +++ b/frontend/app/src/api/embedding-models.ts @@ -53,7 +53,7 @@ const embeddingModelOptionSchema = providerOptionSchema.and(z.object({ })) satisfies ZodType; export async function listEmbeddingModelOptions () { - return await fetch(requestUrl(`/api/v1/admin/embedding-models/options`), { + return await fetch(requestUrl(`/api/v1/admin/embedding-models/providers/options`), { headers: await authenticationHeaders(), }) .then(handleResponse(embeddingModelOptionSchema.array())); diff --git a/frontend/app/src/api/llms.ts b/frontend/app/src/api/llms.ts index f35e57fbc..5a7d82a7d 100644 --- a/frontend/app/src/api/llms.ts +++ b/frontend/app/src/api/llms.ts @@ -51,7 +51,7 @@ const llmOptionSchema = providerOptionSchema.and(z.object({ })) satisfies ZodType; export async function listLlmOptions () { - return await fetch(requestUrl(`/api/v1/admin/llms/provider/options`), { + return await fetch(requestUrl(`/api/v1/admin/llms/providers/options`), { headers: { ...await authenticationHeaders(), }, diff --git a/frontend/app/src/api/rerankers.ts b/frontend/app/src/api/rerankers.ts index 86b7a02ea..bce825ce5 100644 --- a/frontend/app/src/api/rerankers.ts +++ b/frontend/app/src/api/rerankers.ts @@ -50,7 +50,7 @@ const rerankerOptionSchema = providerOptionSchema.and(z.object({ })) satisfies ZodType; export async function listRerankerOptions () { - return await fetch(requestUrl(`/api/v1/admin/reranker-models/options`), { + return await fetch(requestUrl(`/api/v1/admin/reranker-models/providers/options`), { headers: { ...await authenticationHeaders(), }, diff --git a/frontend/app/src/app/(main)/(admin)/chat-engines/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/chat-engines/[id]/page.tsx index a9376b5d4..bde5516b7 100644 --- a/frontend/app/src/app/(main)/(admin)/chat-engines/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/chat-engines/[id]/page.tsx @@ -1,7 +1,8 @@ import { getChatEngine, getDefaultChatEngineOptions } from '@/api/chat-engines'; -import { getBootstrapStatus } from '@/api/system'; + import { AdminPageHeading } from '@/components/admin-page-heading'; import { UpdateChatEngineForm } from '@/components/chat-engine/update-chat-engine-form'; +import { getBootstrapStatus } from '@/api/system'; export default async function ChatEnginePage(props: { params: Promise<{ id: string }> }) { const params = await props.params; @@ -15,7 +16,7 @@ export default async function ChatEnginePage(props: { params: Promise<{ id: stri <> diff --git a/frontend/app/src/app/(main)/(admin)/chat-engines/page.tsx b/frontend/app/src/app/(main)/(admin)/chat-engines/page.tsx index ec72dfc7c..2b9392e49 100644 --- a/frontend/app/src/app/(main)/(admin)/chat-engines/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/chat-engines/page.tsx @@ -7,7 +7,7 @@ export default function ChatEnginesPage () { <> New Chat Engine diff --git a/frontend/app/src/app/(main)/(admin)/embedding-models/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/embedding-models/[id]/page.tsx index 5b412ec42..b42c45187 100644 --- a/frontend/app/src/app/(main)/(admin)/embedding-models/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/embedding-models/[id]/page.tsx @@ -1,12 +1,12 @@ 'use client';; -import { use } from "react"; -import { getEmbeddingModel } from '@/api/embedding-models'; import { AdminPageHeading } from '@/components/admin-page-heading'; import { ConfigViewer } from '@/components/config-viewer'; import { DateFormat } from '@/components/date-format'; -import { OptionDetail } from '@/components/option-detail'; import { Loader2Icon } from 'lucide-react'; +import { OptionDetail } from '@/components/option-detail'; +import { getEmbeddingModel } from '@/api/embedding-models'; +import { use } from "react"; import useSWR from 'swr'; export default function Page(props: { params: Promise<{ id: string }> }) { @@ -18,7 +18,7 @@ export default function Page(props: { params: Promise<{ id: string }> }) { }, ]} /> diff --git a/frontend/app/src/app/(main)/(admin)/embedding-models/create/page.tsx b/frontend/app/src/app/(main)/(admin)/embedding-models/create/page.tsx index f9f5e3694..a066c5c8e 100644 --- a/frontend/app/src/app/(main)/(admin)/embedding-models/create/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/embedding-models/create/page.tsx @@ -14,7 +14,7 @@ export default function Page () { diff --git a/frontend/app/src/app/(main)/(admin)/embedding-models/page.tsx b/frontend/app/src/app/(main)/(admin)/embedding-models/page.tsx index 944b415f6..ce17249a3 100644 --- a/frontend/app/src/app/(main)/(admin)/embedding-models/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/embedding-models/page.tsx @@ -12,7 +12,7 @@ export default function EmbeddingModelPage () { diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/[itemId]/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/[itemId]/page.tsx index 3f0c5ceb0..9627fcd54 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/[itemId]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/[itemId]/page.tsx @@ -1,10 +1,10 @@ 'use client'; import { AdminPageHeading } from '@/components/admin-page-heading'; -import { useEvaluationDataset } from '@/components/evaluations/hooks'; -import { UpdateEvaluationDatasetItemForm } from '@/components/evaluations/update-evaluation-dataset-item-form'; import { Loader2Icon } from 'lucide-react'; +import { UpdateEvaluationDatasetItemForm } from '@/components/evaluations/update-evaluation-dataset-item-form'; import { use } from 'react'; +import { useEvaluationDataset } from '@/components/evaluations/hooks'; export default function Page (props: { params: Promise<{ id: string, itemId: string }> }) { const params = use(props.params); @@ -17,7 +17,7 @@ export default function Page (props: { params: Promise<{ id: string, itemId: str <> , url: `/evaluation/datasets/${evaluationDatasetId}` }, { title: `${evaluationDatasetItemId}` }, diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/new/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/new/page.tsx index f17393990..66adfff4f 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/new/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/items/new/page.tsx @@ -1,11 +1,12 @@ 'use client'; +import { mutateEvaluationDataset, useEvaluationDataset } from '@/components/evaluations/hooks'; +import { use, useTransition } from 'react'; + import { AdminPageHeading } from '@/components/admin-page-heading'; import { CreateEvaluationDatasetItemForm } from '@/components/evaluations/create-evaluation-dataset-item-form'; -import { mutateEvaluationDataset, useEvaluationDataset } from '@/components/evaluations/hooks'; import { Loader2Icon } from 'lucide-react'; import { useRouter } from 'next/navigation'; -import { use, useTransition } from 'react'; export default function CreateEvaluationDatasetItemPage (props: { params: Promise<{ id: string }> }) { const params = use(props.params); @@ -20,7 +21,7 @@ export default function CreateEvaluationDatasetItemPage (props: { params: Promis <> , url: `/evaluation/datasets/${evaluationDatasetId}` }, { title: 'New Item' }, diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/not-found.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/not-found.tsx index d61f26529..54574501b 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/not-found.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/not-found.tsx @@ -6,7 +6,7 @@ export default function NotFound () { <> Not Found }, ]} diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/page.tsx index 93fda8029..96196d130 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/[id]/page.tsx @@ -3,11 +3,11 @@ import { AdminPageHeading } from '@/components/admin-page-heading'; import { EvaluationDatasetInfo } from '@/components/evaluations/evaluation-dataset-info'; import { EvaluationDatasetItemsTable } from '@/components/evaluations/evaluation-dataset-items-table'; -import { useEvaluationDataset } from '@/components/evaluations/hooks'; +import { Loader2Icon } from 'lucide-react'; import { NextLink } from '@/components/nextjs/NextLink'; import { Separator } from '@/components/ui/separator'; -import { Loader2Icon } from 'lucide-react'; import { use } from 'react'; +import { useEvaluationDataset } from '@/components/evaluations/hooks'; export default function EvaluationDatasetPage (props: { params: Promise<{ id: string }> }) { const params = use(props.params); @@ -19,7 +19,7 @@ export default function EvaluationDatasetPage (props: { params: Promise<{ id: st <> }, ]} diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/create/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/create/page.tsx index 0cd558a46..76a7eed7a 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/datasets/create/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/datasets/create/page.tsx @@ -14,7 +14,7 @@ export default function EvaluationTaskPage () { <> diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/not-found.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/not-found.tsx index 0d998ac2d..d3ce78394 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/not-found.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/not-found.tsx @@ -6,7 +6,7 @@ export default function NotFound () { <> Not Found }, ]} diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/page.tsx index eab2e497a..830d2bcae 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/[id]/page.tsx @@ -1,11 +1,11 @@ 'use client'; import { AdminPageHeading } from '@/components/admin-page-heading'; -import { EvaluationTaskItemsTable } from '@/components/evaluations/evaluation-task-items-table'; import { EvaluationTaskInfo } from '@/components/evaluations/evaluation-task-info'; -import { useEvaluationTask } from '@/components/evaluations/hooks'; +import { EvaluationTaskItemsTable } from '@/components/evaluations/evaluation-task-items-table'; import { Loader2Icon } from 'lucide-react'; import { use } from 'react'; +import { useEvaluationTask } from '@/components/evaluations/hooks'; export default function EvaluationTaskPage (props: { params: Promise<{ id: string }> }) { const params = use(props.params); @@ -17,7 +17,7 @@ export default function EvaluationTaskPage (props: { params: Promise<{ id: strin <> }, ]} diff --git a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/create/page.tsx b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/create/page.tsx index 42d3bddd1..2c69c01ba 100644 --- a/frontend/app/src/app/(main)/(admin)/evaluation/tasks/create/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/evaluation/tasks/create/page.tsx @@ -14,7 +14,7 @@ export default function EvaluationTaskPage () { <> diff --git a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/data-sources/new/page.tsx b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/data-sources/new/page.tsx index 860f7db67..8e944e80f 100644 --- a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/data-sources/new/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/data-sources/new/page.tsx @@ -1,11 +1,13 @@ 'use client'; ; + +import { mutateKnowledgeBases, useKnowledgeBase } from '@/components/knowledge-base/hooks'; +import { use, useTransition } from 'react'; + import { AdminPageHeading } from '@/components/admin-page-heading'; import { CreateDatasourceForm } from '@/components/datasource/create-datasource-form'; -import { mutateKnowledgeBases, useKnowledgeBase } from '@/components/knowledge-base/hooks'; import { Loader2Icon } from 'lucide-react'; import { useRouter } from 'next/navigation'; -import { use, useTransition } from 'react'; export default function NewKnowledgeBaseDataSourcePage (props: { params: Promise<{ id: string }> }) { const params = use(props.params); @@ -18,7 +20,7 @@ export default function NewKnowledgeBaseDataSourcePage (props: { params: Promise <> , url: `/knowledge-bases/${id}` }, { title: 'DataSources', url: `/knowledge-bases/${id}/data-sources` }, { title: 'New' }, diff --git a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/documents/[documentId]/chunks/page.tsx b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/documents/[documentId]/chunks/page.tsx index 69c33c6d2..6ca023273 100644 --- a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/documents/[documentId]/chunks/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(special)/documents/[documentId]/chunks/page.tsx @@ -1,13 +1,14 @@ 'use client';; -import { use } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { getKnowledgeBaseDocument, getKnowledgeBaseDocumentChunks } from '@/api/knowledge-base'; + import { AdminPageHeading } from '@/components/admin-page-heading'; -import { DateFormat } from '@/components/date-format'; import { CodeInput } from '@/components/form/widgets/CodeInput'; -import { useKnowledgeBase } from '@/components/knowledge-base/hooks'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { DateFormat } from '@/components/date-format'; import { Loader2Icon } from 'lucide-react'; +import { use } from "react"; +import { useKnowledgeBase } from '@/components/knowledge-base/hooks'; import useSWR from 'swr'; export default function DocumentChunksPage(props: { params: Promise<{ id: string, documentId: string }> }) { @@ -26,7 +27,7 @@ export default function DocumentChunksPage(props: { params: Promise<{ id: string <> , url: `/knowledge-bases/${kbId}` }, { title: document?.name ?? }, { title: 'Chunks' }, diff --git a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/layout.tsx b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/layout.tsx index a6ae25ac9..6f264bb7e 100644 --- a/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/layout.tsx +++ b/frontend/app/src/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/layout.tsx @@ -1,14 +1,15 @@ 'use client';; -import { use } from "react"; -import { KnowledgeBaseTabs } from '@/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/tabs'; +import { SecondaryNavigatorLayout, SecondaryNavigatorList, SecondaryNavigatorMain } from '@/components/secondary-navigator-list'; + import { AdminPageHeading } from '@/components/admin-page-heading'; import { ArrowRightIcon } from '@/components/icons'; -import { useKnowledgeBase } from '@/components/knowledge-base/hooks'; -import { SecondaryNavigatorLayout, SecondaryNavigatorList, SecondaryNavigatorMain } from '@/components/secondary-navigator-list'; -import { Loader2Icon } from 'lucide-react'; +import { KnowledgeBaseTabs } from '@/app/(main)/(admin)/knowledge-bases/[id]/(tabs)/tabs'; import Link from 'next/link'; +import { Loader2Icon } from 'lucide-react'; import type { ReactNode } from 'react'; +import { use } from "react"; +import { useKnowledgeBase } from '@/components/knowledge-base/hooks'; export default function KnowledgeBaseLayout(props: { params: Promise<{ id: string }>, children: ReactNode }) { const params = use(props.params); @@ -24,7 +25,7 @@ export default function KnowledgeBaseLayout(props: { params: Promise<{ id: strin <> diff --git a/frontend/app/src/app/(main)/(admin)/llms/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/llms/[id]/page.tsx index 90e0e65f9..eea867e31 100644 --- a/frontend/app/src/app/(main)/(admin)/llms/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/llms/[id]/page.tsx @@ -1,14 +1,15 @@ 'use client'; import { deleteLlm, getLlm } from '@/api/llms'; +import { use, useTransition } from 'react'; + import { AdminPageHeading } from '@/components/admin-page-heading'; +import { ConfigViewer } from '@/components/config-viewer'; import { DangerousActionButton } from '@/components/dangerous-action-button'; import { DateFormat } from '@/components/date-format'; -import { ConfigViewer } from '@/components/config-viewer'; -import { OptionDetail } from '@/components/option-detail'; import { Loader2Icon } from 'lucide-react'; +import { OptionDetail } from '@/components/option-detail'; import { useRouter } from 'next/navigation'; -import { useTransition, use } from 'react'; import useSWR from 'swr'; export default function Page(props: { params: Promise<{ id: string }> }) { @@ -22,7 +23,7 @@ export default function Page(props: { params: Promise<{ id: string }> }) { }, ]} /> diff --git a/frontend/app/src/app/(main)/(admin)/llms/create/page.tsx b/frontend/app/src/app/(main)/(admin)/llms/create/page.tsx index 27d89dd4d..b8b88e81b 100644 --- a/frontend/app/src/app/(main)/(admin)/llms/create/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/llms/create/page.tsx @@ -14,7 +14,7 @@ export default function Page () { diff --git a/frontend/app/src/app/(main)/(admin)/llms/page.tsx b/frontend/app/src/app/(main)/(admin)/llms/page.tsx index 13e3ccab0..bb63e8156 100644 --- a/frontend/app/src/app/(main)/(admin)/llms/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/llms/page.tsx @@ -9,7 +9,7 @@ export default function Page () { diff --git a/frontend/app/src/app/(main)/(admin)/reranker-models/[id]/page.tsx b/frontend/app/src/app/(main)/(admin)/reranker-models/[id]/page.tsx index b22e6ace8..9cd821380 100644 --- a/frontend/app/src/app/(main)/(admin)/reranker-models/[id]/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/reranker-models/[id]/page.tsx @@ -1,14 +1,15 @@ 'use client'; import { deleteReranker, getReranker } from '@/api/rerankers'; +import { use, useTransition } from 'react'; + import { AdminPageHeading } from '@/components/admin-page-heading'; import { ConfigViewer } from '@/components/config-viewer'; import { DangerousActionButton } from '@/components/dangerous-action-button'; import { DateFormat } from '@/components/date-format'; -import { OptionDetail } from '@/components/option-detail'; import { Loader2Icon } from 'lucide-react'; +import { OptionDetail } from '@/components/option-detail'; import { useRouter } from 'next/navigation'; -import { useTransition, use } from 'react'; import useSWR from 'swr'; export default function Page(props: { params: Promise<{ id: string }> }) { @@ -22,7 +23,7 @@ export default function Page(props: { params: Promise<{ id: string }> }) { }, ]} /> diff --git a/frontend/app/src/app/(main)/(admin)/reranker-models/create/page.tsx b/frontend/app/src/app/(main)/(admin)/reranker-models/create/page.tsx index 3d1d22453..a320f5da5 100644 --- a/frontend/app/src/app/(main)/(admin)/reranker-models/create/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/reranker-models/create/page.tsx @@ -14,7 +14,7 @@ export default function Page () { diff --git a/frontend/app/src/app/(main)/(admin)/reranker-models/page.tsx b/frontend/app/src/app/(main)/(admin)/reranker-models/page.tsx index 31e116ee6..c10789243 100644 --- a/frontend/app/src/app/(main)/(admin)/reranker-models/page.tsx +++ b/frontend/app/src/app/(main)/(admin)/reranker-models/page.tsx @@ -1,7 +1,7 @@ import { AdminPageHeading } from '@/components/admin-page-heading'; import { NextLink } from '@/components/nextjs/NextLink'; -import RerankerModelsTable from '@/components/reranker/RerankerModelsTable'; import { PlusIcon } from 'lucide-react'; +import RerankerModelsTable from '@/components/reranker/RerankerModelsTable'; export default function Page () { return ( @@ -9,7 +9,7 @@ export default function Page () { diff --git a/frontend/app/src/components/evaluations/create-evaluation-dataset-form.tsx b/frontend/app/src/components/evaluations/create-evaluation-dataset-form.tsx index 6412be940..356819f48 100644 --- a/frontend/app/src/components/evaluations/create-evaluation-dataset-form.tsx +++ b/frontend/app/src/components/evaluations/create-evaluation-dataset-form.tsx @@ -1,13 +1,13 @@ -import { uploadFiles } from '@/api/datasources'; -import { createEvaluationDataset } from '@/api/evaluations'; -import { FormInput } from '@/components/form/control-widget'; -import { withCreateEntityForm as withCreateEntityForm } from '@/components/form/create-entity-form'; -import { formFieldLayout } from '@/components/form/field-layout'; +import type { ComponentProps } from 'react'; import { FileInput } from '@/components/form/widgets/FileInput'; -import { zodFile } from '@/lib/zod'; +import { FormInput } from '@/components/form/control-widget'; import Link from 'next/link'; -import type { ComponentProps } from 'react'; +import { createEvaluationDataset } from '@/api/evaluations'; +import { formFieldLayout } from '@/components/form/field-layout'; +import { uploadFiles } from '@/api/datasources'; +import { withCreateEntityForm } from '@/components/form/create-entity-form'; import { z } from 'zod'; +import { zodFile } from '@/lib/zod'; const schema = z.object({ name: z.string().min(1), @@ -42,7 +42,7 @@ export function CreateEvaluationDatasetForm ({ transitioning, onCreated }: Omit< - Evaluation dataset CSV file. See the documentation for the format.}> + Evaluation dataset CSV file. See the documentation for the format.}>