chore: styling tool call funtion render UI
This commit is contained in:
parent
95f90f601d
commit
05ce85d9b1
@ -9,9 +9,10 @@ import {
|
||||
IconPencil,
|
||||
} from '@tabler/icons-react'
|
||||
import { useAppState } from '@/hooks/useAppState'
|
||||
import ThinkingBlock from './ThinkingBlock'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useMessages } from '@/hooks/useMessages'
|
||||
import ThinkingBlock from '@/containers/ThinkingBlock'
|
||||
import ToolCallBlock from '@/containers/ToolCallBlock'
|
||||
|
||||
const CopyButton = ({ text }: { text: string }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
@ -81,6 +82,12 @@ export const ThreadContent = memo(
|
||||
|
||||
const { deleteMessage } = useMessages()
|
||||
|
||||
const isToolCalls =
|
||||
item.metadata &&
|
||||
'tool_calls' in item.metadata &&
|
||||
Array.isArray(item.metadata.tool_calls) &&
|
||||
item.metadata.tool_calls.length
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{item.content?.[0]?.text && item.role === 'user' && (
|
||||
@ -124,41 +131,59 @@ export const ThreadContent = memo(
|
||||
text={reasoningSegment}
|
||||
/>
|
||||
)}
|
||||
|
||||
<RenderMarkdown content={textSegment} components={linkComponents} />
|
||||
<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'
|
||||
)}
|
||||
>
|
||||
<CopyButton text={item.content?.[0]?.text.value || ''} />
|
||||
<button
|
||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||
onClick={() => {
|
||||
deleteMessage(item.thread_id, item.id)
|
||||
}}
|
||||
|
||||
{isToolCalls && item.metadata?.tool_calls ? (
|
||||
<>
|
||||
{(item.metadata.tool_calls as ToolCall[]).map((toolCall) => (
|
||||
<ToolCallBlock
|
||||
id={toolCall.tool?.id ?? 0}
|
||||
name={toolCall.tool?.function?.name ?? ''}
|
||||
key={toolCall.tool?.id}
|
||||
result={JSON.stringify(toolCall.response)}
|
||||
loading={toolCall.state === 'pending'}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : 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} />
|
||||
<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>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||
onClick={() => {
|
||||
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>
|
||||
<CopyButton text={item.content?.[0]?.text.value || ''} />
|
||||
<button
|
||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||
onClick={() => {
|
||||
deleteMessage(item.thread_id, item.id)
|
||||
}}
|
||||
>
|
||||
<IconTrash 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">
|
||||
Delete
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-1 hover:text-accent transition-colors cursor-pointer group relative"
|
||||
onClick={() => {
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{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) => {
|
||||
// Only pass isLastMessage to the last message in the array
|
||||
const isLastMessage = index === messages.length - 1
|
||||
console.log(messages, 'messages')
|
||||
return (
|
||||
<div
|
||||
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