fix: attach file information into message metadata for a quick retrieval (#4298)

This commit is contained in:
Louis 2024-12-19 22:01:13 +07:00 committed by GitHub
parent 4489af6ad9
commit a1ea94aeca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 79 additions and 51 deletions

View File

@ -30,20 +30,23 @@ describe('ErrorMessage Component', () => {
beforeEach(() => {
jest.clearAllMocks()
;(useAtomValue as jest.Mock).mockReturnValue([])
;(useSetAtom as jest.Mock).mockReturnValue(mockSetMainState)
;(useSetAtom as jest.Mock).mockReturnValue(mockSetSelectedSettingScreen)
;(useSetAtom as jest.Mock).mockReturnValue(mockSetModalTroubleShooting)
;(useSendChatMessage as jest.Mock).mockReturnValue({
resendChatMessage: mockResendChatMessage,
})
; (useAtomValue as jest.Mock).mockReturnValue([])
; (useSetAtom as jest.Mock).mockReturnValue(mockSetMainState)
; (useSetAtom as jest.Mock).mockReturnValue(mockSetSelectedSettingScreen)
; (useSetAtom as jest.Mock).mockReturnValue(mockSetModalTroubleShooting)
; (useSendChatMessage as jest.Mock).mockReturnValue({
resendChatMessage: mockResendChatMessage,
})
})
it('renders error message with InvalidApiKey correctly', () => {
const message: ThreadMessage = {
id: '1',
status: MessageStatus.Error,
error_code: ErrorCode.InvalidApiKey,
metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.InvalidApiKey,
},
status: "completed",
content: [{ text: { value: 'Invalid API Key' } }],
} as ThreadMessage
@ -56,8 +59,11 @@ describe('ErrorMessage Component', () => {
it('renders general error message correctly', () => {
const message: ThreadMessage = {
id: '1',
status: MessageStatus.Error,
error_code: ErrorCode.Unknown,
status: "completed",
metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.Unknown
},
content: [{ text: { value: 'Unknown error occurred' } }],
} as ThreadMessage
@ -69,9 +75,11 @@ describe('ErrorMessage Component', () => {
it('opens troubleshooting modal when link is clicked', () => {
const message: ThreadMessage = {
id: '1',
status: MessageStatus.Error,
error_code: ErrorCode.Unknown,
content: [{ text: { value: 'Unknown error occurred' } }],
status: "completed",
metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.Unknown,
}, content: [{ text: { value: 'Unknown error occurred' } }],
} as ThreadMessage
render(<ErrorMessage message={message} />)

View File

@ -53,7 +53,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
const getErrorTitle = () => {
const engine = getEngine()
switch (message.error_code) {
switch (message.metadata?.error_code) {
case ErrorCode.InvalidApiKey:
case ErrorCode.AuthenticationError:
return (
@ -102,7 +102,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
return (
<div className="mx-auto my-6 max-w-[700px]">
{message.status === MessageStatus.Error && (
{!!message.metadata?.error && (
<div
key={message.id}
className="mx-6 flex flex-col items-center space-y-2 text-center font-medium text-[hsla(var(--text-secondary))]"

View File

@ -257,10 +257,19 @@ export default function ModelHandler() {
...thread,
metadata,
})
if (message.status === MessageStatus.Error) {
message.metadata = {
...message.metadata,
error: message.content[0]?.text?.value,
error_code: message.error_code,
}
}
;(async () => {
const updatedMessage = await extensionManager
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.createMessage(message)
.catch(() => undefined)
if (updatedMessage) {
deleteMessage(message.id)
addNewMessage(updatedMessage)

View File

@ -141,7 +141,7 @@ export const deleteMessageAtom = atom(null, (get, set, id: string) => {
if (threadId) {
// Should also delete error messages to clear out the error state
newData[threadId] = newData[threadId].filter(
(e) => e.id !== id && e.status !== MessageStatus.Error
(e) => e.id !== id && !e.metadata?.error
)
set(chatMessages, newData)

View File

@ -230,8 +230,10 @@ export const useCreateNewThread = () => {
await extensionManager
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.createThreadAssistant(thread.id, assistantInfo)
.catch(console.error)
return thread
})
.catch(() => undefined)
}
return {

View File

@ -1,4 +1,4 @@
import { openFileExplorer, joinPath, baseName, fs } from '@janhq/core'
import { openFileExplorer, joinPath, baseName } from '@janhq/core'
import { useAtomValue } from 'jotai'
import { getFileInfo } from '@/utils/file'

View File

@ -200,6 +200,7 @@ export default function useSendChatMessage() {
const createdMessage = await extensionManager
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.createMessage(newMessage)
.catch(() => undefined)
if (!createdMessage) return

View File

@ -23,9 +23,7 @@ const ChatItem = forwardRef<Ref, Props>((message, ref) => {
const [content, setContent] = useState<ThreadContent[]>(message.content)
const [status, setStatus] = useState<MessageStatus>(message.status)
const [errorMessage, setErrorMessage] = useState<ThreadMessage | undefined>(
message.isCurrentMessage && message.status === MessageStatus.Error
? message
: undefined
message.isCurrentMessage && !!message?.metadata?.error ? message : undefined
)
function onMessageUpdate(data: ThreadMessage) {
@ -52,16 +50,18 @@ const ChatItem = forwardRef<Ref, Props>((message, ref) => {
return (
<>
{status !== MessageStatus.Error && content?.length > 0 && (
<div ref={ref} className="relative">
<MessageContainer
{...message}
content={content}
status={status}
isCurrentMessage={message.isCurrentMessage ?? false}
/>
</div>
)}
{status !== MessageStatus.Error &&
!message.metadata?.error &&
content?.length > 0 && (
<div ref={ref} className="relative">
<MessageContainer
{...message}
content={content}
status={status}
isCurrentMessage={message.isCurrentMessage ?? false}
/>
</div>
)}
{errorMessage && !message.loadModelError && (
<ErrorMessage message={errorMessage} />
)}

View File

@ -89,7 +89,7 @@ const EditChatInput: React.FC<Props> = ({ message }) => {
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.deleteMessage(message.thread_id, message.id)
)
)
).catch(console.error)
setMessages(threadId, newMessages)
sendChatMessage(editPrompt, false, newMessages)
}

View File

@ -109,7 +109,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
)}
{message.id === messages[messages.length - 1]?.id &&
messages[messages.length - 1].status !== MessageStatus.Error &&
!messages[messages.length - 1]?.metadata?.error &&
!messages[messages.length - 1].attachments?.length && (
<div
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2"

View File

@ -1,23 +1,19 @@
import { memo, useEffect, useState } from 'react'
import { memo } from 'react'
import { usePath } from '@/hooks/usePath'
import { getFileInfo } from '@/utils/file'
import { toGibibytes } from '@/utils/converter'
import Icon from '../FileUploadPreview/Icon'
const DocMessage = ({ id }: { id: string }) => {
const DocMessage = ({
id,
metadata,
}: {
id: string
metadata: Record<string, unknown> | undefined
}) => {
const { onViewFile } = usePath()
const [fileInfo, setFileInfo] = useState<
{ filename: string; id: string } | undefined
>()
useEffect(() => {
if (!fileInfo) {
getFileInfo(id).then((data) => {
setFileInfo(data)
})
}
}, [fileInfo, id])
return (
<div className="group/file bg-secondary relative mb-2 inline-flex w-60 cursor-pointer gap-x-3 overflow-hidden rounded-lg p-4">
@ -29,10 +25,14 @@ const DocMessage = ({ id }: { id: string }) => {
<Icon type="pdf" />
<div className="w-full">
<h6 className="line-clamp-1 w-4/5 overflow-hidden font-medium">
{fileInfo?.filename}
{metadata && 'filename' in metadata
? (metadata.filename as string)
: id}
</h6>
<p className="text-[hsla(var(--text-secondary)] line-clamp-1 overflow-hidden truncate">
{fileInfo?.id ?? id}
{metadata && 'size' in metadata
? toGibibytes(Number(metadata.size))
: id}
</p>
</div>
</div>

View File

@ -127,7 +127,10 @@ const MessageContainer: React.FC<
<>
{image && <ImageMessage image={image} />}
{attachedFile && (
<DocMessage id={props.attachments?.[0]?.file_id ?? props.id} />
<DocMessage
id={props.attachments?.[0]?.file_id ?? props.id}
metadata={props.metadata}
/>
)}
{editMessage === props.id ? (

View File

@ -6,7 +6,6 @@ import {
ChatCompletionRole,
MessageRequest,
MessageRequestType,
MessageStatus,
ModelInfo,
Thread,
ThreadMessage,
@ -35,7 +34,7 @@ export class MessageRequestBuilder {
this.model = model
this.thread = thread
this.messages = messages
.filter((e) => e.status !== MessageStatus.Error)
.filter((e) => !e.metadata?.error)
.map<ChatCompletionMessage>((msg) => ({
role: msg.role,
content: msg.content[0]?.text?.value ?? '.',

View File

@ -16,6 +16,7 @@ export class ThreadMessageBuilder {
content: ThreadContent[] = []
attachments: Attachment[] = []
metadata: Record<string, unknown> = {}
constructor(messageRequest: MessageRequestBuilder) {
this.messageRequest = messageRequest
@ -33,6 +34,7 @@ export class ThreadMessageBuilder {
completed_at: timestamp,
object: 'thread.message',
content: this.content,
metadata: this.metadata,
}
}
@ -68,6 +70,10 @@ export class ThreadMessageBuilder {
},
],
})
this.metadata = {
filename: fileUpload.name,
size: fileUpload.file?.size,
}
}
return this