chore: styling tool call funtion render UI
This commit is contained in:
parent
95f90f601d
commit
05ce85d9b1
@ -9,9 +9,10 @@ import {
|
|||||||
IconPencil,
|
IconPencil,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { useAppState } from '@/hooks/useAppState'
|
import { useAppState } from '@/hooks/useAppState'
|
||||||
import ThinkingBlock from './ThinkingBlock'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useMessages } from '@/hooks/useMessages'
|
import { useMessages } from '@/hooks/useMessages'
|
||||||
|
import ThinkingBlock from '@/containers/ThinkingBlock'
|
||||||
|
import ToolCallBlock from '@/containers/ToolCallBlock'
|
||||||
|
|
||||||
const CopyButton = ({ text }: { text: string }) => {
|
const CopyButton = ({ text }: { text: string }) => {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
@ -81,6 +82,12 @@ export const ThreadContent = memo(
|
|||||||
|
|
||||||
const { deleteMessage } = useMessages()
|
const { deleteMessage } = useMessages()
|
||||||
|
|
||||||
|
const isToolCalls =
|
||||||
|
item.metadata &&
|
||||||
|
'tool_calls' in item.metadata &&
|
||||||
|
Array.isArray(item.metadata.tool_calls) &&
|
||||||
|
item.metadata.tool_calls.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{item.content?.[0]?.text && item.role === 'user' && (
|
{item.content?.[0]?.text && item.role === 'user' && (
|
||||||
@ -124,41 +131,59 @@ export const ThreadContent = memo(
|
|||||||
text={reasoningSegment}
|
text={reasoningSegment}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<RenderMarkdown content={textSegment} components={linkComponents} />
|
<RenderMarkdown content={textSegment} components={linkComponents} />
|
||||||
<div className="flex items-center gap-2 mt-2 text-main-view-fg/60 text-xs">
|
|
||||||
<div
|
{isToolCalls && item.metadata?.tool_calls ? (
|
||||||
className={cn(
|
<>
|
||||||
'flex items-center gap-2',
|
{(item.metadata.tool_calls as ToolCall[]).map((toolCall) => (
|
||||||
item.isLastMessage &&
|
<ToolCallBlock
|
||||||
streamingContent &&
|
id={toolCall.tool?.id ?? 0}
|
||||||
'opacity-0 visinility-hidden pointer-events-none'
|
name={toolCall.tool?.function?.name ?? ''}
|
||||||
)}
|
key={toolCall.tool?.id}
|
||||||
>
|
result={JSON.stringify(toolCall.response)}
|
||||||
<CopyButton text={item.content?.[0]?.text.value || ''} />
|
loading={toolCall.state === 'pending'}
|
||||||
<button
|
/>
|
||||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
))}
|
||||||
onClick={() => {
|
</>
|
||||||
deleteMessage(item.thread_id, item.id)
|
) : null}
|
||||||
}}
|
|
||||||
|
{!isToolCalls && (
|
||||||
|
<div className="flex items-center gap-2 mt-2 text-main-view-fg/60 text-xs">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-2',
|
||||||
|
item.isLastMessage &&
|
||||||
|
streamingContent &&
|
||||||
|
'opacity-0 visinility-hidden pointer-events-none'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<IconTrash size={16} />
|
<CopyButton text={item.content?.[0]?.text.value || ''} />
|
||||||
<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
|
||||||
Delete
|
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||||
</span>
|
onClick={() => {
|
||||||
</button>
|
deleteMessage(item.thread_id, item.id)
|
||||||
<button
|
}}
|
||||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
>
|
||||||
onClick={() => {
|
<IconTrash size={16} />
|
||||||
console.log('Regenerate clicked')
|
<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">
|
||||||
}}
|
Delete
|
||||||
>
|
</span>
|
||||||
<IconRefresh size={16} />
|
</button>
|
||||||
<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
|
||||||
Regenerate
|
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||||
</span>
|
onClick={() => {
|
||||||
</button>
|
console.log('Regenerate clicked')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconRefresh 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">
|
||||||
|
Regenerate
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{item.type === 'image_url' && image && (
|
{item.type === 'image_url' && image && (
|
||||||
|
|||||||
79
web-app/src/containers/ToolCallBlock.tsx
Normal file
79
web-app/src/containers/ToolCallBlock.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { ChevronDown, ChevronUp, Loader } from 'lucide-react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { create } from 'zustand'
|
||||||
|
import { RenderMarkdown } from './RenderMarkdown'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
result: string
|
||||||
|
name: string
|
||||||
|
id: number
|
||||||
|
loading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToolCallBlockState = {
|
||||||
|
collapseState: { [id: number]: boolean }
|
||||||
|
setCollapseState: (id: number, expanded: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const useToolCallBlockStore = create<ToolCallBlockState>((set) => ({
|
||||||
|
collapseState: {},
|
||||||
|
setCollapseState: (id, expanded) =>
|
||||||
|
set((state) => ({
|
||||||
|
collapseState: {
|
||||||
|
...state.collapseState,
|
||||||
|
[id]: expanded,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const ToolCallBlock = ({ id, name, result, loading }: Props) => {
|
||||||
|
const { collapseState, setCollapseState } = useToolCallBlockStore()
|
||||||
|
const isExpanded = collapseState[id] ?? false
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
const newExpandedState = !isExpanded
|
||||||
|
setCollapseState(id, newExpandedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full cursor-pointer mt-4" onClick={handleClick}>
|
||||||
|
<div className="mb-4 rounded-lg bg-main-view-fg/4 border border-dashed border-main-view-fg/10">
|
||||||
|
<div className="flex items-center gap-3 p-2">
|
||||||
|
{loading && (
|
||||||
|
<Loader className="size-4 animate-spin text-main-view-fg/60" />
|
||||||
|
)}
|
||||||
|
<button className="flex items-center gap-2 focus:outline-none">
|
||||||
|
{isExpanded ? (
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
<span className="font-medium text-main-view-fg/80">
|
||||||
|
View result from{' '}
|
||||||
|
<span className="font-medium text-main-view-fg">{name}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'h-fit w-full overflow-auto transition-all duration-300 px-2',
|
||||||
|
isExpanded ? '' : 'max-h-0 overflow-hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="mt-2 text-main-view-fg/60">
|
||||||
|
<RenderMarkdown
|
||||||
|
content={
|
||||||
|
'```json\n' +
|
||||||
|
JSON.stringify(result ? JSON.parse(result) : null, null, 2) +
|
||||||
|
'\n```'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolCallBlock
|
||||||
@ -177,6 +177,7 @@ function ThreadDetail() {
|
|||||||
messages.map((item, index) => {
|
messages.map((item, index) => {
|
||||||
// Only pass isLastMessage to the last message in the array
|
// Only pass isLastMessage to the last message in the array
|
||||||
const isLastMessage = index === messages.length - 1
|
const isLastMessage = index === messages.length - 1
|
||||||
|
console.log(messages, 'messages')
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
|||||||
10
web-app/src/types/message.d.ts
vendored
Normal file
10
web-app/src/types/message.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
type ToolCall = {
|
||||||
|
tool: {
|
||||||
|
id?: number
|
||||||
|
function?: {
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response?: unknown
|
||||||
|
state?: string
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user