From 02d0703ba1cac7170a5e3162452b0218f669ccf5 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Sat, 19 Oct 2024 19:37:06 +0300 Subject: [PATCH 01/15] Add icons --- .../AgentSettingsForm/DataSourcesStep.tsx | 15 ++++-- .../Conversation/ConversationPanel.tsx | 48 ++++++++++++------- .../src/components/UI/Tooltip.tsx | 2 +- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx index cb7fa964b3..c4597a94c7 100644 --- a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx +++ b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx @@ -2,7 +2,7 @@ import { uniqBy } from 'lodash'; import Link from 'next/link'; import { Dispatch, ReactNode, RefObject, SetStateAction, useRef } from 'react'; -import { Button, Icon, IconName, Spinner, Text } from '@/components/UI'; +import { Button, Icon, IconButton, IconName, Spinner, Text } from '@/components/UI'; import { ACCEPTED_FILE_TYPES, TOOL_GOOGLE_DRIVE_ID } from '@/constants'; import { useUploadAgentFile } from '@/hooks'; import { DataSourceArtifact } from '@/types/tools'; @@ -222,7 +222,7 @@ const DataSourceFileList: React.FC<{
- {artifacts.map(({ id, type, name }) => ( + {artifacts.map(({ id, type, name, url }) => (
{name} -
))} diff --git a/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx b/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx index ac3dd76fdd..2522ad5dae 100644 --- a/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx +++ b/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx @@ -158,13 +158,23 @@ export const ConversationPanel: React.FC = () => {
    {agentKnowledgeFiles.map((file) => ( -
  1. - + + + {file.name} + + - {file.name}
  2. ))}
@@ -188,10 +198,7 @@ export const ConversationPanel: React.FC = () => { {files && files.length > 0 && (
{files.map(({ file_name: name, id }) => ( -
+
= () => { /> {name}
- handleDeleteFile(id)} - disabled={isDeletingFile} - iconName="close" - className="invisible group-hover:visible" - /> +
+ + handleDeleteFile(id)} + /> +
))} diff --git a/src/interfaces/assistants_web/src/components/UI/Tooltip.tsx b/src/interfaces/assistants_web/src/components/UI/Tooltip.tsx index 055c4862f7..60cc85a733 100644 --- a/src/interfaces/assistants_web/src/components/UI/Tooltip.tsx +++ b/src/interfaces/assistants_web/src/components/UI/Tooltip.tsx @@ -84,7 +84,7 @@ export const Tooltip: React.FC = ({ return ( <> {children ? ( -
+
{children}
) : ( From f75d0e6b227d5117a25fc5fb6c21e084cd937903 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Sat, 19 Oct 2024 19:37:28 +0300 Subject: [PATCH 02/15] Run format-web for MessageRow --- .../assistants_web/src/components/MessageRow/MessageRow.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interfaces/assistants_web/src/components/MessageRow/MessageRow.tsx b/src/interfaces/assistants_web/src/components/MessageRow/MessageRow.tsx index b9b1ffa300..00f1ddd4fe 100644 --- a/src/interfaces/assistants_web/src/components/MessageRow/MessageRow.tsx +++ b/src/interfaces/assistants_web/src/components/MessageRow/MessageRow.tsx @@ -12,10 +12,9 @@ import { LongPressMenu, } from '@/components/UI'; import { Breakpoint, useBreakpoint } from '@/hooks'; -import { useSettingsStore } from '@/stores'; import { useExperimentalFeatures } from '@/hooks/use-experimentalFeatures'; import { SynthesisStatus } from '@/hooks/use-synthesizer'; - +import { useSettingsStore } from '@/stores'; import { type ChatMessage, isAbortedMessage, @@ -68,7 +67,7 @@ export const MessageRow = forwardRef(function MessageRowI // For showing thinking steps const { showSteps } = useSettingsStore(); const [isStepsExpanded, setIsStepsExpanded] = useState(true); - + useEffect(() => { setIsStepsExpanded(showSteps); }, [showSteps]); From 6f98847180fe061802a3d3ca4c5639b4bec9b9ca Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Sat, 19 Oct 2024 21:33:54 +0300 Subject: [PATCH 03/15] Add handlers --- .../AgentSettingsForm/DataSourcesStep.tsx | 14 ++++++++++++++ .../components/Conversation/ConversationPanel.tsx | 15 +++++++++++++++ .../src/components/UI/FileViewer.tsx | 7 +++++++ 3 files changed, 36 insertions(+) create mode 100644 src/interfaces/assistants_web/src/components/UI/FileViewer.tsx diff --git a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx index c4597a94c7..d6855290a0 100644 --- a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx +++ b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx @@ -3,7 +3,9 @@ import Link from 'next/link'; import { Dispatch, ReactNode, RefObject, SetStateAction, useRef } from 'react'; import { Button, Icon, IconButton, IconName, Spinner, Text } from '@/components/UI'; +import { FileViewer } from '@/components/UI/FileViewer'; import { ACCEPTED_FILE_TYPES, TOOL_GOOGLE_DRIVE_ID } from '@/constants'; +import { useContextStore } from '@/context'; import { useUploadAgentFile } from '@/hooks'; import { DataSourceArtifact } from '@/types/tools'; import { mapMimeTypeToExtension, pluralize } from '@/utils'; @@ -194,6 +196,7 @@ const DataSourceFileList: React.FC<{ handleRemoveFile: (id: string) => void; handleRemoveTool: VoidFunction; }> = ({ name, icon, artifacts = [], addFileButton, handleRemoveFile, handleRemoveTool }) => { + const { open } = useContextStore(); const filesCount = getCountString( 'file', artifacts.filter((artifact) => artifact.type === 'file') @@ -205,6 +208,16 @@ const DataSourceFileList: React.FC<{ const countCopy = [filesCount, foldersCount].filter((text) => !!text).join(', '); + const handleOpenFile = (fileId: string, url?: string) => { + if (url) { + window.open(url, '_blank'); + } else { + open({ + content: , + }); + } + }; + return (
@@ -235,6 +248,7 @@ const DataSourceFileList: React.FC<{ iconName={url ? 'arrow-up-right' : 'show'} tooltip={{ label: url ? 'Open url' : 'Show content' }} className="h-auto w-auto flex-shrink-0 self-center group-hover:visible" + onClick={() => handleOpenFile(id, url)} /> = () => { const { agentId, conversationId } = useChatRoutes(); const { data: agent } = useAgent({ agentId }); const { theme } = useBrandedColors(agentId); + const { open } = useContextStore(); const { params: { fileIds }, @@ -69,6 +72,16 @@ export const ConversationPanel: React.FC = () => { ...agentToolMetadataArtifacts.folders, ]; + const handleOpenFile = (fileId: string, url?: string) => { + if (url) { + window.open(url, '_blank'); + } else { + open({ + content: , + }); + } + }; + const handleDeleteFile = async (fileId: string) => { if (isDeletingFile || !conversationId) return; @@ -174,6 +187,7 @@ export const ConversationPanel: React.FC = () => { iconName={file.url ? 'arrow-up-right' : 'show'} tooltip={{ label: file.url ? 'Open url' : 'Show content' }} className="h-auto w-auto flex-shrink-0 self-center group-hover:visible" + onClick={() => handleOpenFile(file.id, file.url)} /> ))} @@ -214,6 +228,7 @@ export const ConversationPanel: React.FC = () => { tooltip={{ label: 'Show content' }} className="h-auto w-auto flex-shrink-0 self-center" disabled={isDeletingFile} + onClick={() => handleOpenFile(id)} /> = ({ fileId }) => { + return
{fileId}
; +}; From 264ffbc1db299b93c00dfbc68d05977b49a548d8 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Sun, 20 Oct 2024 00:28:07 +0300 Subject: [PATCH 04/15] Fetch conv files --- src/backend/routers/conversation.py | 45 ++++++++++++++++++++++++++++- src/backend/schemas/file.py | 8 +++++ src/backend/services/file.py | 4 ++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/backend/routers/conversation.py b/src/backend/routers/conversation.py index a59baf3b2f..e85108e54d 100644 --- a/src/backend/routers/conversation.py +++ b/src/backend/routers/conversation.py @@ -23,6 +23,7 @@ UpdateConversationRequest, ) from backend.schemas.file import ( + ConversationFileFull, DeleteConversationFileResponse, ListConversationFile, UploadConversationFileResponse, @@ -461,6 +462,48 @@ async def list_files( return files_with_conversation_id +@router.get("/{conversation_id}/files/{file_id}", response_model=ConversationFileFull) +async def get_file( + conversation_id: str, file_id: str, session: DBSessionDep, ctx: Context = Depends(get_context) +) -> ConversationFileFull: + """ + Get a file by ID. + + Args: + conversation_id (str): Conversation ID. + file_id (str): File ID. + session (DBSessionDep): Database session. + ctx (Context): Context object. + + Returns: + ConversationFileFull: File with the given ID. + + Raises: + HTTPException: If the conversation or file with the given ID is not found, or if the file does not belong to the conversation. + """ + user_id = ctx.get_user_id() + + conversation = validate_conversation(session, conversation_id, user_id) + file = validate_file(session, file_id, user_id) + + if file.id not in conversation.file_ids: + raise HTTPException( + status_code=404, + detail=f"File with ID: {file_id} does not belong to the conversation with ID: {conversation_id}." + ) + + return ConversationFileFull( + id=file.id, + conversation_id=conversation_id, + file_name=file.file_name, + file_content=file.file_content, + file_size=file.file_size, + user_id=file.user_id, + created_at=file.created_at, + updated_at=file.updated_at, + ) + + @router.delete("/{conversation_id}/files/{file_id}") async def delete_file( conversation_id: str, @@ -484,7 +527,7 @@ async def delete_file( """ user_id = ctx.get_user_id() _ = validate_conversation(session, conversation_id, user_id) - validate_file(session, file_id, user_id, conversation_id, ctx) + validate_file(session, file_id, user_id) # Delete the File DB object get_file_service().delete_conversation_file_by_id( diff --git a/src/backend/schemas/file.py b/src/backend/schemas/file.py index 6ca9cf114b..75844c3a98 100644 --- a/src/backend/schemas/file.py +++ b/src/backend/schemas/file.py @@ -29,6 +29,9 @@ class ConversationFilePublic(BaseModel): file_size: int = Field(default=0, ge=0) +class ConversationFileFull(ConversationFilePublic): + file_content: str + class AgentFilePublic(BaseModel): id: str @@ -38,6 +41,11 @@ class AgentFilePublic(BaseModel): file_name: str file_size: int = Field(default=0, ge=0) + +class AgentFileFull(AgentFilePublic): + file_content: str + + class ListConversationFile(ConversationFilePublic): pass diff --git a/src/backend/services/file.py b/src/backend/services/file.py index 1834d12e1c..c4d87bc677 100644 --- a/src/backend/services/file.py +++ b/src/backend/services/file.py @@ -287,7 +287,7 @@ def get_files_by_message_id( # Misc def validate_file( - session: DBSessionDep, file_id: str, user_id: str, index: str, ctx: Context + session: DBSessionDep, file_id: str, user_id: str ) -> File: """ Validates if a file exists and belongs to the user @@ -311,6 +311,8 @@ def validate_file( detail=f"File with ID: {file_id} not found.", ) + return file + async def insert_files_in_db( session: DBSessionDep, From 4bf8949dd58c2409cb67fca0447bf9528f9b8e1a Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Fri, 25 Oct 2024 21:08:31 +0300 Subject: [PATCH 05/15] Fetch agent files --- src/backend/crud/file.py | 9 +++- src/backend/routers/agent.py | 42 +++++++++++++++++- src/backend/routers/conversation.py | 10 ++--- src/backend/services/file.py | 66 ++++++++++++++++++----------- 4 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/backend/crud/file.py b/src/backend/crud/file.py index ab9e114112..51788e8c0e 100644 --- a/src/backend/crud/file.py +++ b/src/backend/crud/file.py @@ -35,7 +35,7 @@ def batch_create_files(db: Session, files: list[File]) -> list[File]: @validate_transaction -def get_file(db: Session, file_id: str, user_id: str) -> File: +def get_file(db: Session, file_id: str, user_id: str | None = None) -> File: """ Get a file by ID. @@ -47,7 +47,12 @@ def get_file(db: Session, file_id: str, user_id: str) -> File: Returns: File: File with the given ID. """ - return db.query(File).filter(File.id == file_id, File.user_id == user_id).first() + filters = [File.id == file_id] + + if user_id: + filters.append(File.user_id == user_id) + + return db.query(File).filter(*filters).first() @validate_transaction diff --git a/src/backend/routers/agent.py b/src/backend/routers/agent.py index bd16455b22..f677ac9d6e 100644 --- a/src/backend/routers/agent.py +++ b/src/backend/routers/agent.py @@ -8,6 +8,7 @@ from backend.config.routers import RouterName from backend.crud import agent as agent_crud from backend.crud import agent_tool_metadata as agent_tool_metadata_crud +from backend.crud import file as file_crud from backend.crud import snapshot as snapshot_crud from backend.database_models.agent import Agent as AgentModel from backend.database_models.agent_tool_metadata import ( @@ -30,7 +31,11 @@ ) from backend.schemas.context import Context from backend.schemas.deployment import Deployment as DeploymentSchema -from backend.schemas.file import DeleteAgentFileResponse, UploadAgentFileResponse +from backend.schemas.file import ( + AgentFileFull, + DeleteAgentFileResponse, + UploadAgentFileResponse, +) from backend.services.agent import ( raise_db_error, validate_agent_exists, @@ -633,6 +638,39 @@ async def batch_upload_file( return uploaded_files +@router.get("/{agent_id}/files/{file_id}") +async def get_agent_file( + agent_id: str, + file_id: str, + session: DBSessionDep, + ctx: Context = Depends(get_context), +): + user_id = ctx.get_user_id() + + if file_id not in get_file_service().get_file_ids_by_agent_id(session, user_id, agent_id, ctx): + raise HTTPException( + status_code=404, + detail=f"File with ID: {file_id} does not belong to the agent with ID: {agent_id}." + ) + + file = file_crud.get_file(session, file_id) + + if not file: + raise HTTPException( + status_code=404, + detail=f"File with ID: {file_id} not found.", + ) + + return AgentFileFull( + id=file.id, + file_name=file.file_name, + file_content=file.file_content, + file_size=file.file_size, + created_at=file.created_at, + updated_at=file.updated_at, + ) + + @router.delete("/{agent_id}/files/{file_id}") async def delete_agent_file( agent_id: str, @@ -655,7 +693,7 @@ async def delete_agent_file( HTTPException: If the agent with the given ID is not found. """ user_id = ctx.get_user_id() - _ = validate_agent_exists(session, agent_id) + _ = validate_agent_exists(session, agent_id, user_id) validate_file(session, file_id, user_id) # Delete the File DB object diff --git a/src/backend/routers/conversation.py b/src/backend/routers/conversation.py index 02c16a10f0..33c13d93aa 100644 --- a/src/backend/routers/conversation.py +++ b/src/backend/routers/conversation.py @@ -484,21 +484,21 @@ async def get_file( user_id = ctx.get_user_id() conversation = validate_conversation(session, conversation_id, user_id) - file = validate_file(session, file_id, user_id) - if file.id not in conversation.file_ids: + if file_id not in conversation.file_ids: raise HTTPException( status_code=404, - detail=f"File with ID: {file_id} does not belong to the conversation with ID: {conversation_id}." + detail=f"File with ID: {file_id} does not belong to the conversation with ID: {conversation.id}." ) + file = validate_file(session, file_id, user_id) + return ConversationFileFull( id=file.id, - conversation_id=conversation_id, + conversation_id=conversation.id, file_name=file.file_name, file_content=file.file_content, file_size=file.file_size, - user_id=file.user_id, created_at=file.created_at, updated_at=file.updated_at, ) diff --git a/src/backend/services/file.py b/src/backend/services/file.py index 74fac7bf8a..b1fdf35431 100644 --- a/src/backend/services/file.py +++ b/src/backend/services/file.py @@ -118,49 +118,65 @@ async def create_agent_files( return uploaded_files - def get_files_by_agent_id( + def get_file_ids_by_agent_id( self, session: DBSessionDep, user_id: str, agent_id: str, ctx: Context - ) -> list[File]: + ) -> list[str]: """ - Get files by agent ID + Get file IDs associated with a specific agent ID Args: session (DBSessionDep): The database session user_id (str): The user ID agent_id (str): The agent ID + ctx (Context): Context object Returns: - list[File]: The files that were created + list[str]: IDs of files that were created """ from backend.config.tools import ToolName from backend.tools.files import FileToolsArtifactTypes agent = validate_agent_exists(session, agent_id, user_id) - files = [] - agent_tool_metadata = agent.tools_metadata - if agent_tool_metadata is not None and len(agent_tool_metadata) > 0: - artifacts = next( - ( - tool_metadata.artifacts - for tool_metadata in agent_tool_metadata - if tool_metadata.tool_name == ToolName.Read_File - or tool_metadata.tool_name == ToolName.Search_File - ), - [], # Default value if the generator is empty - ) + if not agent.tools_metadata: + return [] - file_ids = list( - { - artifact.get("id") - for artifact in artifacts - if artifact.get("type") == FileToolsArtifactTypes.local_file - } - ) + artifacts = next( + ( + tool_metadata.artifacts + for tool_metadata in agent.tools_metadata + if tool_metadata.tool_name == ToolName.Read_File or tool_metadata.tool_name == ToolName.Search_File + ), + [], # Default value if the generator is empty + ) - files = file_crud.get_files_by_ids(session, file_ids, user_id) + return [ + artifact.get("id") + for artifact in artifacts + if artifact.get("type") == FileToolsArtifactTypes.local_file + ] - return files + def get_files_by_agent_id( + self, session: DBSessionDep, user_id: str, agent_id: str, ctx: Context + ) -> list[File]: + """ + Get files by agent ID + + Args: + session (DBSessionDep): The database session + user_id (str): The user ID + agent_id (str): The agent ID + ctx (Context): Context object + + Returns: + list[File]: The files that were created + """ + file_ids = self.get_file_ids_by_agent_id(session, user_id, agent_id, ctx) + + if not file_ids: + return [] + + return file_crud.get_files_by_ids(session, file_ids, user_id) def get_files_by_conversation_id( self, session: DBSessionDep, user_id: str, conversation_id: str, ctx: Context From fecef10f245bf0a7af60c6ec8c50bcfaca6ed088 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Fri, 25 Oct 2024 21:45:07 +0300 Subject: [PATCH 06/15] Add unit tests --- src/backend/routers/conversation.py | 1 + .../tests/unit/routers/test_conversation.py | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/backend/routers/conversation.py b/src/backend/routers/conversation.py index 33c13d93aa..0abf0dc47a 100644 --- a/src/backend/routers/conversation.py +++ b/src/backend/routers/conversation.py @@ -499,6 +499,7 @@ async def get_file( file_name=file.file_name, file_content=file.file_content, file_size=file.file_size, + user_id=file.user_id, created_at=file.created_at, updated_at=file.updated_at, ) diff --git a/src/backend/tests/unit/routers/test_conversation.py b/src/backend/tests/unit/routers/test_conversation.py index 28b4a917f8..915ff5df16 100644 --- a/src/backend/tests/unit/routers/test_conversation.py +++ b/src/backend/tests/unit/routers/test_conversation.py @@ -583,6 +583,57 @@ def test_list_files_missing_user_id( assert response.json() == {"detail": "User-Id required in request headers."} +def test_get_file( + session_client: TestClient, session: Session, user: User +) -> None: + conversation = get_factory("Conversation", session).create(user_id=user.id) + response = session_client.post( + "/v1/conversations/batch_upload_file", + headers={"User-Id": conversation.user_id}, + files=[ + ("files", ("Mariana_Trench.pdf", open("src/backend/tests/unit/test_data/Mariana_Trench.pdf", "rb"))) + ], + data={"conversation_id": conversation.id}, + ) + assert response.status_code == 200 + uploaded_file = response.json()[0] + + response = session_client.get( + f"/v1/conversations/{conversation.id}/files/{uploaded_file['id']}", + headers={"User-Id": conversation.user_id}, + ) + + assert response.status_code == 200 + response_file = response.json() + assert response_file["id"] == uploaded_file["id"] + assert response_file["file_name"] == uploaded_file["file_name"] + + +def test_fail_get_file_nonexistent_conversation( + session_client: TestClient, session: Session, user: User +) -> None: + response = session_client.get( + "/v1/conversations/123/files/456", + headers={"User-Id": user.id}, + ) + + assert response.status_code == 404 + assert response.json() == {"detail": "Conversation with ID: 123 not found."} + + +def test_fail_get_file_nonbelong_file( + session_client: TestClient, session: Session, user: User +) -> None: + conversation = get_factory("Conversation", session).create(user_id=user.id) + response = session_client.get( + f"/v1/conversations/{conversation.id}/files/123", + headers={"User-Id": conversation.user_id}, + ) + + assert response.status_code == 404 + assert response.json() == {"detail": f"File with ID: 123 does not belong to the conversation with ID: {conversation.id}."} + + def test_batch_upload_file_existing_conversation( session_client: TestClient, session: Session, user ) -> None: From 2c64fcbd6acc7ad6705ace1892cb5af471884296 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Fri, 25 Oct 2024 21:53:36 +0300 Subject: [PATCH 07/15] Generate web client --- src/backend/routers/agent.py | 19 +++- src/backend/routers/conversation.py | 2 +- .../cohere-client/generated/schemas.gen.ts | 88 +++++++++++++++++++ .../cohere-client/generated/services.gen.ts | 78 ++++++++++++++++ .../src/cohere-client/generated/types.gen.ts | 60 +++++++++++++ 5 files changed, 244 insertions(+), 3 deletions(-) diff --git a/src/backend/routers/agent.py b/src/backend/routers/agent.py index f677ac9d6e..852f6f6ea3 100644 --- a/src/backend/routers/agent.py +++ b/src/backend/routers/agent.py @@ -638,13 +638,28 @@ async def batch_upload_file( return uploaded_files -@router.get("/{agent_id}/files/{file_id}") +@router.get("/{agent_id}/files/{file_id}", response_model=AgentFileFull) async def get_agent_file( agent_id: str, file_id: str, session: DBSessionDep, ctx: Context = Depends(get_context), -): +) -> AgentFileFull: + """ + Get an agent file by ID. + + Args: + agent_id (str): Agent ID. + file_id (str): File ID. + session (DBSessionDep): Database session. + ctx (Context): Context object. + + Returns: + AgentFileFull: File with the given ID. + + Raises: + HTTPException: If the agent or file with the given ID is not found, or if the file does not belong to the agent. + """ user_id = ctx.get_user_id() if file_id not in get_file_service().get_file_ids_by_agent_id(session, user_id, agent_id, ctx): diff --git a/src/backend/routers/conversation.py b/src/backend/routers/conversation.py index 0abf0dc47a..d6f69d416e 100644 --- a/src/backend/routers/conversation.py +++ b/src/backend/routers/conversation.py @@ -467,7 +467,7 @@ async def get_file( conversation_id: str, file_id: str, session: DBSessionDep, ctx: Context = Depends(get_context) ) -> ConversationFileFull: """ - Get a file by ID. + Get a conversation file by ID. Args: conversation_id (str): Conversation ID. diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts index 81dfd72bcd..c07733e17d 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts @@ -1,5 +1,41 @@ // This file is auto-generated by @hey-api/openapi-ts +export const $AgentFileFull = { + properties: { + id: { + type: 'string', + title: 'Id', + }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At', + }, + updated_at: { + type: 'string', + format: 'date-time', + title: 'Updated At', + }, + file_name: { + type: 'string', + title: 'File Name', + }, + file_size: { + type: 'integer', + minimum: 0, + title: 'File Size', + default: 0, + }, + file_content: { + type: 'string', + title: 'File Content', + }, + }, + type: 'object', + required: ['id', 'created_at', 'updated_at', 'file_name', 'file_content'], + title: 'AgentFileFull', +} as const; + export const $AgentPublic = { properties: { user_id: { @@ -756,6 +792,58 @@ export const $CohereChatRequest = { See: https://github.com/cohere-ai/cohere-python/blob/main/src/cohere/base_client.py#L1629`, } as const; +export const $ConversationFileFull = { + properties: { + id: { + type: 'string', + title: 'Id', + }, + user_id: { + type: 'string', + title: 'User Id', + }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At', + }, + updated_at: { + type: 'string', + format: 'date-time', + title: 'Updated At', + }, + conversation_id: { + type: 'string', + title: 'Conversation Id', + }, + file_name: { + type: 'string', + title: 'File Name', + }, + file_size: { + type: 'integer', + minimum: 0, + title: 'File Size', + default: 0, + }, + file_content: { + type: 'string', + title: 'File Content', + }, + }, + type: 'object', + required: [ + 'id', + 'user_id', + 'created_at', + 'updated_at', + 'conversation_id', + 'file_name', + 'file_content', + ], + title: 'ConversationFileFull', +} as const; + export const $ConversationFilePublic = { properties: { id: { diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts index f281bd5624..031e9a8e26 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts @@ -63,10 +63,14 @@ import type { GetAgentByIdV1AgentsAgentIdGetResponse, GetAgentDeploymentsV1AgentsAgentIdDeploymentsGetData, GetAgentDeploymentsV1AgentsAgentIdDeploymentsGetResponse, + GetAgentFileV1AgentsAgentIdFilesFileIdGetData, + GetAgentFileV1AgentsAgentIdFilesFileIdGetResponse, GetConversationV1ConversationsConversationIdGetData, GetConversationV1ConversationsConversationIdGetResponse, GetDeploymentV1DeploymentsDeploymentIdGetData, GetDeploymentV1DeploymentsDeploymentIdGetResponse, + GetFileV1ConversationsConversationIdFilesFileIdGetData, + GetFileV1ConversationsConversationIdFilesFileIdGetResponse, GetGroupScimV2GroupsGroupIdGetData, GetGroupScimV2GroupsGroupIdGetResponse, GetGroupsScimV2GroupsGetData, @@ -868,6 +872,43 @@ export class DefaultService { }); } + /** + * Get File + * Get a conversation file by ID. + * + * Args: + * conversation_id (str): Conversation ID. + * file_id (str): File ID. + * session (DBSessionDep): Database session. + * ctx (Context): Context object. + * + * Returns: + * ConversationFileFull: File with the given ID. + * + * Raises: + * HTTPException: If the conversation or file with the given ID is not found, or if the file does not belong to the conversation. + * @param data The data for the request. + * @param data.conversationId + * @param data.fileId + * @returns ConversationFileFull Successful Response + * @throws ApiError + */ + public getFileV1ConversationsConversationIdFilesFileIdGet( + data: GetFileV1ConversationsConversationIdFilesFileIdGetData + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/v1/conversations/{conversation_id}/files/{file_id}', + path: { + conversation_id: data.conversationId, + file_id: data.fileId, + }, + errors: { + 422: 'Validation Error', + }, + }); + } + /** * Delete File * Delete a file by ID. @@ -1597,6 +1638,43 @@ export class DefaultService { }); } + /** + * Get Agent File + * Get an agent file by ID. + * + * Args: + * agent_id (str): Agent ID. + * file_id (str): File ID. + * session (DBSessionDep): Database session. + * ctx (Context): Context object. + * + * Returns: + * AgentFileFull: File with the given ID. + * + * Raises: + * HTTPException: If the agent or file with the given ID is not found, or if the file does not belong to the agent. + * @param data The data for the request. + * @param data.agentId + * @param data.fileId + * @returns AgentFileFull Successful Response + * @throws ApiError + */ + public getAgentFileV1AgentsAgentIdFilesFileIdGet( + data: GetAgentFileV1AgentsAgentIdFilesFileIdGetData + ): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/v1/agents/{agent_id}/files/{file_id}', + path: { + agent_id: data.agentId, + file_id: data.fileId, + }, + errors: { + 422: 'Validation Error', + }, + }); + } + /** * Delete Agent File * Delete an agent file by ID. diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts index c6cb588614..3d3db38a2b 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts @@ -1,5 +1,14 @@ // This file is auto-generated by @hey-api/openapi-ts +export type AgentFileFull = { + id: string; + created_at: string; + updated_at: string; + file_name: string; + file_size?: number; + file_content: string; +}; + export type AgentPublic = { user_id: string; id: string; @@ -152,6 +161,17 @@ export type CohereChatRequest = { agent_id?: string | null; }; +export type ConversationFileFull = { + id: string; + user_id: string; + created_at: string; + updated_at: string; + conversation_id: string; + file_name: string; + file_size?: number; + file_content: string; +}; + export type ConversationFilePublic = { id: string; user_id: string; @@ -934,6 +954,13 @@ export type ListFilesV1ConversationsConversationIdFilesGetData = { export type ListFilesV1ConversationsConversationIdFilesGetResponse = Array; +export type GetFileV1ConversationsConversationIdFilesFileIdGetData = { + conversationId: string; + fileId: string; +}; + +export type GetFileV1ConversationsConversationIdFilesFileIdGetResponse = ConversationFileFull; + export type DeleteFileV1ConversationsConversationIdFilesFileIdDeleteData = { conversationId: string; fileId: string; @@ -1083,6 +1110,13 @@ export type BatchUploadFileV1AgentsBatchUploadFilePostData = { export type BatchUploadFileV1AgentsBatchUploadFilePostResponse = Array; +export type GetAgentFileV1AgentsAgentIdFilesFileIdGetData = { + agentId: string; + fileId: string; +}; + +export type GetAgentFileV1AgentsAgentIdFilesFileIdGetResponse = AgentFileFull; + export type DeleteAgentFileV1AgentsAgentIdFilesFileIdDeleteData = { agentId: string; fileId: string; @@ -1560,6 +1594,19 @@ export type $OpenApiTs = { }; }; '/v1/conversations/{conversation_id}/files/{file_id}': { + get: { + req: GetFileV1ConversationsConversationIdFilesFileIdGetData; + res: { + /** + * Successful Response + */ + 200: ConversationFileFull; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; delete: { req: DeleteFileV1ConversationsConversationIdFilesFileIdDeleteData; res: { @@ -1871,6 +1918,19 @@ export type $OpenApiTs = { }; }; '/v1/agents/{agent_id}/files/{file_id}': { + get: { + req: GetAgentFileV1AgentsAgentIdFilesFileIdGetData; + res: { + /** + * Successful Response + */ + 200: AgentFileFull; + /** + * Validation Error + */ + 422: HTTPValidationError; + }; + }; delete: { req: DeleteAgentFileV1AgentsAgentIdFilesFileIdDeleteData; res: { From 835742c17264a22f1c951bd0e8902c3ec8735e47 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Wed, 30 Oct 2024 19:22:35 +0200 Subject: [PATCH 08/15] Add API calls --- .../src/cohere-client/client.ts | 20 ++++++++++++++ .../AgentSettingsForm/DataSourcesStep.tsx | 19 +++++++------ .../Conversation/ConversationPanel.tsx | 26 +++++++++++++----- .../src/components/UI/FileViewer.tsx | 15 +++++++++-- .../assistants_web/src/hooks/use-files.ts | 27 ++++++++++++++++++- 5 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/interfaces/assistants_web/src/cohere-client/client.ts b/src/interfaces/assistants_web/src/cohere-client/client.ts index 43d182f69c..2f45f1f9de 100644 --- a/src/interfaces/assistants_web/src/cohere-client/client.ts +++ b/src/interfaces/assistants_web/src/cohere-client/client.ts @@ -53,6 +53,19 @@ export class CohereClient { }); } + public getConversationFile({ + conversationId, + fileId, + }: { + conversationId: string; + fileId: string; + }) { + return this.cohereService.default.getFileV1ConversationsConversationIdFilesFileIdGet({ + conversationId, + fileId, + }); + } + public batchUploadConversationFile( formData: Body_batch_upload_file_v1_conversations_batch_upload_file_post ) { @@ -61,6 +74,13 @@ export class CohereClient { }); } + public getAgentFile({ agentId, fileId }: { agentId: string; fileId: string }) { + return this.cohereService.default.getAgentFileV1AgentsAgentIdFilesFileIdGet({ + agentId, + fileId, + }); + } + public batchUploadAgentFile(formData: Body_batch_upload_file_v1_agents_batch_upload_file_post) { return this.cohereService.default.batchUploadFileV1AgentsBatchUploadFilePost({ formData, diff --git a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx index d6855290a0..3e09588e42 100644 --- a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx +++ b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx @@ -6,7 +6,7 @@ import { Button, Icon, IconButton, IconName, Spinner, Text } from '@/components/ import { FileViewer } from '@/components/UI/FileViewer'; import { ACCEPTED_FILE_TYPES, TOOL_GOOGLE_DRIVE_ID } from '@/constants'; import { useContextStore } from '@/context'; -import { useUploadAgentFile } from '@/hooks'; +import { useChatRoutes, useUploadAgentFile } from '@/hooks'; import { DataSourceArtifact } from '@/types/tools'; import { mapMimeTypeToExtension, pluralize } from '@/utils'; @@ -197,6 +197,7 @@ const DataSourceFileList: React.FC<{ handleRemoveTool: VoidFunction; }> = ({ name, icon, artifacts = [], addFileButton, handleRemoveFile, handleRemoveTool }) => { const { open } = useContextStore(); + const { agentId } = useChatRoutes(); const filesCount = getCountString( 'file', artifacts.filter((artifact) => artifact.type === 'file') @@ -213,7 +214,7 @@ const DataSourceFileList: React.FC<{ window.open(url, '_blank'); } else { open({ - content: , + content: , }); } }; @@ -244,12 +245,14 @@ const DataSourceFileList: React.FC<{ {name} - handleOpenFile(id, url)} - /> + {agentId && ( + handleOpenFile(id, url)} + /> + )} = () => { ...agentToolMetadataArtifacts.folders, ]; - const handleOpenFile = (fileId: string, url?: string) => { + const handleOpenFile = ({ + fileId, + agentId, + conversationId, + url, + }: { + fileId: string; + agentId?: string; + conversationId?: string; + url?: string; + }) => { if (url) { window.open(url, '_blank'); } else { open({ - content: , + content: , }); } }; @@ -186,8 +196,10 @@ export const ConversationPanel: React.FC = () => { handleOpenFile(file.id, file.url)} + className="invisible h-auto w-auto flex-shrink-0 self-center group-hover:visible" + onClick={() => + handleOpenFile({ fileId: file.id, agentId: agent!.id, url: file.url }) + } /> ))} @@ -211,7 +223,7 @@ export const ConversationPanel: React.FC = () => {
{files && files.length > 0 && (
- {files.map(({ file_name: name, id }) => ( + {files.map(({ id, conversation_id, file_name: name }) => (
@@ -228,7 +240,9 @@ export const ConversationPanel: React.FC = () => { tooltip={{ label: 'Show content' }} className="h-auto w-auto flex-shrink-0 self-center" disabled={isDeletingFile} - onClick={() => handleOpenFile(id)} + onClick={() => + handleOpenFile({ fileId: id, conversationId: conversation_id }) + } /> = ({ fileId }) => { - return
{fileId}
; +export const FileViewer: React.FC = ({ fileId, agentId, conversationId }) => { + const { data: file, isLoading } = useFile({ fileId, agentId, conversationId }); + + if (isLoading) { + return ; + } + + return
{file!.file_name}
; }; diff --git a/src/interfaces/assistants_web/src/hooks/use-files.ts b/src/interfaces/assistants_web/src/hooks/use-files.ts index fcb6122d86..b2c8fed4a4 100644 --- a/src/interfaces/assistants_web/src/hooks/use-files.ts +++ b/src/interfaces/assistants_web/src/hooks/use-files.ts @@ -1,7 +1,9 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { UseQueryResult, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { + AgentFileFull, ApiError, + ConversationFileFull, DeleteAgentFileResponse, ListConversationFile, useCohereClient, @@ -12,6 +14,29 @@ import { useConversationStore, useFilesStore, useParamsStore } from '@/stores'; import { UploadingFile } from '@/stores/slices/filesSlice'; import { fileSizeToBytes, formatFileSize, getFileExtension, mapExtensionToMimeType } from '@/utils'; +export const useFile = ({ + fileId, + agentId, + conversationId, +}: { + fileId: string; + agentId?: string; + conversationId?: string; +}): UseQueryResult => { + const cohereClient = useCohereClient(); + return useQuery({ + queryKey: ['file', fileId], + queryFn: async () => { + if ((!agentId && !conversationId) || (agentId && conversationId)) { + throw new Error('Exactly one of agentId or conversationId must be provided'); + } + return agentId + ? await cohereClient.getAgentFile({ agentId: agentId!, fileId }) + : await cohereClient.getConversationFile({ conversationId: conversationId!, fileId }); + }, + }); +}; + export const useListConversationFiles = ( conversationId?: string, options?: { enabled?: boolean } From bc34a3dd1c2c2d87cada92adb774da2923effcec Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Thu, 31 Oct 2024 01:51:32 +0200 Subject: [PATCH 09/15] Add content for FileViewer --- .../src/components/UI/FileViewer.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx index 38bc11c742..071fc93c7a 100644 --- a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx +++ b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx @@ -1,4 +1,7 @@ +import { Markdown } from '@/components/Markdown'; +import { Icon } from '@/components/UI/Icon'; import { Spinner } from '@/components/UI/Spinner'; +import { Text } from '@/components/UI/Text'; import { useFile } from '@/hooks'; type Props = { @@ -14,5 +17,19 @@ export const FileViewer: React.FC = ({ fileId, agentId, conversationId }) return ; } - return
{file!.file_name}
; + return ( +
+
+
+ +
+ + {file!.file_name} + +
+
+ +
+
+ ); }; From 94e128d107b20bd20fe24d8a49e8a812c46f2d5f Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Thu, 31 Oct 2024 02:03:54 +0200 Subject: [PATCH 10/15] Add padding settings for modals --- .../AgentSettingsForm/DataSourcesStep.tsx | 1 + .../Conversation/ConversationPanel.tsx | 1 + .../src/components/UI/Modal.tsx | 5 ++++- .../src/context/ModalContext.tsx | 22 ++++++++++++++----- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx index 3e09588e42..dd5a0d470d 100644 --- a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx +++ b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx @@ -215,6 +215,7 @@ const DataSourceFileList: React.FC<{ } else { open({ content: , + dialogPaddingClassName: 'p-5', }); } }; diff --git a/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx b/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx index 47e2754392..3bc3a0dd54 100644 --- a/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx +++ b/src/interfaces/assistants_web/src/components/Conversation/ConversationPanel.tsx @@ -88,6 +88,7 @@ export const ConversationPanel: React.FC = () => { } else { open({ content: , + dialogPaddingClassName: 'p-5', }); } }; diff --git a/src/interfaces/assistants_web/src/components/UI/Modal.tsx b/src/interfaces/assistants_web/src/components/UI/Modal.tsx index 0271f6c345..b45232d249 100644 --- a/src/interfaces/assistants_web/src/components/UI/Modal.tsx +++ b/src/interfaces/assistants_web/src/components/UI/Modal.tsx @@ -11,6 +11,7 @@ type ModalProps = { title?: string; children?: React.ReactNode; onClose?: VoidFunction; + dialogPaddingClassName?: string; }; /** @@ -21,6 +22,7 @@ export const Modal: React.FC = ({ isOpen, children, onClose = () => {}, + dialogPaddingClassName, }) => { return ( @@ -60,7 +62,8 @@ export const Modal: React.FC = ({ {children && ( void; @@ -18,6 +19,7 @@ interface Context { open: OpenFunction; close: CloseFunction; content: React.ReactNode | React.FC; + dialogPaddingClassName?: string; } /** @@ -27,18 +29,22 @@ const useModal = (): Context => { const [isOpen, setIsOpen] = useState(false); const [title, setTitle] = useState(undefined); const [content, setContent] = useState(undefined); + const [dialogPaddingClassName, setDialogPaddingClassName] = useState( + undefined + ); - const open = ({ title, content }: OpenParams) => { + const open = ({ title, content, dialogPaddingClassName }: OpenParams) => { setIsOpen(true); setTitle(title); setContent(content); + setDialogPaddingClassName(dialogPaddingClassName); }; const close = () => { setIsOpen(false); }; - return { isOpen, open, close, content, title }; + return { isOpen, open, close, content, title, dialogPaddingClassName }; }; /** @@ -56,15 +62,21 @@ const ModalContext = createContext({ open: () => {}, close: () => {}, content: undefined, + dialogPaddingClassName: undefined, }); const ModalProvider: React.FC = ({ children }) => { - const { isOpen, title, open, close, content } = useModal(); + const { isOpen, title, open, close, content, dialogPaddingClassName } = useModal(); return ( - + <>{children} - + <>{content} From d0dcfc84103a01c39e82dd53781edbbffb8acfc7 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Thu, 31 Oct 2024 02:07:50 +0200 Subject: [PATCH 11/15] Refactor styles --- src/interfaces/assistants_web/src/components/UI/FileViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx index 071fc93c7a..6d312345c3 100644 --- a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx +++ b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx @@ -20,7 +20,7 @@ export const FileViewer: React.FC = ({ fileId, agentId, conversationId }) return (
-
+
From 78c8407af13a3a443dde75e9d265ea18c52bd8e4 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Thu, 31 Oct 2024 02:23:32 +0200 Subject: [PATCH 12/15] Minor clean up --- .../AgentSettingsForm/DataSourcesStep.tsx | 35 +++---------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx index dd5a0d470d..cb7fa964b3 100644 --- a/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx +++ b/src/interfaces/assistants_web/src/components/AgentSettingsForm/DataSourcesStep.tsx @@ -2,11 +2,9 @@ import { uniqBy } from 'lodash'; import Link from 'next/link'; import { Dispatch, ReactNode, RefObject, SetStateAction, useRef } from 'react'; -import { Button, Icon, IconButton, IconName, Spinner, Text } from '@/components/UI'; -import { FileViewer } from '@/components/UI/FileViewer'; +import { Button, Icon, IconName, Spinner, Text } from '@/components/UI'; import { ACCEPTED_FILE_TYPES, TOOL_GOOGLE_DRIVE_ID } from '@/constants'; -import { useContextStore } from '@/context'; -import { useChatRoutes, useUploadAgentFile } from '@/hooks'; +import { useUploadAgentFile } from '@/hooks'; import { DataSourceArtifact } from '@/types/tools'; import { mapMimeTypeToExtension, pluralize } from '@/utils'; @@ -196,8 +194,6 @@ const DataSourceFileList: React.FC<{ handleRemoveFile: (id: string) => void; handleRemoveTool: VoidFunction; }> = ({ name, icon, artifacts = [], addFileButton, handleRemoveFile, handleRemoveTool }) => { - const { open } = useContextStore(); - const { agentId } = useChatRoutes(); const filesCount = getCountString( 'file', artifacts.filter((artifact) => artifact.type === 'file') @@ -209,17 +205,6 @@ const DataSourceFileList: React.FC<{ const countCopy = [filesCount, foldersCount].filter((text) => !!text).join(', '); - const handleOpenFile = (fileId: string, url?: string) => { - if (url) { - window.open(url, '_blank'); - } else { - open({ - content: , - dialogPaddingClassName: 'p-5', - }); - } - }; - return (
@@ -237,7 +222,7 @@ const DataSourceFileList: React.FC<{
- {artifacts.map(({ id, type, name, url }) => ( + {artifacts.map(({ id, type, name }) => (
{name} - {agentId && ( - handleOpenFile(id, url)} - /> - )} - handleRemoveFile(id)} - /> +
))} From e60a1eaa894b5ca639021e66d82d7e52b20d59ec Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Thu, 31 Oct 2024 02:27:45 +0200 Subject: [PATCH 13/15] Run format-web --- src/interfaces/assistants_web/src/components/UI/FileViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx index 6d312345c3..071fc93c7a 100644 --- a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx +++ b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx @@ -20,7 +20,7 @@ export const FileViewer: React.FC = ({ fileId, agentId, conversationId }) return (
-
+
From 48c6df4e0ad62f30bf4fe36a569d01b4d926b9ef Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Tue, 12 Nov 2024 21:25:25 +0200 Subject: [PATCH 14/15] Merge AgentFileFull and ConversationFileFull --- src/backend/routers/agent.py | 10 +- src/backend/routers/conversation.py | 12 +- src/backend/schemas/file.py | 11 +- .../cohere-client/generated/schemas.gen.ts | 124 +++++------------- .../cohere-client/generated/services.gen.ts | 8 +- .../src/cohere-client/generated/types.gen.ts | 37 ++---- .../assistants_web/src/hooks/use-files.ts | 6 +- 7 files changed, 71 insertions(+), 137 deletions(-) diff --git a/src/backend/routers/agent.py b/src/backend/routers/agent.py index aa10355b69..65c8d19efa 100644 --- a/src/backend/routers/agent.py +++ b/src/backend/routers/agent.py @@ -36,8 +36,8 @@ from backend.schemas.context import Context from backend.schemas.deployment import Deployment as DeploymentSchema from backend.schemas.file import ( - AgentFileFull, DeleteAgentFileResponse, + FileMetadata, UploadAgentFileResponse, ) from backend.services.agent import ( @@ -588,13 +588,13 @@ async def batch_upload_file( return uploaded_files -@router.get("/{agent_id}/files/{file_id}", response_model=AgentFileFull) +@router.get("/{agent_id}/files/{file_id}", response_model=FileMetadata) async def get_agent_file( agent_id: str, file_id: str, session: DBSessionDep, ctx: Context = Depends(get_context), -) -> AgentFileFull: +) -> FileMetadata: """ Get an agent file by ID. @@ -605,7 +605,7 @@ async def get_agent_file( ctx (Context): Context object. Returns: - AgentFileFull: File with the given ID. + FileMetadata: File with the given ID. Raises: HTTPException: If the agent or file with the given ID is not found, or if the file does not belong to the agent. @@ -626,7 +626,7 @@ async def get_agent_file( detail=f"File with ID: {file_id} not found.", ) - return AgentFileFull( + return FileMetadata( id=file.id, file_name=file.file_name, file_content=file.file_content, diff --git a/src/backend/routers/conversation.py b/src/backend/routers/conversation.py index d6f69d416e..537bbe798f 100644 --- a/src/backend/routers/conversation.py +++ b/src/backend/routers/conversation.py @@ -23,8 +23,8 @@ UpdateConversationRequest, ) from backend.schemas.file import ( - ConversationFileFull, DeleteConversationFileResponse, + FileMetadata, ListConversationFile, UploadConversationFileResponse, ) @@ -462,10 +462,10 @@ async def list_files( return files_with_conversation_id -@router.get("/{conversation_id}/files/{file_id}", response_model=ConversationFileFull) +@router.get("/{conversation_id}/files/{file_id}", response_model=FileMetadata) async def get_file( conversation_id: str, file_id: str, session: DBSessionDep, ctx: Context = Depends(get_context) -) -> ConversationFileFull: +) -> FileMetadata: """ Get a conversation file by ID. @@ -476,7 +476,7 @@ async def get_file( ctx (Context): Context object. Returns: - ConversationFileFull: File with the given ID. + FileMetadata: File with the given ID. Raises: HTTPException: If the conversation or file with the given ID is not found, or if the file does not belong to the conversation. @@ -493,13 +493,11 @@ async def get_file( file = validate_file(session, file_id, user_id) - return ConversationFileFull( + return FileMetadata( id=file.id, - conversation_id=conversation.id, file_name=file.file_name, file_content=file.file_content, file_size=file.file_size, - user_id=file.user_id, created_at=file.created_at, updated_at=file.updated_at, ) diff --git a/src/backend/schemas/file.py b/src/backend/schemas/file.py index 8963cc3794..affd632be6 100644 --- a/src/backend/schemas/file.py +++ b/src/backend/schemas/file.py @@ -30,10 +30,6 @@ class ConversationFilePublic(BaseModel): file_size: int = Field(default=0, ge=0) -class ConversationFileFull(ConversationFilePublic): - file_content: str - - class AgentFilePublic(BaseModel): id: str created_at: datetime.datetime @@ -43,8 +39,13 @@ class AgentFilePublic(BaseModel): file_size: int = Field(default=0, ge=0) -class AgentFileFull(AgentFilePublic): +class FileMetadata(BaseModel): + id: str + file_name: str file_content: str + file_size: int = Field(default=0, ge=0) + created_at: datetime.datetime + updated_at: datetime.datetime class ListConversationFile(ConversationFilePublic): diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts index 2cdd3e7f55..d517eda38d 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/schemas.gen.ts @@ -1,41 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -export const $AgentFileFull = { - properties: { - id: { - type: 'string', - title: 'Id', - }, - created_at: { - type: 'string', - format: 'date-time', - title: 'Created At', - }, - updated_at: { - type: 'string', - format: 'date-time', - title: 'Updated At', - }, - file_name: { - type: 'string', - title: 'File Name', - }, - file_size: { - type: 'integer', - minimum: 0, - title: 'File Size', - default: 0, - }, - file_content: { - type: 'string', - title: 'File Content', - }, - }, - type: 'object', - required: ['id', 'created_at', 'updated_at', 'file_name', 'file_content'], - title: 'AgentFileFull', -} as const; - export const $AgentPublic = { properties: { user_id: { @@ -778,58 +742,6 @@ export const $CohereChatRequest = { See: https://github.com/cohere-ai/cohere-python/blob/main/src/cohere/base_client.py#L1629`, } as const; -export const $ConversationFileFull = { - properties: { - id: { - type: 'string', - title: 'Id', - }, - user_id: { - type: 'string', - title: 'User Id', - }, - created_at: { - type: 'string', - format: 'date-time', - title: 'Created At', - }, - updated_at: { - type: 'string', - format: 'date-time', - title: 'Updated At', - }, - conversation_id: { - type: 'string', - title: 'Conversation Id', - }, - file_name: { - type: 'string', - title: 'File Name', - }, - file_size: { - type: 'integer', - minimum: 0, - title: 'File Size', - default: 0, - }, - file_content: { - type: 'string', - title: 'File Content', - }, - }, - type: 'object', - required: [ - 'id', - 'user_id', - 'created_at', - 'updated_at', - 'conversation_id', - 'file_name', - 'file_content', - ], - title: 'ConversationFileFull', -} as const; - export const $ConversationFilePublic = { properties: { id: { @@ -1604,6 +1516,42 @@ export const $Email = { title: 'Email', } as const; +export const $FileMetadata = { + properties: { + id: { + type: 'string', + title: 'Id', + }, + file_name: { + type: 'string', + title: 'File Name', + }, + file_content: { + type: 'string', + title: 'File Content', + }, + file_size: { + type: 'integer', + minimum: 0, + title: 'File Size', + default: 0, + }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At', + }, + updated_at: { + type: 'string', + format: 'date-time', + title: 'Updated At', + }, + }, + type: 'object', + required: ['id', 'file_name', 'file_content', 'created_at', 'updated_at'], + title: 'FileMetadata', +} as const; + export const $GenerateTitleResponse = { properties: { title: { diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts index 64fe28e880..a84f49c81e 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/services.gen.ts @@ -883,14 +883,14 @@ export class DefaultService { * ctx (Context): Context object. * * Returns: - * ConversationFileFull: File with the given ID. + * FileMetadata: File with the given ID. * * Raises: * HTTPException: If the conversation or file with the given ID is not found, or if the file does not belong to the conversation. * @param data The data for the request. * @param data.conversationId * @param data.fileId - * @returns ConversationFileFull Successful Response + * @returns FileMetadata Successful Response * @throws ApiError */ public getFileV1ConversationsConversationIdFilesFileIdGet( @@ -1649,14 +1649,14 @@ export class DefaultService { * ctx (Context): Context object. * * Returns: - * AgentFileFull: File with the given ID. + * FileMetadata: File with the given ID. * * Raises: * HTTPException: If the agent or file with the given ID is not found, or if the file does not belong to the agent. * @param data The data for the request. * @param data.agentId * @param data.fileId - * @returns AgentFileFull Successful Response + * @returns FileMetadata Successful Response * @throws ApiError */ public getAgentFileV1AgentsAgentIdFilesFileIdGet( diff --git a/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts b/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts index 0464399d64..860fee09a0 100644 --- a/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts +++ b/src/interfaces/assistants_web/src/cohere-client/generated/types.gen.ts @@ -1,14 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -export type AgentFileFull = { - id: string; - created_at: string; - updated_at: string; - file_name: string; - file_size?: number; - file_content: string; -}; - export type AgentPublic = { user_id: string; id: string; @@ -153,17 +144,6 @@ export type CohereChatRequest = { agent_id?: string | null; }; -export type ConversationFileFull = { - id: string; - user_id: string; - created_at: string; - updated_at: string; - conversation_id: string; - file_name: string; - file_size?: number; - file_content: string; -}; - export type ConversationFilePublic = { id: string; user_id: string; @@ -316,6 +296,15 @@ export type Email = { type: string; }; +export type FileMetadata = { + id: string; + file_name: string; + file_content: string; + file_size?: number; + created_at: string; + updated_at: string; +}; + export type GenerateTitleResponse = { title: string; error?: string | null; @@ -935,7 +924,7 @@ export type GetFileV1ConversationsConversationIdFilesFileIdGetData = { fileId: string; }; -export type GetFileV1ConversationsConversationIdFilesFileIdGetResponse = ConversationFileFull; +export type GetFileV1ConversationsConversationIdFilesFileIdGetResponse = FileMetadata; export type DeleteFileV1ConversationsConversationIdFilesFileIdDeleteData = { conversationId: string; @@ -1091,7 +1080,7 @@ export type GetAgentFileV1AgentsAgentIdFilesFileIdGetData = { fileId: string; }; -export type GetAgentFileV1AgentsAgentIdFilesFileIdGetResponse = AgentFileFull; +export type GetAgentFileV1AgentsAgentIdFilesFileIdGetResponse = FileMetadata; export type DeleteAgentFileV1AgentsAgentIdFilesFileIdDeleteData = { agentId: string; @@ -1576,7 +1565,7 @@ export type $OpenApiTs = { /** * Successful Response */ - 200: ConversationFileFull; + 200: FileMetadata; /** * Validation Error */ @@ -1900,7 +1889,7 @@ export type $OpenApiTs = { /** * Successful Response */ - 200: AgentFileFull; + 200: FileMetadata; /** * Validation Error */ diff --git a/src/interfaces/assistants_web/src/hooks/use-files.ts b/src/interfaces/assistants_web/src/hooks/use-files.ts index b2c8fed4a4..5997b68336 100644 --- a/src/interfaces/assistants_web/src/hooks/use-files.ts +++ b/src/interfaces/assistants_web/src/hooks/use-files.ts @@ -1,9 +1,7 @@ -import { UseQueryResult, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { - AgentFileFull, ApiError, - ConversationFileFull, DeleteAgentFileResponse, ListConversationFile, useCohereClient, @@ -22,7 +20,7 @@ export const useFile = ({ fileId: string; agentId?: string; conversationId?: string; -}): UseQueryResult => { +}) => { const cohereClient = useCohereClient(); return useQuery({ queryKey: ['file', fileId], From b2274cd59dff213482a8f0cb4e30b854c8de4845 Mon Sep 17 00:00:00 2001 From: Danylo Boiko Date: Tue, 12 Nov 2024 21:59:14 +0200 Subject: [PATCH 15/15] Add error message --- .../assistants_web/src/components/UI/FileViewer.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx index 071fc93c7a..76df78fd8b 100644 --- a/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx +++ b/src/interfaces/assistants_web/src/components/UI/FileViewer.tsx @@ -21,15 +21,17 @@ export const FileViewer: React.FC = ({ fileId, agentId, conversationId })
- +
- {file!.file_name} + {file?.file_name ?? 'Failed to load file content'}
-
- -
+ {file && ( +
+ +
+ )}
); };