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

@ -42,8 +42,11 @@ describe('ErrorMessage Component', () => {
it('renders error message with InvalidApiKey correctly', () => { it('renders error message with InvalidApiKey correctly', () => {
const message: ThreadMessage = { const message: ThreadMessage = {
id: '1', id: '1',
status: MessageStatus.Error, metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.InvalidApiKey, error_code: ErrorCode.InvalidApiKey,
},
status: "completed",
content: [{ text: { value: 'Invalid API Key' } }], content: [{ text: { value: 'Invalid API Key' } }],
} as ThreadMessage } as ThreadMessage
@ -56,8 +59,11 @@ describe('ErrorMessage Component', () => {
it('renders general error message correctly', () => { it('renders general error message correctly', () => {
const message: ThreadMessage = { const message: ThreadMessage = {
id: '1', id: '1',
status: MessageStatus.Error, status: "completed",
error_code: ErrorCode.Unknown, metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.Unknown
},
content: [{ text: { value: 'Unknown error occurred' } }], content: [{ text: { value: 'Unknown error occurred' } }],
} as ThreadMessage } as ThreadMessage
@ -69,9 +75,11 @@ describe('ErrorMessage Component', () => {
it('opens troubleshooting modal when link is clicked', () => { it('opens troubleshooting modal when link is clicked', () => {
const message: ThreadMessage = { const message: ThreadMessage = {
id: '1', id: '1',
status: MessageStatus.Error, status: "completed",
metadata: {
error: MessageStatus.Error,
error_code: ErrorCode.Unknown, error_code: ErrorCode.Unknown,
content: [{ text: { value: 'Unknown error occurred' } }], }, content: [{ text: { value: 'Unknown error occurred' } }],
} as ThreadMessage } as ThreadMessage
render(<ErrorMessage message={message} />) render(<ErrorMessage message={message} />)

View File

@ -53,7 +53,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
const getErrorTitle = () => { const getErrorTitle = () => {
const engine = getEngine() const engine = getEngine()
switch (message.error_code) { switch (message.metadata?.error_code) {
case ErrorCode.InvalidApiKey: case ErrorCode.InvalidApiKey:
case ErrorCode.AuthenticationError: case ErrorCode.AuthenticationError:
return ( return (
@ -102,7 +102,7 @@ const ErrorMessage = ({ message }: { message: ThreadMessage }) => {
return ( return (
<div className="mx-auto my-6 max-w-[700px]"> <div className="mx-auto my-6 max-w-[700px]">
{message.status === MessageStatus.Error && ( {!!message.metadata?.error && (
<div <div
key={message.id} key={message.id}
className="mx-6 flex flex-col items-center space-y-2 text-center font-medium text-[hsla(var(--text-secondary))]" 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, ...thread,
metadata, metadata,
}) })
if (message.status === MessageStatus.Error) {
message.metadata = {
...message.metadata,
error: message.content[0]?.text?.value,
error_code: message.error_code,
}
}
;(async () => { ;(async () => {
const updatedMessage = await extensionManager const updatedMessage = await extensionManager
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational) .get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.createMessage(message) ?.createMessage(message)
.catch(() => undefined)
if (updatedMessage) { if (updatedMessage) {
deleteMessage(message.id) deleteMessage(message.id)
addNewMessage(updatedMessage) addNewMessage(updatedMessage)

View File

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

View File

@ -230,8 +230,10 @@ export const useCreateNewThread = () => {
await extensionManager await extensionManager
.get<ConversationalExtension>(ExtensionTypeEnum.Conversational) .get<ConversationalExtension>(ExtensionTypeEnum.Conversational)
?.createThreadAssistant(thread.id, assistantInfo) ?.createThreadAssistant(thread.id, assistantInfo)
.catch(console.error)
return thread return thread
}) })
.catch(() => undefined)
} }
return { 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 { useAtomValue } from 'jotai'
import { getFileInfo } from '@/utils/file' import { getFileInfo } from '@/utils/file'

View File

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

View File

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

View File

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

View File

@ -109,7 +109,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
)} )}
{message.id === messages[messages.length - 1]?.id && {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 && ( !messages[messages.length - 1].attachments?.length && (
<div <div
className="cursor-pointer rounded-lg border border-[hsla(var(--app-border))] p-2" 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 { usePath } from '@/hooks/usePath'
import { getFileInfo } from '@/utils/file' import { toGibibytes } from '@/utils/converter'
import Icon from '../FileUploadPreview/Icon' 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 { onViewFile } = usePath()
const [fileInfo, setFileInfo] = useState<
{ filename: string; id: string } | undefined
>()
useEffect(() => {
if (!fileInfo) {
getFileInfo(id).then((data) => {
setFileInfo(data)
})
}
}, [fileInfo, id])
return ( 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"> <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" /> <Icon type="pdf" />
<div className="w-full"> <div className="w-full">
<h6 className="line-clamp-1 w-4/5 overflow-hidden font-medium"> <h6 className="line-clamp-1 w-4/5 overflow-hidden font-medium">
{fileInfo?.filename} {metadata && 'filename' in metadata
? (metadata.filename as string)
: id}
</h6> </h6>
<p className="text-[hsla(var(--text-secondary)] line-clamp-1 overflow-hidden truncate"> <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> </p>
</div> </div>
</div> </div>

View File

@ -127,7 +127,10 @@ const MessageContainer: React.FC<
<> <>
{image && <ImageMessage image={image} />} {image && <ImageMessage image={image} />}
{attachedFile && ( {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 ? ( {editMessage === props.id ? (

View File

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

View File

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