chore: token speed and edit message (#5031)
* chore: add token speed measurement * chore: add edit message handler * chore: add DialogClose wrapper around save button
This commit is contained in:
parent
76827d42f5
commit
46943a1cf7
@ -49,6 +49,7 @@ const ChatInput = ({
|
|||||||
const { prompt, setPrompt } = usePrompt()
|
const { prompt, setPrompt } = usePrompt()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { spellCheckChatInput } = useGeneralSetting()
|
const { spellCheckChatInput } = useGeneralSetting()
|
||||||
|
const { tokenSpeed } = useAppState()
|
||||||
const maxRows = 10
|
const maxRows = 10
|
||||||
|
|
||||||
const { selectedModel } = useModelProvider()
|
const { selectedModel } = useModelProvider()
|
||||||
@ -226,7 +227,7 @@ const ChatInput = ({
|
|||||||
{showSpeedToken && (
|
{showSpeedToken && (
|
||||||
<div className="flex items-center gap-1 text-main-view-fg/60 text-xs">
|
<div className="flex items-center gap-1 text-main-view-fg/60 text-xs">
|
||||||
<IconBrandSpeedtest size={18} />
|
<IconBrandSpeedtest size={18} />
|
||||||
<span>42 tokens/sec</span>
|
<span>{Math.round(tokenSpeed?.tokenSpeed ?? 0)} tokens/sec</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -14,6 +14,18 @@ import { useMessages } from '@/hooks/useMessages'
|
|||||||
import ThinkingBlock from '@/containers/ThinkingBlock'
|
import ThinkingBlock from '@/containers/ThinkingBlock'
|
||||||
import ToolCallBlock from '@/containers/ToolCallBlock'
|
import ToolCallBlock from '@/containers/ToolCallBlock'
|
||||||
import { useChat } from '@/hooks/useChat'
|
import { useChat } from '@/hooks/useChat'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
const CopyButton = ({ text }: { text: string }) => {
|
const CopyButton = ({ text }: { text: string }) => {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
@ -49,6 +61,8 @@ 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 }) => {
|
||||||
|
const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
|
||||||
|
|
||||||
// Use useMemo to stabilize the components prop
|
// Use useMemo to stabilize the components prop
|
||||||
const linkComponents = useMemo(
|
const linkComponents = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -94,6 +108,20 @@ export const ThreadContent = memo(
|
|||||||
sendMessage(lastMessage.content?.[0]?.text?.value || '')
|
sendMessage(lastMessage.content?.[0]?.text?.value || '')
|
||||||
}, [deleteMessage, getMessages, item, sendMessage])
|
}, [deleteMessage, getMessages, item, sendMessage])
|
||||||
|
|
||||||
|
const editMessage = useCallback(
|
||||||
|
(messageId: string) => {
|
||||||
|
const threadMessages = getMessages(item.thread_id)
|
||||||
|
const index = threadMessages.findIndex((msg) => msg.id === messageId)
|
||||||
|
if (index === -1) return
|
||||||
|
// Delete all messages after the edited message
|
||||||
|
for (let i = threadMessages.length - 1; i >= index; i--) {
|
||||||
|
deleteMessage(threadMessages[i].thread_id, threadMessages[i].id)
|
||||||
|
}
|
||||||
|
sendMessage(message)
|
||||||
|
},
|
||||||
|
[deleteMessage, getMessages, item.thread_id, message, sendMessage]
|
||||||
|
)
|
||||||
|
|
||||||
const isToolCalls =
|
const isToolCalls =
|
||||||
item.metadata &&
|
item.metadata &&
|
||||||
'tool_calls' in item.metadata &&
|
'tool_calls' in item.metadata &&
|
||||||
@ -110,17 +138,63 @@ export const ThreadContent = memo(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 text-main-view-fg/60 text-xs mt-2">
|
<div className="flex items-center justify-end gap-2 text-main-view-fg/60 text-xs mt-2">
|
||||||
<button
|
<Dialog>
|
||||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
<DialogTrigger asChild>
|
||||||
onClick={() => {
|
<button
|
||||||
console.log('Edit clicked')
|
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||||
}}
|
onClick={() => {
|
||||||
>
|
console.log('Edit clicked')
|
||||||
<IconPencil size={16} />
|
}}
|
||||||
<span className="opacity-0 w-0 overflow-hidden whitespace-nowrap group-hover:w-auto group-hover:opacity-100 transition-all duration-300 ease-in-out">
|
>
|
||||||
Edit
|
<IconPencil size={16} />
|
||||||
</span>
|
<span className="opacity-0 w-0 overflow-hidden whitespace-nowrap group-hover:w-auto group-hover:opacity-100 transition-all duration-300 ease-in-out">
|
||||||
</button>
|
Edit
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit Message</DialogTitle>
|
||||||
|
<Textarea
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => {
|
||||||
|
setMessage(e.target.value)
|
||||||
|
}}
|
||||||
|
className="mt-2 resize-none"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
// Prevent key from being captured by parent components
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DialogFooter className="mt-2 flex items-center">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
className="hover:no-underline"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button
|
||||||
|
disabled={!message}
|
||||||
|
onClick={() => {
|
||||||
|
editMessage(item.id)
|
||||||
|
toast.success('Edit Message', {
|
||||||
|
id: 'edit-message',
|
||||||
|
description:
|
||||||
|
'Message edited successfully. Please wait for the model to respond.',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogHeader>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@ -8,11 +8,14 @@ type AppState = {
|
|||||||
tools: MCPTool[]
|
tools: MCPTool[]
|
||||||
serverStatus: 'running' | 'stopped' | 'pending'
|
serverStatus: 'running' | 'stopped' | 'pending'
|
||||||
abortControllers: Record<string, AbortController>
|
abortControllers: Record<string, AbortController>
|
||||||
|
tokenSpeed?: TokenSpeed
|
||||||
setServerStatus: (value: 'running' | 'stopped' | 'pending') => void
|
setServerStatus: (value: 'running' | 'stopped' | 'pending') => void
|
||||||
updateStreamingContent: (content: ThreadMessage | undefined) => void
|
updateStreamingContent: (content: ThreadMessage | undefined) => void
|
||||||
updateLoadingModel: (loading: boolean) => void
|
updateLoadingModel: (loading: boolean) => void
|
||||||
updateTools: (tools: MCPTool[]) => void
|
updateTools: (tools: MCPTool[]) => void
|
||||||
setAbortController: (threadId: string, controller: AbortController) => void
|
setAbortController: (threadId: string, controller: AbortController) => void
|
||||||
|
updateTokenSpeed: (message: ThreadMessage) => void
|
||||||
|
resetTokenSpeed: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppState = create<AppState>()((set) => ({
|
export const useAppState = create<AppState>()((set) => ({
|
||||||
@ -21,6 +24,7 @@ export const useAppState = create<AppState>()((set) => ({
|
|||||||
tools: [],
|
tools: [],
|
||||||
serverStatus: 'stopped',
|
serverStatus: 'stopped',
|
||||||
abortControllers: {},
|
abortControllers: {},
|
||||||
|
tokenSpeed: undefined,
|
||||||
updateStreamingContent: (content) => {
|
updateStreamingContent: (content) => {
|
||||||
set({ streamingContent: content })
|
set({ streamingContent: content })
|
||||||
},
|
},
|
||||||
@ -39,4 +43,37 @@ export const useAppState = create<AppState>()((set) => ({
|
|||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
|
updateTokenSpeed: (message) =>
|
||||||
|
set((state) => {
|
||||||
|
const currentTimestamp = new Date().getTime() // Get current time in milliseconds
|
||||||
|
if (!state.tokenSpeed) {
|
||||||
|
// If this is the first update, just set the lastTimestamp and return
|
||||||
|
return {
|
||||||
|
tokenSpeed: {
|
||||||
|
lastTimestamp: currentTimestamp,
|
||||||
|
tokenSpeed: 0,
|
||||||
|
tokenCount: 1,
|
||||||
|
message: message.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeDiffInSeconds =
|
||||||
|
(currentTimestamp - state.tokenSpeed.lastTimestamp) / 1000 // Time difference in seconds
|
||||||
|
const totalTokenCount = state.tokenSpeed.tokenCount + 1
|
||||||
|
const averageTokenSpeed =
|
||||||
|
totalTokenCount / (timeDiffInSeconds > 0 ? timeDiffInSeconds : 1) // Calculate average token speed
|
||||||
|
return {
|
||||||
|
tokenSpeed: {
|
||||||
|
...state.tokenSpeed,
|
||||||
|
tokenSpeed: averageTokenSpeed,
|
||||||
|
tokenCount: totalTokenCount,
|
||||||
|
message: message.id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
resetTokenSpeed: () =>
|
||||||
|
set({
|
||||||
|
tokenSpeed: undefined,
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { useAssistant } from './useAssistant'
|
|||||||
|
|
||||||
export const useChat = () => {
|
export const useChat = () => {
|
||||||
const { prompt, setPrompt } = usePrompt()
|
const { prompt, setPrompt } = usePrompt()
|
||||||
const { tools } = useAppState()
|
const { tools, updateTokenSpeed, resetTokenSpeed } = useAppState()
|
||||||
const { currentAssistant } = useAssistant()
|
const { currentAssistant } = useAssistant()
|
||||||
|
|
||||||
const { getProviderByName, selectedModel, selectedProvider } =
|
const { getProviderByName, selectedModel, selectedProvider } =
|
||||||
@ -68,6 +68,7 @@ export const useChat = () => {
|
|||||||
async (message: string) => {
|
async (message: string) => {
|
||||||
const activeThread = await getCurrentThread()
|
const activeThread = await getCurrentThread()
|
||||||
|
|
||||||
|
resetTokenSpeed()
|
||||||
if (!activeThread || !provider) return
|
if (!activeThread || !provider) return
|
||||||
|
|
||||||
updateStreamingContent(emptyThreadContent)
|
updateStreamingContent(emptyThreadContent)
|
||||||
@ -119,6 +120,7 @@ export const useChat = () => {
|
|||||||
accumulatedText
|
accumulatedText
|
||||||
)
|
)
|
||||||
updateStreamingContent(currentContent)
|
updateStreamingContent(currentContent)
|
||||||
|
updateTokenSpeed(currentContent)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,6 +146,7 @@ export const useChat = () => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
getCurrentThread,
|
getCurrentThread,
|
||||||
|
resetTokenSpeed,
|
||||||
provider,
|
provider,
|
||||||
updateStreamingContent,
|
updateStreamingContent,
|
||||||
addMessage,
|
addMessage,
|
||||||
@ -153,6 +156,7 @@ export const useChat = () => {
|
|||||||
setAbortController,
|
setAbortController,
|
||||||
updateLoadingModel,
|
updateLoadingModel,
|
||||||
tools,
|
tools,
|
||||||
|
updateTokenSpeed,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
7
web-app/src/types/threads.d.ts
vendored
7
web-app/src/types/threads.d.ts
vendored
@ -55,3 +55,10 @@ type Assistant = {
|
|||||||
instructions: string
|
instructions: string
|
||||||
parameters: Record<string, unknown>
|
parameters: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TokenSpeed = {
|
||||||
|
message: string
|
||||||
|
tokenSpeed: number
|
||||||
|
tokenCount: number
|
||||||
|
lastTimestamp: number
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user