mirror of
https://github.com/langgenius/dify.git
synced 2026-01-11 08:51:54 +00:00
Compare commits
13 Commits
detached
...
feat/llm-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d3d8b35d9 | ||
|
|
c323028179 | ||
|
|
70149ea05e | ||
|
|
1d93f41fcf | ||
|
|
04f40303fd | ||
|
|
ececc5ec2c | ||
|
|
e83635ee5a | ||
|
|
d79372a46d | ||
|
|
bbd11c9e89 | ||
|
|
d132abcdb4 | ||
|
|
d60348572e | ||
|
|
0cff94d90e | ||
|
|
a7859de625 |
@@ -21,6 +21,7 @@ import BasicContent from './basic-content'
|
||||
import More from './more'
|
||||
import Operation from './operation'
|
||||
import SuggestedQuestions from './suggested-questions'
|
||||
import ToolCalls from './tool-calls'
|
||||
import WorkflowProcessItem from './workflow-process'
|
||||
|
||||
type AnswerProps = {
|
||||
@@ -61,6 +62,7 @@ const Answer: FC<AnswerProps> = ({
|
||||
workflowProcess,
|
||||
allFiles,
|
||||
message_files,
|
||||
toolCalls,
|
||||
} = item
|
||||
const hasAgentThoughts = !!agent_thoughts?.length
|
||||
|
||||
@@ -154,6 +156,11 @@ const Answer: FC<AnswerProps> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!toolCalls?.length && (
|
||||
<ToolCalls toolCalls={toolCalls} />
|
||||
)
|
||||
}
|
||||
{
|
||||
responding && contentIsEmpty && !hasAgentThoughts && (
|
||||
<div className="flex h-5 w-6 items-center justify-center">
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ToolCallItem } from '@/types/workflow'
|
||||
import ToolCallItemComponent from '@/app/components/workflow/run/llm-log/tool-call-item'
|
||||
|
||||
type ToolCallsProps = {
|
||||
toolCalls: ToolCallItem[]
|
||||
}
|
||||
const ToolCalls = ({
|
||||
toolCalls,
|
||||
}: ToolCallsProps) => {
|
||||
return (
|
||||
<div className="my-1 space-y-1">
|
||||
{toolCalls.map((toolCall: ToolCallItem, index: number) => (
|
||||
<ToolCallItemComponent
|
||||
key={index}
|
||||
payload={toolCall}
|
||||
className="bg-background-gradient-bg-fill-chat-bubble-bg-2 shadow-none"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolCalls
|
||||
@@ -45,7 +45,7 @@ const WorkflowProcessItem = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'-mx-1 rounded-xl px-2.5',
|
||||
'rounded-xl px-2.5',
|
||||
collapse ? 'border-l-[0.25px] border-components-panel-border py-[7px]' : 'border-[0.5px] border-components-panel-border-subtle px-1 pb-1 pt-[7px]',
|
||||
running && !collapse && 'bg-background-section-burn',
|
||||
succeeded && !collapse && 'bg-state-success-hover',
|
||||
|
||||
@@ -319,6 +319,9 @@ export const useChat = (
|
||||
return player
|
||||
}
|
||||
|
||||
let toolCallId = ''
|
||||
let thoughtId = ''
|
||||
|
||||
ssePost(
|
||||
url,
|
||||
{
|
||||
@@ -326,7 +329,19 @@ export const useChat = (
|
||||
},
|
||||
{
|
||||
isPublicAPI,
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
onData: (message: string, isFirstMessage: boolean, {
|
||||
conversationId: newConversationId,
|
||||
messageId,
|
||||
taskId,
|
||||
chunk_type,
|
||||
tool_icon,
|
||||
tool_icon_dark,
|
||||
tool_name,
|
||||
tool_arguments,
|
||||
tool_files,
|
||||
tool_error,
|
||||
tool_elapsed_time,
|
||||
}: any) => {
|
||||
if (!isAgentMode) {
|
||||
responseItem.content = responseItem.content + message
|
||||
}
|
||||
@@ -336,6 +351,57 @@ export const useChat = (
|
||||
lastThought.thought = lastThought.thought + message // need immer setAutoFreeze
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_call') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
toolCallId = uuidV4()
|
||||
responseItem.toolCalls?.push({
|
||||
id: toolCallId,
|
||||
type: 'tool',
|
||||
toolName: tool_name,
|
||||
toolArguments: tool_arguments,
|
||||
toolIcon: tool_icon,
|
||||
toolIconDark: tool_icon_dark,
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_result') {
|
||||
const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1
|
||||
|
||||
if (currentToolCallIndex > -1) {
|
||||
responseItem.toolCalls![currentToolCallIndex].toolError = tool_error
|
||||
responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time
|
||||
responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files
|
||||
responseItem.toolCalls![currentToolCallIndex].toolOutput = message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_start') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
thoughtId = uuidV4()
|
||||
responseItem.toolCalls.push({
|
||||
id: thoughtId,
|
||||
type: 'thought',
|
||||
thoughtOutput: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_end') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId && !hasSetResponseId) {
|
||||
questionItem.id = `question-${messageId}`
|
||||
responseItem.id = messageId
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { InputVarType } from '@/app/components/workflow/types'
|
||||
import type { Annotation, MessageRating } from '@/models/log'
|
||||
import type { FileResponse } from '@/types/workflow'
|
||||
import type { FileResponse, ToolCallItem } from '@/types/workflow'
|
||||
|
||||
export type MessageMore = {
|
||||
time: string
|
||||
@@ -104,6 +104,7 @@ export type IChatItem = {
|
||||
siblingIndex?: number
|
||||
prevSibling?: string
|
||||
nextSibling?: string
|
||||
toolCalls?: ToolCallItem[]
|
||||
}
|
||||
|
||||
export type Metadata = {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 9.49479C0.782372 8.51826 0 7.01768 0 5.33333C0 2.38782 2.38782 0 5.33333 0C8.20841 0 10.5503 2.27504 10.6608 5.12305L11.888 6.96354C12.0843 7.25794 12.0161 7.65424 11.7331 7.86654L10.6667 8.66602V10C10.6667 10.7364 10.0697 11.3333 9.33333 11.3333H8V13.3333H6.66667V10.6667C6.66667 10.2985 6.96514 10 7.33333 10H9.33333V8.33333C9.33333 8.12349 9.43239 7.92603 9.60026 7.80013L10.4284 7.17838L9.44531 5.70312C9.3723 5.59361 9.33333 5.46495 9.33333 5.33333C9.33333 3.1242 7.54248 1.33333 5.33333 1.33333C3.1242 1.33333 1.33333 3.1242 1.33333 5.33333C1.33333 6.69202 2.0103 7.89261 3.04818 8.61654C3.2269 8.74119 3.33329 8.94552 3.33333 9.16341V13.3333H2V9.49479Z" fill="#354052"/>
|
||||
<path d="M6.04367 4.24012L5.6504 3.21778C5.59993 3.08657 5.47393 3 5.33333 3C5.19273 3 5.06673 3.08657 5.01627 3.21778L4.62303 4.24012C4.55531 4.41618 4.41618 4.55531 4.24012 4.62303L3.21778 5.01624C3.08657 5.0667 3 5.19276 3 5.33333C3 5.47393 3.08657 5.59993 3.21778 5.6504L4.24012 6.04367C4.41618 6.11133 4.55531 6.25047 4.62303 6.42653L5.01627 7.44887C5.06673 7.58007 5.19273 7.66667 5.33333 7.66667C5.47393 7.66667 5.59993 7.58007 5.6504 7.44887L6.04367 6.42653C6.11133 6.25047 6.25047 6.11133 6.42653 6.04367L7.44887 5.6504C7.58007 5.59993 7.66667 5.47393 7.66667 5.33333C7.66667 5.19276 7.58007 5.0667 7.44887 5.01624L6.42653 4.62303C6.25047 4.55531 6.11133 4.41618 6.04367 4.24012Z" fill="#354052"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "12",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 12 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M2 9.49479C0.782372 8.51826 0 7.01768 0 5.33333C0 2.38782 2.38782 0 5.33333 0C8.20841 0 10.5503 2.27504 10.6608 5.12305L11.888 6.96354C12.0843 7.25794 12.0161 7.65424 11.7331 7.86654L10.6667 8.66602V10C10.6667 10.7364 10.0697 11.3333 9.33333 11.3333H8V13.3333H6.66667V10.6667C6.66667 10.2985 6.96514 10 7.33333 10H9.33333V8.33333C9.33333 8.12349 9.43239 7.92603 9.60026 7.80013L10.4284 7.17838L9.44531 5.70312C9.3723 5.59361 9.33333 5.46495 9.33333 5.33333C9.33333 3.1242 7.54248 1.33333 5.33333 1.33333C3.1242 1.33333 1.33333 3.1242 1.33333 5.33333C1.33333 6.69202 2.0103 7.89261 3.04818 8.61654C3.2269 8.74119 3.33329 8.94552 3.33333 9.16341V13.3333H2V9.49479Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M6.04367 4.24012L5.6504 3.21778C5.59993 3.08657 5.47393 3 5.33333 3C5.19273 3 5.06673 3.08657 5.01627 3.21778L4.62303 4.24012C4.55531 4.41618 4.41618 4.55531 4.24012 4.62303L3.21778 5.01624C3.08657 5.0667 3 5.19276 3 5.33333C3 5.47393 3.08657 5.59993 3.21778 5.6504L4.24012 6.04367C4.41618 6.11133 4.55531 6.25047 4.62303 6.42653L5.01627 7.44887C5.06673 7.58007 5.19273 7.66667 5.33333 7.66667C5.47393 7.66667 5.59993 7.58007 5.6504 7.44887L6.04367 6.42653C6.11133 6.25047 6.25047 6.11133 6.42653 6.04367L7.44887 5.6504C7.58007 5.59993 7.66667 5.47393 7.66667 5.33333C7.66667 5.19276 7.58007 5.0667 7.44887 5.01624L6.42653 4.62303C6.25047 4.55531 6.11133 4.41618 6.04367 4.24012Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Thinking"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './Thinking.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Thinking'
|
||||
|
||||
export default Icon
|
||||
@@ -24,6 +24,7 @@ export { default as ParameterExtractor } from './ParameterExtractor'
|
||||
export { default as QuestionClassifier } from './QuestionClassifier'
|
||||
export { default as Schedule } from './Schedule'
|
||||
export { default as TemplatingTransform } from './TemplatingTransform'
|
||||
export { default as Thinking } from './Thinking'
|
||||
export { default as TriggerAll } from './TriggerAll'
|
||||
export { default as VariableX } from './VariableX'
|
||||
export { default as WebhookLine } from './WebhookLine'
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { FC } from 'react'
|
||||
import {
|
||||
RiFileTextLine,
|
||||
RiFilmAiLine,
|
||||
RiHammerLine,
|
||||
RiImageCircleAiLine,
|
||||
RiVoiceAiFill,
|
||||
} from '@remixicon/react'
|
||||
@@ -38,17 +39,33 @@ const FeatureIcon: FC<FeatureIconProps> = ({
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (feature === ModelFeatureEnum.toolCall) {
|
||||
// return (
|
||||
// <Tooltip
|
||||
// popupContent={t('common.modelProvider.featureSupported', { feature: ModelFeatureTextEnum.toolCall })}
|
||||
// >
|
||||
// <ModelBadge className={`mr-0.5 !px-0 w-[18px] justify-center text-gray-500 ${className}`}>
|
||||
// <MagicWand className='w-3 h-3' />
|
||||
// </ModelBadge>
|
||||
// </Tooltip>
|
||||
// )
|
||||
// }
|
||||
if (feature === ModelFeatureEnum.toolCall) {
|
||||
if (showFeaturesLabel) {
|
||||
return (
|
||||
<ModelBadge className={cn('gap-x-0.5', className)}>
|
||||
<RiHammerLine className="size-3" />
|
||||
<span>{ModelFeatureTextEnum.toolCall}</span>
|
||||
</ModelBadge>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.toolCall })}
|
||||
>
|
||||
<div className="inline-block cursor-help">
|
||||
<ModelBadge
|
||||
className={cn(
|
||||
'w-[18px] justify-center !px-0',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<RiHammerLine className="size-3" />
|
||||
</ModelBadge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
// if (feature === ModelFeatureEnum.multiToolCall) {
|
||||
// return (
|
||||
|
||||
@@ -96,6 +96,14 @@ const PopupItem: FC<PopupItemProps> = ({
|
||||
<div className='text-text-tertiary system-xs-regular'>{currentProvider?.description?.[language] || currentProvider?.description?.en_US}</div>
|
||||
)} */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{
|
||||
modelItem.features?.includes(ModelFeatureEnum.toolCall) && (
|
||||
<FeatureIcon
|
||||
feature={ModelFeatureEnum.toolCall}
|
||||
showFeaturesLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
{modelItem.model_type && (
|
||||
<ModelBadge>
|
||||
{modelTypeFormat(modelItem.model_type)}
|
||||
@@ -118,7 +126,7 @@ const PopupItem: FC<PopupItemProps> = ({
|
||||
<div className="pt-2">
|
||||
<div className="system-2xs-medium-uppercase mb-1 text-text-tertiary">{t('model.capabilities', { ns: 'common' })}</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{modelItem.features?.map(feature => (
|
||||
{modelItem.features?.filter(feature => feature !== ModelFeatureEnum.toolCall).map(feature => (
|
||||
<FeatureIcon
|
||||
key={feature}
|
||||
feature={feature}
|
||||
|
||||
@@ -150,6 +150,10 @@ export const LLM_OUTPUT_STRUCT: Var[] = [
|
||||
variable: 'usage',
|
||||
type: VarType.object,
|
||||
},
|
||||
{
|
||||
variable: 'generation',
|
||||
type: VarType.object,
|
||||
},
|
||||
]
|
||||
|
||||
export const KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT: Var[] = [
|
||||
|
||||
@@ -6,17 +6,20 @@ export type BoxProps = {
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
withBorderTop?: boolean
|
||||
}
|
||||
export const Box = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
withBorderTop,
|
||||
}: BoxProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'py-2',
|
||||
withBorderBottom && 'border-b border-divider-subtle',
|
||||
withBorderTop && 'border-t border-divider-subtle',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { cn } from '@/utils/classnames'
|
||||
|
||||
export type FieldTitleProps = {
|
||||
title?: string
|
||||
className?: string
|
||||
operation?: ReactNode
|
||||
subTitle?: string | ReactNode
|
||||
tooltip?: string
|
||||
@@ -19,6 +20,7 @@ export type FieldTitleProps = {
|
||||
}
|
||||
export const FieldTitle = memo(({
|
||||
title,
|
||||
className,
|
||||
operation,
|
||||
subTitle,
|
||||
tooltip,
|
||||
@@ -31,7 +33,7 @@ export const FieldTitle = memo(({
|
||||
const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal
|
||||
|
||||
return (
|
||||
<div className={cn('mb-0.5', !!subTitle && 'mb-1')}>
|
||||
<div className={cn('mb-0.5', !!subTitle && 'mb-1', className)}>
|
||||
<div
|
||||
className="group/collapse flex items-center justify-between py-1"
|
||||
onClick={() => {
|
||||
|
||||
@@ -6,17 +6,20 @@ export type GroupProps = {
|
||||
className?: string
|
||||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
withBorderTop?: boolean
|
||||
}
|
||||
export const Group = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
withBorderTop,
|
||||
}: GroupProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'px-4 py-2',
|
||||
withBorderBottom && 'border-b border-divider-subtle',
|
||||
withBorderTop && 'border-t border-divider-subtle',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||
|
||||
const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
@@ -18,3 +20,27 @@ const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
}
|
||||
|
||||
export default useNodeCrud
|
||||
|
||||
export const useNodeCurdKit = <T>(id: string) => {
|
||||
const store = useStoreApi()
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
|
||||
const getNodeData = useCallback(() => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
||||
return nodes.find(node => node.id === id)
|
||||
}, [store, id])
|
||||
|
||||
const handleNodeDataUpdate = useCallback((data: Partial<CommonNodeType<T>>) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data,
|
||||
})
|
||||
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||
|
||||
return {
|
||||
getNodeData,
|
||||
handleNodeDataUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
|
||||
import { BoxGroup } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import MaxIterations from './max-iterations'
|
||||
import { useNodeTools } from './use-node-tools'
|
||||
|
||||
type ToolsProps = {
|
||||
nodeId: string
|
||||
tools?: ToolValue[]
|
||||
maxIterations?: number
|
||||
}
|
||||
const Tools = ({
|
||||
nodeId,
|
||||
tools = [],
|
||||
maxIterations = 10,
|
||||
}: ToolsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
handleToolsChange,
|
||||
handleMaxIterationsChange,
|
||||
} = useNodeTools(nodeId)
|
||||
|
||||
return (
|
||||
<BoxGroup
|
||||
boxProps={{
|
||||
withBorderBottom: true,
|
||||
withBorderTop: true,
|
||||
}}
|
||||
groupProps={{
|
||||
className: 'px-0',
|
||||
}}
|
||||
>
|
||||
<MultipleToolSelector
|
||||
nodeId={nodeId}
|
||||
nodeOutputVars={[]}
|
||||
availableNodes={[]}
|
||||
value={tools}
|
||||
label={t(`nodes.llm.tools.title`, { ns: 'workflow' })}
|
||||
tooltip={t(`nodes.llm.tools.title`, { ns: 'workflow' })}
|
||||
onChange={handleToolsChange}
|
||||
supportCollapse
|
||||
/>
|
||||
<MaxIterations
|
||||
value={maxIterations}
|
||||
onChange={handleMaxIterationsChange}
|
||||
/>
|
||||
</BoxGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Tools)
|
||||
@@ -0,0 +1,40 @@
|
||||
import { memo } from 'react'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type MaxIterationsProps = {
|
||||
value?: number
|
||||
onChange?: (value: number) => void
|
||||
}
|
||||
const MaxIterations = ({ value = 10, onChange }: MaxIterationsProps) => {
|
||||
return (
|
||||
<div className="mt-3 flex h-10 items-center">
|
||||
<div className="system-sm-semibold mr-0.5 truncate uppercase text-text-secondary">Max Iterations</div>
|
||||
<Tooltip
|
||||
popupContent="Max Iterations is the maximum number of iterations to run the tool."
|
||||
triggerClassName="shrink-0 w-4 h-4"
|
||||
/>
|
||||
<div className="mr-3 flex grow items-center justify-end">
|
||||
<Slider
|
||||
className="w-[124px]"
|
||||
value={value}
|
||||
onChange={onChange ?? (() => {})}
|
||||
min={1}
|
||||
max={99}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
<InputNumber
|
||||
className="w-10 shrink-0"
|
||||
value={value}
|
||||
onChange={onChange ?? (() => {})}
|
||||
min={1}
|
||||
max={99}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(MaxIterations)
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { LLMNodeType } from '../../types'
|
||||
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
||||
import { useNodeCurdKit } from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
|
||||
export const useNodeTools = (nodeId: string) => {
|
||||
const { handleNodeDataUpdate } = useNodeCurdKit<LLMNodeType>(nodeId)
|
||||
|
||||
const handleToolsChange = (tools: ToolValue[]) => {
|
||||
handleNodeDataUpdate({
|
||||
tools,
|
||||
})
|
||||
}
|
||||
const handleMaxIterationsChange = (maxIterations: number) => {
|
||||
handleNodeDataUpdate({
|
||||
max_iterations: maxIterations,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
handleToolsChange,
|
||||
handleMaxIterationsChange,
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import VarReferencePicker from '../_base/components/variable/var-reference-picke
|
||||
import ConfigPrompt from './components/config-prompt'
|
||||
import ReasoningFormatConfig from './components/reasoning-format-config'
|
||||
import StructureOutput from './components/structure-output'
|
||||
import Tools from './components/tools'
|
||||
import useConfig from './use-config'
|
||||
|
||||
const i18nPrefix = 'nodes.llm'
|
||||
@@ -233,6 +234,12 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
<Tools
|
||||
nodeId={id}
|
||||
tools={inputs.tools}
|
||||
maxIterations={inputs.max_iterations}
|
||||
/>
|
||||
|
||||
{/* Vision: GPT4-vision and so on */}
|
||||
<ConfigVision
|
||||
nodeId={id}
|
||||
@@ -308,6 +315,11 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
type="object"
|
||||
description={t(`${i18nPrefix}.outputVars.usage`, { ns: 'workflow' })}
|
||||
/>
|
||||
<VarItem
|
||||
name="generation"
|
||||
type="object"
|
||||
description={t(`${i18nPrefix}.outputVars.generation`, { ns: 'workflow' })}
|
||||
/>
|
||||
{inputs.structured_output_enabled && (
|
||||
<>
|
||||
<Split className="mt-3" />
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
|
||||
import type { CommonNodeType, Memory, ModelConfig, PromptItem, ValueSelector, Variable, VisionSetting } from '@/app/components/workflow/types'
|
||||
|
||||
export type Tool = {
|
||||
enabled: boolean
|
||||
type: string
|
||||
provider_name: 'plugin' | 'builtin' | 'api' | 'workflow' | 'app' | 'dataset-retrieval'
|
||||
tool_name: string
|
||||
plugin_unique_identifier?: string
|
||||
credential_id?: string
|
||||
parameters?: Record<string, any>
|
||||
settings?: Record<string, any>
|
||||
extra?: Record<string, any>
|
||||
}
|
||||
|
||||
export type LLMNodeType = CommonNodeType & {
|
||||
model: ModelConfig
|
||||
prompt_template: PromptItem[] | PromptItem
|
||||
@@ -18,6 +31,8 @@ export type LLMNodeType = CommonNodeType & {
|
||||
structured_output_enabled?: boolean
|
||||
structured_output?: StructuredOutput
|
||||
reasoning_format?: 'tagged' | 'separated'
|
||||
tools?: ToolValue[]
|
||||
max_iterations?: number
|
||||
}
|
||||
|
||||
export enum Type {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { v4 as uuidV4 } from 'uuid'
|
||||
import {
|
||||
getProcessedInputs,
|
||||
processOpeningStatement,
|
||||
@@ -266,13 +267,78 @@ export const useChat = (
|
||||
}
|
||||
|
||||
let hasSetResponseId = false
|
||||
let toolCallId = ''
|
||||
let thoughtId = ''
|
||||
|
||||
handleRun(
|
||||
bodyParams,
|
||||
{
|
||||
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }: any) => {
|
||||
onData: (message: string, isFirstMessage: boolean, {
|
||||
conversationId: newConversationId,
|
||||
messageId,
|
||||
taskId,
|
||||
chunk_type,
|
||||
tool_icon,
|
||||
tool_icon_dark,
|
||||
tool_name,
|
||||
tool_arguments,
|
||||
tool_files,
|
||||
tool_error,
|
||||
tool_elapsed_time,
|
||||
}: any) => {
|
||||
responseItem.content = responseItem.content + message
|
||||
|
||||
if (chunk_type === 'tool_call') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
toolCallId = uuidV4()
|
||||
responseItem.toolCalls?.push({
|
||||
id: toolCallId,
|
||||
type: 'tool',
|
||||
toolName: tool_name,
|
||||
toolArguments: tool_arguments,
|
||||
toolIcon: tool_icon,
|
||||
toolIconDark: tool_icon_dark,
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'tool_result') {
|
||||
const currentToolCallIndex = responseItem.toolCalls?.findIndex(item => item.id === toolCallId) ?? -1
|
||||
|
||||
if (currentToolCallIndex > -1) {
|
||||
responseItem.toolCalls![currentToolCallIndex].toolError = tool_error
|
||||
responseItem.toolCalls![currentToolCallIndex].toolDuration = tool_elapsed_time
|
||||
responseItem.toolCalls![currentToolCallIndex].toolFiles = tool_files
|
||||
responseItem.toolCalls![currentToolCallIndex].toolOutput = message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_start') {
|
||||
if (!responseItem.toolCalls)
|
||||
responseItem.toolCalls = []
|
||||
thoughtId = uuidV4()
|
||||
responseItem.toolCalls.push({
|
||||
id: thoughtId,
|
||||
type: 'thought',
|
||||
thoughtOutput: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_type === 'thought_end') {
|
||||
const currentThoughtIndex = responseItem.toolCalls?.findIndex(item => item.id === thoughtId) ?? -1
|
||||
if (currentThoughtIndex > -1) {
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtOutput += message
|
||||
responseItem.toolCalls![currentThoughtIndex].thoughtCompleted = true
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId && !hasSetResponseId) {
|
||||
questionItem.id = `question-${messageId}`
|
||||
responseItem.id = messageId
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
AgentLogItemWithChildren,
|
||||
IterationDurationMap,
|
||||
LLMTraceItem,
|
||||
LoopDurationMap,
|
||||
LoopVariableMap,
|
||||
NodeTracing,
|
||||
@@ -79,8 +80,18 @@ export const useLogs = () => {
|
||||
}
|
||||
}, [setAgentOrToolLogItemStack, setAgentOrToolLogListMap])
|
||||
|
||||
const [showLLMDetail, {
|
||||
setTrue: setShowLLMDetailTrue,
|
||||
setFalse: setShowLLMDetailFalse,
|
||||
}] = useBoolean(false)
|
||||
const [llmResultList, setLLMResultList] = useState<LLMTraceItem[]>([])
|
||||
const handleShowLLMDetail = useCallback((detail: LLMTraceItem[]) => {
|
||||
setShowLLMDetailTrue()
|
||||
setLLMResultList(detail)
|
||||
}, [setShowLLMDetailTrue, setLLMResultList])
|
||||
|
||||
return {
|
||||
showSpecialResultPanel: showRetryDetail || showIteratingDetail || showLoopingDetail || !!agentOrToolLogItemStack.length,
|
||||
showSpecialResultPanel: showRetryDetail || showIteratingDetail || showLoopingDetail || !!agentOrToolLogItemStack.length || showLLMDetail,
|
||||
showRetryDetail,
|
||||
setShowRetryDetailTrue,
|
||||
setShowRetryDetailFalse,
|
||||
@@ -111,5 +122,12 @@ export const useLogs = () => {
|
||||
agentOrToolLogItemStack,
|
||||
agentOrToolLogListMap,
|
||||
handleShowAgentOrToolLog,
|
||||
|
||||
showLLMDetail,
|
||||
setShowLLMDetailTrue,
|
||||
setShowLLMDetailFalse,
|
||||
llmResultList,
|
||||
setLLMResultList,
|
||||
handleShowLLMDetail,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ const RunPanel: FC<RunProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
{/* panel detail */}
|
||||
<div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-xl bg-components-panel-bg')}>
|
||||
<div ref={ref} className={cn('relative h-0 grow overflow-y-auto rounded-b-xl bg-background-section')}>
|
||||
{loading && (
|
||||
<div className="flex h-full items-center justify-center bg-components-panel-bg">
|
||||
<Loading />
|
||||
@@ -192,7 +192,7 @@ const RunPanel: FC<RunProps> = ({
|
||||
)}
|
||||
{!loading && currentTab === 'TRACING' && (
|
||||
<TracingPanel
|
||||
className="bg-background-section-burn"
|
||||
className="bg-background-section"
|
||||
list={list}
|
||||
/>
|
||||
)}
|
||||
|
||||
2
web/app/components/workflow/run/llm-log/index.tsx
Normal file
2
web/app/components/workflow/run/llm-log/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as LLMLogTrigger } from './llm-log-trigger'
|
||||
export { default as LLMResultPanel } from './llm-result-panel'
|
||||
41
web/app/components/workflow/run/llm-log/llm-log-trigger.tsx
Normal file
41
web/app/components/workflow/run/llm-log/llm-log-trigger.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { LLMTraceItem, NodeTracing } from '@/types/workflow'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Thinking } from '@/app/components/base/icons/src/vender/workflow'
|
||||
|
||||
type LLMLogTriggerProps = {
|
||||
nodeInfo: NodeTracing
|
||||
onShowLLMDetail: (detail: LLMTraceItem[]) => void
|
||||
}
|
||||
const LLMLogTrigger = ({
|
||||
nodeInfo,
|
||||
onShowLLMDetail,
|
||||
}: LLMLogTriggerProps) => {
|
||||
const { t } = useTranslation()
|
||||
const llmTrace = nodeInfo?.execution_metadata?.llm_trace || []
|
||||
|
||||
const handleShowLLMDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onShowLLMDetail(llmTrace || [])
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="mb-1 flex w-full items-center justify-between"
|
||||
variant="tertiary"
|
||||
onClick={handleShowLLMDetail}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Thinking className="mr-[5px] h-4 w-4 shrink-0 text-components-button-tertiary-text" />
|
||||
{t('detail', { ns: 'runLog' })}
|
||||
</div>
|
||||
<RiArrowRightSLine className="h-4 w-4 shrink-0 text-components-button-tertiary-text" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default LLMLogTrigger
|
||||
73
web/app/components/workflow/run/llm-log/llm-result-panel.tsx
Normal file
73
web/app/components/workflow/run/llm-log/llm-result-panel.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type {
|
||||
LLMTraceItem,
|
||||
ToolCallItem,
|
||||
} from '@/types/workflow'
|
||||
import {
|
||||
RiArrowLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ToolCallItemComponent from '@/app/components/workflow/run/llm-log/tool-call-item'
|
||||
|
||||
type Props = {
|
||||
list: LLMTraceItem[]
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
const LLMResultPanel: FC<Props> = ({
|
||||
list,
|
||||
onBack,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const formattedList = list.map((item) => {
|
||||
if (item.type === 'tool') {
|
||||
return {
|
||||
type: 'tool',
|
||||
toolName: item.name,
|
||||
toolProvider: item.provider,
|
||||
toolIcon: item.icon,
|
||||
toolIconDark: item.icon_dark,
|
||||
toolArguments: item.output.arguments,
|
||||
toolOutput: item.output.output,
|
||||
toolDuration: item.duration,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'model',
|
||||
modelName: item.name,
|
||||
modelProvider: item.provider,
|
||||
modelIcon: item.icon,
|
||||
modelIconDark: item.icon_dark,
|
||||
modelOutput: item.output,
|
||||
modelDuration: item.duration,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="system-sm-medium flex h-8 cursor-pointer items-center bg-components-panel-bg px-4 text-text-accent-secondary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onBack()
|
||||
}}
|
||||
>
|
||||
<RiArrowLeftLine className="mr-1 h-4 w-4" />
|
||||
{t('singleRun.back', { ns: 'workflow' })}
|
||||
</div>
|
||||
<div className="space-y-1 p-2">
|
||||
{
|
||||
formattedList.map((item, index) => (
|
||||
<ToolCallItemComponent key={index} payload={item as ToolCallItem} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default memo(LLMResultPanel)
|
||||
152
web/app/components/workflow/run/llm-log/tool-call-item.tsx
Normal file
152
web/app/components/workflow/run/llm-log/tool-call-item.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import type { ToolCallItem } from '@/types/workflow'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Thinking } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type ToolCallItemComponentProps = {
|
||||
className?: string
|
||||
payload: ToolCallItem
|
||||
}
|
||||
const ToolCallItemComponent = ({
|
||||
className,
|
||||
payload,
|
||||
}: ToolCallItemComponentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [expand, setExpand] = useState(false)
|
||||
return (
|
||||
<div
|
||||
className={cn('rounded-[10px] border-[0.5px] border-components-panel-border bg-background-default-subtle px-2 pb-1 pt-2 shadow-xs', className)}
|
||||
>
|
||||
<div
|
||||
className="mb-1 flex cursor-pointer items-center hover:bg-background-gradient-bg-fill-chat-bubble-bg-2"
|
||||
onClick={() => {
|
||||
setExpand(!expand)
|
||||
}}
|
||||
>
|
||||
{
|
||||
payload.type === 'thought' && (
|
||||
<Thinking className="mr-1 h-4 w-4 shrink-0" />
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<BlockIcon
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={payload.toolIcon}
|
||||
className="mr-1 h-4 w-4 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'model' && (
|
||||
<AppIcon
|
||||
iconType={typeof payload.modelIcon === 'string' ? 'image' : undefined}
|
||||
imageUrl={typeof payload.modelIcon === 'string' ? payload.modelIcon : undefined}
|
||||
background={typeof payload.modelIcon === 'object' ? payload.modelIcon.background : undefined}
|
||||
className="mr-1 h-4 w-4 shrink-0"
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'thought' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.thoughtOutput}>
|
||||
{
|
||||
payload.thoughtCompleted && !expand && (payload.thoughtOutput || '') as string
|
||||
}
|
||||
{
|
||||
payload.thoughtCompleted && expand && 'THOUGHT'
|
||||
}
|
||||
{
|
||||
!payload.thoughtCompleted && 'THINKING...'
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.toolName}>{payload.toolName}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'model' && (
|
||||
<div className="system-xs-medium mr-1 grow truncate text-text-secondary" title={payload.modelName}>{payload.modelName}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!payload.toolDuration && (
|
||||
<div className="system-xs-regular mr-1 shrink-0 text-text-tertiary">
|
||||
{payload.toolDuration?.toFixed(1)}
|
||||
s
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!payload.modelDuration && (
|
||||
<div className="system-xs-regular mr-1 shrink-0 text-text-tertiary">
|
||||
{payload.modelDuration?.toFixed(1)}
|
||||
s
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<RiArrowDownSLine className="h-4 w-4 shrink-0" />
|
||||
</div>
|
||||
{
|
||||
expand && (
|
||||
<div className="relative px-2 pl-9">
|
||||
<div className="absolute bottom-1 left-2 top-1 w-[1px] bg-divider-regular"></div>
|
||||
{
|
||||
payload.type === 'thought' && typeof payload.thoughtOutput === 'string' && (
|
||||
<div className="body-sm-medium text-text-tertiary">{payload.thoughtOutput}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'model' && (
|
||||
<CodeEditor
|
||||
readOnly
|
||||
title={<div>{t('common.data', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.modelOutput}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<CodeEditor
|
||||
readOnly
|
||||
title={<div>{t('common.input', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.toolArguments}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
payload.type === 'tool' && (
|
||||
<CodeEditor
|
||||
readOnly
|
||||
className="mt-1"
|
||||
title={<div>{t('common.output', { ns: 'workflow' })}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={payload.toolOutput}
|
||||
isJSONStringifyBeauty
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToolCallItemComponent
|
||||
@@ -3,6 +3,7 @@ import type { FC } from 'react'
|
||||
import type {
|
||||
AgentLogItemWithChildren,
|
||||
IterationDurationMap,
|
||||
LLMTraceItem,
|
||||
LoopDurationMap,
|
||||
LoopVariableMap,
|
||||
NodeTracing,
|
||||
@@ -29,6 +30,7 @@ import { BlockEnum } from '../types'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
import { AgentLogTrigger } from './agent-log'
|
||||
import { IterationLogTrigger } from './iteration-log'
|
||||
import { LLMLogTrigger } from './llm-log'
|
||||
import { LoopLogTrigger } from './loop-log'
|
||||
import { RetryLogTrigger } from './retry-log'
|
||||
|
||||
@@ -43,6 +45,7 @@ type Props = {
|
||||
onShowLoopDetail?: (detail: NodeTracing[][], loopDurationMap: LoopDurationMap, loopVariableMap: LoopVariableMap) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
onShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void
|
||||
onShowLLMDetail?: (detail: LLMTraceItem[]) => void
|
||||
notShowIterationNav?: boolean
|
||||
notShowLoopNav?: boolean
|
||||
}
|
||||
@@ -58,6 +61,7 @@ const NodePanel: FC<Props> = ({
|
||||
onShowLoopDetail,
|
||||
onShowRetryDetail,
|
||||
onShowAgentOrToolLog,
|
||||
onShowLLMDetail,
|
||||
notShowIterationNav,
|
||||
notShowLoopNav,
|
||||
}) => {
|
||||
@@ -96,6 +100,7 @@ const NodePanel: FC<Props> = ({
|
||||
const isRetryNode = hasRetryNode(nodeInfo.node_type) && !!nodeInfo.retryDetail?.length
|
||||
const isAgentNode = nodeInfo.node_type === BlockEnum.Agent && !!nodeInfo.agentLog?.length
|
||||
const isToolNode = nodeInfo.node_type === BlockEnum.Tool && !!nodeInfo.agentLog?.length
|
||||
const isLLMNode = nodeInfo.node_type === BlockEnum.LLM && !!nodeInfo.execution_metadata?.llm_trace?.length
|
||||
|
||||
const inputsTitle = useMemo(() => {
|
||||
let text = t('common.input', { ns: 'workflow' })
|
||||
@@ -193,6 +198,12 @@ const NodePanel: FC<Props> = ({
|
||||
onShowRetryResultList={onShowRetryDetail}
|
||||
/>
|
||||
)}
|
||||
{isLLMNode && onShowLLMDetail && (
|
||||
<LLMLogTrigger
|
||||
nodeInfo={nodeInfo}
|
||||
onShowLLMDetail={onShowLLMDetail}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
(isAgentNode || isToolNode) && onShowAgentOrToolLog && (
|
||||
<AgentLogTrigger
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import type {
|
||||
AgentLogItemWithChildren,
|
||||
LLMTraceItem,
|
||||
NodeTracing,
|
||||
} from '@/types/workflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -15,6 +16,7 @@ import { RetryLogTrigger } from '@/app/components/workflow/run/retry-log'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||
import LargeDataAlert from '../variable-inspect/large-data-alert'
|
||||
import { LLMLogTrigger } from './llm-log'
|
||||
import MetaData from './meta'
|
||||
import StatusPanel from './status'
|
||||
|
||||
@@ -45,6 +47,7 @@ export type ResultPanelProps = {
|
||||
handleShowLoopResultList?: (detail: NodeTracing[][], loopDurationMap: any) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
handleShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void
|
||||
onShowLLMDetail?: (detail: LLMTraceItem[]) => void
|
||||
}
|
||||
|
||||
const ResultPanel: FC<ResultPanelProps> = ({
|
||||
@@ -71,6 +74,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
handleShowLoopResultList,
|
||||
onShowRetryDetail,
|
||||
handleShowAgentOrToolLog,
|
||||
onShowLLMDetail,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const isIterationNode = nodeInfo?.node_type === BlockEnum.Iteration && !!nodeInfo?.details?.length
|
||||
@@ -78,6 +82,7 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
const isRetryNode = hasRetryNode(nodeInfo?.node_type) && !!nodeInfo?.retryDetail?.length
|
||||
const isAgentNode = nodeInfo?.node_type === BlockEnum.Agent && !!nodeInfo?.agentLog?.length
|
||||
const isToolNode = nodeInfo?.node_type === BlockEnum.Tool && !!nodeInfo?.agentLog?.length
|
||||
const isLLMNode = nodeInfo?.node_type === BlockEnum.LLM && !!nodeInfo?.execution_metadata?.llm_trace?.length
|
||||
|
||||
return (
|
||||
<div className="bg-components-panel-bg py-2">
|
||||
@@ -116,6 +121,14 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
isLLMNode && onShowLLMDetail && (
|
||||
<LLMLogTrigger
|
||||
nodeInfo={nodeInfo}
|
||||
onShowLLMDetail={onShowLLMDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
(isAgentNode || isToolNode) && handleShowAgentOrToolLog && (
|
||||
<AgentLogTrigger
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type {
|
||||
AgentLogItemWithChildren,
|
||||
IterationDurationMap,
|
||||
LLMTraceItem,
|
||||
LoopDurationMap,
|
||||
LoopVariableMap,
|
||||
NodeTracing,
|
||||
} from '@/types/workflow'
|
||||
import { AgentResultPanel } from './agent-log'
|
||||
import { IterationResultPanel } from './iteration-log'
|
||||
import { LLMResultPanel } from './llm-log'
|
||||
import { LoopResultPanel } from './loop-log'
|
||||
import { RetryResultPanel } from './retry-log'
|
||||
|
||||
@@ -29,6 +31,10 @@ export type SpecialResultPanelProps = {
|
||||
agentOrToolLogItemStack?: AgentLogItemWithChildren[]
|
||||
agentOrToolLogListMap?: Record<string, AgentLogItemWithChildren[]>
|
||||
handleShowAgentOrToolLog?: (detail?: AgentLogItemWithChildren) => void
|
||||
|
||||
showLLMDetail?: boolean
|
||||
setShowLLMDetailFalse?: () => void
|
||||
llmResultList?: LLMTraceItem[]
|
||||
}
|
||||
const SpecialResultPanel = ({
|
||||
showRetryDetail,
|
||||
@@ -49,6 +55,10 @@ const SpecialResultPanel = ({
|
||||
agentOrToolLogItemStack,
|
||||
agentOrToolLogListMap,
|
||||
handleShowAgentOrToolLog,
|
||||
|
||||
showLLMDetail,
|
||||
setShowLLMDetailFalse,
|
||||
llmResultList,
|
||||
}: SpecialResultPanelProps) => {
|
||||
return (
|
||||
<div onClick={(e) => {
|
||||
@@ -64,6 +74,14 @@ const SpecialResultPanel = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!showLLMDetail && !!llmResultList?.length && setShowLLMDetailFalse && (
|
||||
<LLMResultPanel
|
||||
list={llmResultList}
|
||||
onBack={setShowLLMDetailFalse}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
showIteratingDetail && !!iterationResultList?.length && setShowIteratingDetailFalse && (
|
||||
<IterationResultPanel
|
||||
|
||||
@@ -91,6 +91,11 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
||||
agentOrToolLogItemStack,
|
||||
agentOrToolLogListMap,
|
||||
handleShowAgentOrToolLog,
|
||||
|
||||
showLLMDetail,
|
||||
setShowLLMDetailFalse,
|
||||
llmResultList,
|
||||
handleShowLLMDetail,
|
||||
} = useLogs()
|
||||
|
||||
const renderNode = (node: NodeTracing) => {
|
||||
@@ -153,6 +158,7 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
||||
onShowLoopDetail={handleShowLoopResultList}
|
||||
onShowRetryDetail={handleShowRetryResultList}
|
||||
onShowAgentOrToolLog={handleShowAgentOrToolLog}
|
||||
onShowLLMDetail={handleShowLLMDetail}
|
||||
hideInfo={hideNodeInfo}
|
||||
hideProcessDetail={hideNodeProcessDetail}
|
||||
/>
|
||||
@@ -182,6 +188,10 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
||||
agentOrToolLogItemStack={agentOrToolLogItemStack}
|
||||
agentOrToolLogListMap={agentOrToolLogListMap}
|
||||
handleShowAgentOrToolLog={handleShowAgentOrToolLog}
|
||||
|
||||
showLLMDetail={showLLMDetail}
|
||||
setShowLLMDetailFalse={setShowLLMDetailFalse}
|
||||
llmResultList={llmResultList}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"common.currentDraftUnpublished": "Current Draft Unpublished",
|
||||
"common.currentView": "Current View",
|
||||
"common.currentWorkflow": "Current Workflow",
|
||||
"common.data": "Data",
|
||||
"common.debugAndPreview": "Preview",
|
||||
"common.disconnect": "Disconnect",
|
||||
"common.duplicate": "Duplicate",
|
||||
@@ -650,6 +651,7 @@
|
||||
"nodes.llm.jsonSchema.warningTips.saveSchema": "Please finish editing the current field before saving the schema",
|
||||
"nodes.llm.model": "model",
|
||||
"nodes.llm.notSetContextInPromptTip": "To enable the context feature, please fill in the context variable in PROMPT.",
|
||||
"nodes.llm.outputVars.generation": "Generation Information",
|
||||
"nodes.llm.outputVars.output": "Generate content",
|
||||
"nodes.llm.outputVars.reasoning_content": "Reasoning Content",
|
||||
"nodes.llm.outputVars.usage": "Model Usage Information",
|
||||
@@ -666,6 +668,7 @@
|
||||
"nodes.llm.roleDescription.user": "Provide instructions, queries, or any text-based input to the model",
|
||||
"nodes.llm.singleRun.variable": "Variable",
|
||||
"nodes.llm.sysQueryInUser": "sys.query in user message is required",
|
||||
"nodes.llm.tools.title": "Tools",
|
||||
"nodes.llm.variables": "variables",
|
||||
"nodes.llm.vision": "vision",
|
||||
"nodes.loop.ErrorMethod.continueOnError": "Continue on Error",
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"common.currentDraftUnpublished": "当前草稿未发布",
|
||||
"common.currentView": "当前视图",
|
||||
"common.currentWorkflow": "整个工作流",
|
||||
"common.data": "数据",
|
||||
"common.debugAndPreview": "预览",
|
||||
"common.disconnect": "断开连接",
|
||||
"common.duplicate": "复制",
|
||||
@@ -650,6 +651,7 @@
|
||||
"nodes.llm.jsonSchema.warningTips.saveSchema": "请先完成当前字段的编辑",
|
||||
"nodes.llm.model": "模型",
|
||||
"nodes.llm.notSetContextInPromptTip": "要启用上下文功能,请在提示中填写上下文变量。",
|
||||
"nodes.llm.outputVars.generation": "生成信息",
|
||||
"nodes.llm.outputVars.output": "生成内容",
|
||||
"nodes.llm.outputVars.reasoning_content": "推理内容",
|
||||
"nodes.llm.outputVars.usage": "模型用量信息",
|
||||
|
||||
@@ -34,12 +34,27 @@ import { getWebAppPassport } from './webapp-auth'
|
||||
|
||||
const TIME_OUT = 100000
|
||||
|
||||
export type IconObject = {
|
||||
background: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export type IOnDataMoreInfo = {
|
||||
conversationId?: string
|
||||
taskId?: string
|
||||
messageId: string
|
||||
errorMessage?: string
|
||||
errorCode?: string
|
||||
chunk_type?: 'text' | 'tool_call' | 'tool_result' | 'thought' | 'thought_start' | 'thought_end'
|
||||
tool_call_id?: string
|
||||
tool_name?: string
|
||||
tool_arguments?: string
|
||||
tool_icon?: string | IconObject
|
||||
tool_icon_dark?: string | IconObject
|
||||
|
||||
tool_files?: string[]
|
||||
tool_error?: string
|
||||
tool_elapsed_time?: number
|
||||
}
|
||||
|
||||
export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
|
||||
@@ -234,6 +249,15 @@ export const handleStream = (
|
||||
conversationId: bufferObj.conversation_id,
|
||||
taskId: bufferObj.task_id,
|
||||
messageId: bufferObj.id,
|
||||
chunk_type: bufferObj.chunk_type,
|
||||
tool_call_id: bufferObj.tool_call_id,
|
||||
tool_name: bufferObj.tool_name,
|
||||
tool_arguments: bufferObj.tool_arguments,
|
||||
tool_icon: bufferObj.tool_icon,
|
||||
tool_icon_dark: bufferObj.tool_icon_dark,
|
||||
tool_files: bufferObj.tool_files,
|
||||
tool_error: bufferObj.tool_error,
|
||||
tool_elapsed_time: bufferObj.tool_elapsed_time,
|
||||
})
|
||||
isFirstMessage = false
|
||||
}
|
||||
|
||||
@@ -28,6 +28,68 @@ export type AgentLogItemWithChildren = AgentLogItem & {
|
||||
children: AgentLogItemWithChildren[]
|
||||
}
|
||||
|
||||
export type IconObject = {
|
||||
background: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export type ToolCallItem = {
|
||||
id: string
|
||||
type: 'model' | 'tool' | 'thought'
|
||||
thoughtCompleted?: boolean
|
||||
thoughtOutput?: string
|
||||
|
||||
toolName?: string
|
||||
toolProvider?: string
|
||||
toolIcon?: string | IconObject
|
||||
toolIconDark?: string | IconObject
|
||||
toolArguments?: string
|
||||
toolOutput?: Record<string, any> | string
|
||||
toolFiles?: string[]
|
||||
toolError?: string
|
||||
toolDuration?: number
|
||||
|
||||
modelName?: string
|
||||
modelProvider?: string
|
||||
modelOutput?: Record<string, any> | string
|
||||
modelDuration?: number
|
||||
modelIcon?: string | IconObject
|
||||
modelIconDark?: string | IconObject
|
||||
}
|
||||
|
||||
export type ToolCallDetail = {
|
||||
id: string
|
||||
name: string
|
||||
arguments: string
|
||||
output: string
|
||||
files: string[]
|
||||
error: string
|
||||
elapsed_time?: number
|
||||
status: string
|
||||
}
|
||||
export type SequenceSegment
|
||||
= | { type: 'context', start: number, end: number }
|
||||
| { type: 'reasoning', index: number }
|
||||
| { type: 'tool_call', index: number }
|
||||
|
||||
export type LLMLogItem = {
|
||||
reasoning_content: string[]
|
||||
tool_calls: ToolCallDetail[]
|
||||
sequence: SequenceSegment[]
|
||||
}
|
||||
|
||||
export type LLMTraceItem = {
|
||||
type: 'model' | 'tool'
|
||||
duration: number
|
||||
output: Record<string, any>
|
||||
provider?: string
|
||||
name: string
|
||||
icon?: string | IconObject
|
||||
icon_dark?: string | IconObject
|
||||
error?: string
|
||||
status?: 'success' | 'error'
|
||||
}
|
||||
|
||||
export type NodeTracing = {
|
||||
id: string
|
||||
index: number
|
||||
@@ -72,6 +134,7 @@ export type NodeTracing = {
|
||||
icon?: string
|
||||
}
|
||||
loop_variable_map?: Record<string, any>
|
||||
llm_trace?: LLMTraceItem[]
|
||||
}
|
||||
metadata: {
|
||||
iterator_length: number
|
||||
@@ -104,6 +167,7 @@ export type NodeTracing = {
|
||||
parent_parallel_id?: string
|
||||
parent_parallel_start_node_id?: string
|
||||
agentLog?: AgentLogItemWithChildren[] // agent log
|
||||
generation_detail?: LLMLogItem
|
||||
}
|
||||
|
||||
export type FetchWorkflowDraftResponse = {
|
||||
|
||||
Reference in New Issue
Block a user