fix: attach file information into message metadata for a quick retrieval (#4298)
This commit is contained in:
parent
4489af6ad9
commit
a1ea94aeca
@ -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} />)
|
||||||
|
|||||||
@ -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))]"
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 ? (
|
||||||
|
|||||||
@ -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 ?? '.',
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user