fix: attach file information into message metadata for a quick retrieval (#4298)
This commit is contained in:
parent
4489af6ad9
commit
a1ea94aeca
@ -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} />)
|
||||
|
||||
@ -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))]"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -200,6 +200,7 @@ export default function useSendChatMessage() {
|
||||
const createdMessage = await extensionManager
|
||||
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
|
||||
?.createMessage(newMessage)
|
||||
.catch(() => undefined)
|
||||
|
||||
if (!createdMessage) return
|
||||
|
||||
|
||||
@ -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} />
|
||||
)}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 ? (
|
||||
|
||||
@ -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 ?? '.',
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user