enhancement: show assistant info on message (#5064)
This commit is contained in:
parent
2d7d731a76
commit
12ad61aaa8
@ -1,6 +1,7 @@
|
|||||||
import { useAppState } from '@/hooks/useAppState'
|
import { useAppState } from '@/hooks/useAppState'
|
||||||
import { ThreadContent } from './ThreadContent'
|
import { ThreadContent } from './ThreadContent'
|
||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
|
import { useMessages } from '@/hooks/useMessages'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
threadId: string
|
threadId: string
|
||||||
@ -9,10 +10,22 @@ type Props = {
|
|||||||
// Use memo with no dependencies to allow re-renders when props change
|
// Use memo with no dependencies to allow re-renders when props change
|
||||||
export const StreamingContent = memo(({ threadId }: Props) => {
|
export const StreamingContent = memo(({ threadId }: Props) => {
|
||||||
const { streamingContent } = useAppState()
|
const { streamingContent } = useAppState()
|
||||||
|
const { getMessages } = useMessages()
|
||||||
|
const messages = getMessages(threadId)
|
||||||
|
|
||||||
if (!streamingContent || streamingContent.thread_id !== threadId) return null
|
if (!streamingContent || streamingContent.thread_id !== threadId) return null
|
||||||
|
|
||||||
// Pass a new object to ThreadContent to avoid reference issues
|
// Pass a new object to ThreadContent to avoid reference issues
|
||||||
// The streaming content is always the last message
|
// The streaming content is always the last message
|
||||||
return <ThreadContent {...streamingContent} isLastMessage={true} />
|
return (
|
||||||
|
<ThreadContent
|
||||||
|
{...streamingContent}
|
||||||
|
isLastMessage={true}
|
||||||
|
showAssistant={
|
||||||
|
messages.length > 0
|
||||||
|
? messages[messages.length - 1].role !== 'assistant'
|
||||||
|
: true
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip'
|
} from '@/components/ui/tooltip'
|
||||||
|
import { formatDate } from '@/utils/formatDate'
|
||||||
|
|
||||||
const CopyButton = ({ text }: { text: string }) => {
|
const CopyButton = ({ text }: { text: string }) => {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
@ -67,7 +68,13 @@ const CopyButton = ({ text }: { text: string }) => {
|
|||||||
|
|
||||||
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
|
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
|
||||||
export const ThreadContent = memo(
|
export const ThreadContent = memo(
|
||||||
(item: ThreadMessage & { isLastMessage?: boolean; index?: number }) => {
|
(
|
||||||
|
item: ThreadMessage & {
|
||||||
|
isLastMessage?: boolean
|
||||||
|
index?: number
|
||||||
|
showAssistant?: boolean
|
||||||
|
}
|
||||||
|
) => {
|
||||||
const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
|
const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
|
||||||
|
|
||||||
// Use useMemo to stabilize the components prop
|
// Use useMemo to stabilize the components prop
|
||||||
@ -135,6 +142,10 @@ export const ThreadContent = memo(
|
|||||||
Array.isArray(item.metadata.tool_calls) &&
|
Array.isArray(item.metadata.tool_calls) &&
|
||||||
item.metadata.tool_calls.length
|
item.metadata.tool_calls.length
|
||||||
|
|
||||||
|
const assistant = item.metadata?.assistant as
|
||||||
|
| { avatar?: React.ReactNode; name?: React.ReactNode }
|
||||||
|
| undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{item.content?.[0]?.text && item.role === 'user' && (
|
{item.content?.[0]?.text && item.role === 'user' && (
|
||||||
@ -221,6 +232,25 @@ export const ThreadContent = memo(
|
|||||||
)}
|
)}
|
||||||
{item.content?.[0]?.text && item.role !== 'user' && (
|
{item.content?.[0]?.text && item.role !== 'user' && (
|
||||||
<>
|
<>
|
||||||
|
{item.showAssistant && (
|
||||||
|
<div className="flex items-center gap-2 mb-3 text-main-view-fg/60">
|
||||||
|
<div className="flex items-center gap-2 size-8 rounded-md justify-center border border-main-view-fg/10 bg-main-view-fg/5 p-2">
|
||||||
|
<span className="text-base">{assistant?.avatar || '👋'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-main-view-fg font-medium">
|
||||||
|
{assistant?.name || 'Jan'}
|
||||||
|
</span>
|
||||||
|
{item?.created_at && (
|
||||||
|
<span className="text-xs mt-0.5">
|
||||||
|
{formatDate(item?.created_at)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{reasoningSegment && (
|
{reasoningSegment && (
|
||||||
<ThinkingBlock
|
<ThinkingBlock
|
||||||
id={item.index ?? Number(item.id)}
|
id={item.index ?? Number(item.id)}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import { ThreadMessage } from '@janhq/core'
|
import { ThreadMessage } from '@janhq/core'
|
||||||
import { MCPTool } from '@/types/completion'
|
import { MCPTool } from '@/types/completion'
|
||||||
|
import { useAssistant } from './useAssistant'
|
||||||
|
|
||||||
type AppState = {
|
type AppState = {
|
||||||
streamingContent?: ThreadMessage
|
streamingContent?: ThreadMessage
|
||||||
@ -25,8 +26,19 @@ export const useAppState = create<AppState>()((set) => ({
|
|||||||
serverStatus: 'stopped',
|
serverStatus: 'stopped',
|
||||||
abortControllers: {},
|
abortControllers: {},
|
||||||
tokenSpeed: undefined,
|
tokenSpeed: undefined,
|
||||||
updateStreamingContent: (content) => {
|
updateStreamingContent: (content: ThreadMessage | undefined) => {
|
||||||
set({ streamingContent: content })
|
set(() => ({
|
||||||
|
streamingContent: content
|
||||||
|
? {
|
||||||
|
...content,
|
||||||
|
created_at: content.created_at || Date.now(),
|
||||||
|
metadata: {
|
||||||
|
...content.metadata,
|
||||||
|
assistant: useAssistant.getState().currentAssistant,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
updateLoadingModel: (loading) => {
|
updateLoadingModel: (loading) => {
|
||||||
set({ loadingModel: loading })
|
set({ loadingModel: loading })
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
createMessage,
|
createMessage,
|
||||||
deleteMessage as deleteMessageExt,
|
deleteMessage as deleteMessageExt,
|
||||||
} from '@/services/messages'
|
} from '@/services/messages'
|
||||||
|
import { useAssistant } from './useAssistant'
|
||||||
|
|
||||||
type MessageState = {
|
type MessageState = {
|
||||||
messages: Record<string, ThreadMessage[]>
|
messages: Record<string, ThreadMessage[]>
|
||||||
@ -31,7 +32,16 @@ export const useMessages = create<MessageState>()(
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
addMessage: (message) => {
|
addMessage: (message) => {
|
||||||
createMessage(message).then((createdMessage) => {
|
const currentAssistant = useAssistant.getState().currentAssistant
|
||||||
|
const newMessage = {
|
||||||
|
...message,
|
||||||
|
created_at: message.created_at || Date.now(),
|
||||||
|
metadata: {
|
||||||
|
...message.metadata,
|
||||||
|
assistant: currentAssistant,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createMessage(newMessage).then((createdMessage) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
messages: {
|
messages: {
|
||||||
...state.messages,
|
...state.messages,
|
||||||
|
|||||||
@ -196,6 +196,11 @@ function ThreadDetail() {
|
|||||||
<ThreadContent
|
<ThreadContent
|
||||||
{...item}
|
{...item}
|
||||||
isLastMessage={isLastMessage}
|
isLastMessage={isLastMessage}
|
||||||
|
showAssistant={
|
||||||
|
item.role === 'assistant' &&
|
||||||
|
(index === 0 ||
|
||||||
|
messages[index - 1].role !== 'assistant')
|
||||||
|
}
|
||||||
index={index}
|
index={index}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
web-app/src/utils/formatDate.ts
Normal file
10
web-app/src/utils/formatDate.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const formatDate = (date: string | number | Date): string => {
|
||||||
|
return new Date(date).toLocaleString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user