mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 23:59:45 +08:00
feat(Tools): add WebFetchTool and update tool rendering logic
- Introduced WebFetchTool for fetching web content with specified prompts and URLs. - Updated MessageTool to include WebFetch in the tool rendering options. - Enhanced BashTool and TaskTool to improve display of input and output information. - Refactored GenericTools for better parameter display and consistency across tools. - Adjusted types to include WebFetchTool input and output definitions.
This commit is contained in:
parent
91b4d806cd
commit
7631d9d730
@ -16,15 +16,16 @@ export function BashTool({ input, output }: { input: BashToolInputType; output?:
|
||||
<ToolTitle
|
||||
icon={<Terminal className="h-4 w-4" />}
|
||||
label="Bash"
|
||||
params={
|
||||
<Code size="sm" className="text-sm">
|
||||
{input.command}
|
||||
</Code>
|
||||
}
|
||||
params={input.description}
|
||||
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<Code size="sm" className="line-clamp-1 w-max max-w-full text-ellipsis text-xs">
|
||||
{input.command}
|
||||
</Code>
|
||||
}>
|
||||
<div>{output}</div>
|
||||
<div className="whitespace-pre-line">{output}</div>
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
|
||||
@ -19,9 +19,9 @@ export function ToolTitle({
|
||||
return (
|
||||
<div className={`flex items-center gap-1 ${className}`}>
|
||||
{icon}
|
||||
{label && <span className="font-medium">{label}</span>}
|
||||
{params && <span className="flex-shrink-0 text-muted-foreground text-sm">{params}</span>}
|
||||
{stats && <span className="flex-shrink-0 text-muted-foreground">{stats}</span>}
|
||||
{label && <span className="font-medium text-sm">{label}</span>}
|
||||
{params && <span className="flex-shrink-0 text-muted-foreground text-xs">{params}</span>}
|
||||
{stats && <span className="flex-shrink-0 text-muted-foreground text-xs">{stats}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { AccordionItem, ScrollShadow } from '@heroui/react'
|
||||
import { FileText } from 'lucide-react'
|
||||
|
||||
import { SimpleFieldInputTool, StringOutputTool, ToolTitle } from './GenericTools'
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import { AgentToolsType } from './types'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import type { ReadToolInput as ReadToolInputType, ReadToolOutput as ReadToolOutputType } from './types'
|
||||
|
||||
export function ReadTool({ input, output }: { input: ReadToolInputType; output?: ReadToolOutputType }) {
|
||||
@ -20,7 +22,7 @@ export function ReadTool({ input, output }: { input: ReadToolInputType; output?:
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
key="tool"
|
||||
key={AgentToolsType.Read}
|
||||
aria-label="Read Tool"
|
||||
title={
|
||||
<ToolTitle
|
||||
@ -30,14 +32,11 @@ export function ReadTool({ input, output }: { input: ReadToolInputType; output?:
|
||||
stats={output && stats ? `${stats.lineCount} lines, ${stats.formatSize(stats.fileSize)}` : undefined}
|
||||
/>
|
||||
}>
|
||||
<div>
|
||||
<SimpleFieldInputTool input={input} label="File Path" fieldName="file_path" />
|
||||
{output && (
|
||||
<div>
|
||||
<StringOutputTool output={output} label="File Content" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{output ? (
|
||||
// <div className="h-full scroll-auto">
|
||||
<ReactMarkdown>{output}</ReactMarkdown>
|
||||
// </div>
|
||||
) : null}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ import { AccordionItem } from '@heroui/react'
|
||||
import { Bot } from 'lucide-react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import Markdown from 'react-markdown'
|
||||
|
||||
import type { TaskToolInput as TaskToolInputType, TaskToolOutput as TaskToolOutputType } from './types'
|
||||
|
||||
export function TaskTool({ input, output }: { input: TaskToolInputType; output?: TaskToolOutputType }) {
|
||||
@ -12,8 +14,7 @@ export function TaskTool({ input, output }: { input: TaskToolInputType; output?:
|
||||
title={<ToolTitle icon={<Bot className="h-4 w-4" />} label="Task" params={input.description} />}>
|
||||
{output?.map((item) => (
|
||||
<div key={item.type}>
|
||||
<div>Type: {item.type}</div>
|
||||
<div>{item.text}</div>
|
||||
<div>{item.type === 'text' ? <Markdown>{item.text}</Markdown> : item.text}</div>
|
||||
</div>
|
||||
))}
|
||||
</AccordionItem>
|
||||
|
||||
@ -1,16 +1,47 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { ListTodo } from 'lucide-react'
|
||||
import { AccordionItem, Chip, Card, CardBody } from '@heroui/react'
|
||||
import { ListTodo, CheckCircle, Clock, Circle } from 'lucide-react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type {
|
||||
TodoWriteToolInput as TodoWriteToolInputType,
|
||||
TodoWriteToolOutput as TodoWriteToolOutputType
|
||||
TodoWriteToolOutput as TodoWriteToolOutputType,
|
||||
TodoItem
|
||||
} from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
const getStatusConfig = (status: TodoItem['status']) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return {
|
||||
color: 'success' as const,
|
||||
icon: <CheckCircle className="h-3 w-3" />,
|
||||
label: '已完成'
|
||||
}
|
||||
case 'in_progress':
|
||||
return {
|
||||
color: 'primary' as const,
|
||||
icon: <Clock className="h-3 w-3" />,
|
||||
label: '进行中'
|
||||
}
|
||||
case 'pending':
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <Circle className="h-3 w-3" />,
|
||||
label: '待处理'
|
||||
}
|
||||
default:
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <Circle className="h-3 w-3" />,
|
||||
label: '待处理'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType; output?: TodoWriteToolOutputType }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key="tool"
|
||||
key={AgentToolsType.TodoWrite}
|
||||
aria-label="Todo Write Tool"
|
||||
title={
|
||||
<ToolTitle
|
||||
@ -19,16 +50,34 @@ export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType
|
||||
stats={`${input.todos.length} ${input.todos.length === 1 ? 'item' : 'items'}`}
|
||||
/>
|
||||
}>
|
||||
<div>
|
||||
{input.todos.map((todo, index) => (
|
||||
<div key={index}>
|
||||
<div>
|
||||
<span>{todo.status}</span>
|
||||
{todo.activeForm && <span>{todo.activeForm}</span>}
|
||||
</div>
|
||||
<div>{todo.content}</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="space-y-3">
|
||||
{input.todos.map((todo, index) => {
|
||||
const statusConfig = getStatusConfig(todo.status)
|
||||
return (
|
||||
<Card key={index} className="shadow-sm">
|
||||
<CardBody>
|
||||
<div className="flex items-start gap-3">
|
||||
<Chip
|
||||
color={statusConfig.color}
|
||||
variant="flat"
|
||||
size="sm"
|
||||
startContent={statusConfig.icon}
|
||||
className="flex-shrink-0">
|
||||
{statusConfig.label}
|
||||
</Chip>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className={`text-sm ${todo.status === 'completed' ? 'text-default-500 line-through' : ''}`}>
|
||||
{todo.status === 'completed' ? <s>{todo.content}</s> : todo.content}
|
||||
</div>
|
||||
{todo.status === 'in_progress' && (
|
||||
<div className="mt-1 text-default-400 text-xs">{todo.activeForm}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{output}
|
||||
</AccordionItem>
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { Globe } from 'lucide-react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { WebFetchToolInput, WebFetchToolOutput } from './types'
|
||||
|
||||
export function WebFetchTool({ input, output }: { input: WebFetchToolInput; output?: WebFetchToolOutput }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key="tool"
|
||||
aria-label="Web Fetch Tool"
|
||||
title={<ToolTitle icon={<Globe className="h-4 w-4" />} label="Web Fetch" params={input.url} />}
|
||||
subtitle={input.prompt}>
|
||||
{output}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -20,10 +20,7 @@ export function WebSearchTool({ input, output }: { input: WebSearchToolInput; ou
|
||||
stats={output ? `${resultCount} ${resultCount === 1 ? 'result' : 'results'}` : undefined}
|
||||
/>
|
||||
}>
|
||||
<div>
|
||||
<SimpleFieldInputTool input={input} label="Web Search Query" fieldName="query" />
|
||||
{output}
|
||||
</div>
|
||||
{output}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import { TodoWriteTool } from './TodoWriteTool'
|
||||
import { AgentToolsType, ToolInput, ToolOutput } from './types'
|
||||
import { WebSearchTool } from './WebSearchTool'
|
||||
import { WriteTool } from './WriteTool'
|
||||
import { WebFetchTool } from './WebFetchTool'
|
||||
|
||||
const logger = loggerService.withContext('MessageAgentTools')
|
||||
|
||||
@ -29,7 +30,8 @@ export const toolRenderers = {
|
||||
[AgentToolsType.TodoWrite]: TodoWriteTool,
|
||||
[AgentToolsType.WebSearch]: WebSearchTool,
|
||||
[AgentToolsType.Grep]: GrepTool,
|
||||
[AgentToolsType.Write]: WriteTool
|
||||
[AgentToolsType.Write]: WriteTool,
|
||||
[AgentToolsType.WebFetch]: WebFetchTool
|
||||
} as const
|
||||
|
||||
// 类型守卫函数
|
||||
@ -40,18 +42,18 @@ export function isValidAgentToolsType(toolName: unknown): toolName is AgentTools
|
||||
// 统一的渲染函数
|
||||
function renderToolContent(toolName: AgentToolsType, input: ToolInput, output?: ToolOutput) {
|
||||
const Renderer = toolRenderers[toolName]
|
||||
if (!Renderer) {
|
||||
logger.error('Unknown tool type', { toolName })
|
||||
return <div>Unknown tool type: {toolName}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
className="w-max max-w-full"
|
||||
itemClasses={{
|
||||
trigger:
|
||||
'p-0 [&>div:first-child]:!flex-none [&>div:first-child]:flex [&>div:first-child]:flex-col [&>div:first-child]:text-start'
|
||||
}}>
|
||||
'p-0 [&>div:first-child]:!flex-none [&>div:first-child]:flex [&>div:first-child]:flex-col [&>div:first-child]:text-start [&>div:first-child]:max-w-full',
|
||||
indicator: 'flex-shrink-0',
|
||||
subtitle: 'text-xs',
|
||||
content: 'rounded-md bg-foreground-50 p-2 text-foreground-900 dark:bg-foreground-100 h-fit max-h-96 scroll-auto'
|
||||
}}
|
||||
defaultExpandedKeys={toolName === AgentToolsType.TodoWrite ? [AgentToolsType.TodoWrite] : []}>
|
||||
{/* <Renderer input={input as any} output={output as any} /> */}
|
||||
{Renderer({ input: input as any, output: output as any })}
|
||||
</Accordion>
|
||||
|
||||
@ -7,7 +7,8 @@ export enum AgentToolsType {
|
||||
TodoWrite = 'TodoWrite',
|
||||
WebSearch = 'WebSearch',
|
||||
Grep = 'Grep',
|
||||
Write = 'Write'
|
||||
Write = 'Write',
|
||||
WebFetch = 'WebFetch'
|
||||
}
|
||||
|
||||
// Read 工具的类型定义
|
||||
@ -52,7 +53,7 @@ export type GlobToolOutput = string
|
||||
// TodoWrite 工具的类型定义
|
||||
export interface TodoItem {
|
||||
content: string
|
||||
status: string
|
||||
status: 'completed' | 'in_progress' | 'pending'
|
||||
activeForm?: string
|
||||
}
|
||||
|
||||
@ -66,9 +67,15 @@ export type TodoWriteToolOutput = string
|
||||
export interface WebSearchToolInput {
|
||||
query: string
|
||||
}
|
||||
|
||||
export type WebSearchToolOutput = string
|
||||
|
||||
// WebFetch 工具的类型定义
|
||||
export type WebFetchToolInput = {
|
||||
prompt: string
|
||||
url: string
|
||||
}
|
||||
export type WebFetchToolOutput = string
|
||||
|
||||
// Grep 工具的类型定义
|
||||
export interface GrepToolInput {
|
||||
pattern: string
|
||||
@ -93,6 +100,7 @@ export type ToolInput =
|
||||
| GlobToolInput
|
||||
| TodoWriteToolInput
|
||||
| WebSearchToolInput
|
||||
| WebFetchToolInput
|
||||
| GrepToolInput
|
||||
| WriteToolInput
|
||||
export type ToolOutput =
|
||||
@ -104,6 +112,7 @@ export type ToolOutput =
|
||||
| TodoWriteToolOutput
|
||||
| WebSearchToolOutput
|
||||
| GrepToolOutput
|
||||
| WebFetchToolOutput
|
||||
| WriteToolOutput
|
||||
// 工具渲染器接口
|
||||
export interface ToolRenderer {
|
||||
|
||||
@ -34,6 +34,7 @@ const ChooseTool = (toolResponse: NormalToolResponse): React.ReactNode | null =>
|
||||
case 'WebSearch':
|
||||
case 'Grep':
|
||||
case 'Write':
|
||||
case 'WebFetch':
|
||||
return <MessageAgentTools toolResponse={toolResponse} />
|
||||
default:
|
||||
return null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user