diff --git a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx index 52b73924cc..c78b4a98c6 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-parameter-modal/agent-model-trigger.tsx @@ -72,6 +72,7 @@ const AgentModelTrigger: FC = ({ const [inModelList, setInModelList] = useState(false) const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const handleOpenModal = useModelModalHandler() + useEffect(() => { (async () => { if (modelId && currentProvider) { @@ -96,11 +97,8 @@ const AgentModelTrigger: FC = ({ catch (error) { // pass } - setIsPluginChecked(true) - } - else { - setIsPluginChecked(true) } + setIsPluginChecked(true) })() }, [providerName, modelId, currentProvider]) diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index d00b65f16d..1be8498788 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -119,7 +119,7 @@ const ToolSelector: FC = ({ }, } onSelect(toolValue) - setIsShowChooseTool(false) + // setIsShowChooseTool(false) } const handleDescriptionChange = (e: React.ChangeEvent) => { diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 3dd520d39a..0c74f90a1b 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -130,11 +130,15 @@ const PluginItem: FC = ({ packageName={name} packageNameClassName='w-auto max-w-[150px]' /> -
·
-
- - {t('plugin.endpointsEnabled', { num: endpoints_active })} -
+ {category === PluginType.extension && ( + <> +
·
+
+ + {t('plugin.endpointsEnabled', { num: endpoints_active })} +
+ + )}
@@ -154,7 +158,7 @@ const PluginItem: FC = ({ && <>
{t('plugin.from')} marketplace
- +
} diff --git a/web/app/components/workflow/hooks/use-checklist.ts b/web/app/components/workflow/hooks/use-checklist.ts index 722ae5f032..b7e717de2b 100644 --- a/web/app/components/workflow/hooks/use-checklist.ts +++ b/web/app/components/workflow/hooks/use-checklist.ts @@ -35,7 +35,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { const buildInTools = useStore(s => s.buildInTools) const customTools = useStore(s => s.customTools) const workflowTools = useStore(s => s.workflowTools) - const { data: agentStrategies } = useStrategyProviders() + const { data: strategyProviders } = useStrategyProviders() const needWarningNodes = useMemo(() => { const list = [] @@ -62,12 +62,14 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { if (node.data.type === BlockEnum.Agent) { const data = node.data as AgentNodeType - const provider = agentStrategies?.find(s => s.plugin_unique_identifier === data.plugin_unique_identifier) + const isReadyForCheckValid = !!strategyProviders + const provider = strategyProviders?.find(provider => provider.declaration.identity.name === data.agent_strategy_provider_name) const strategy = provider?.declaration.strategies?.find(s => s.identity.name === data.agent_strategy_name) moreDataForCheckValid = { provider, strategy, language, + isReadyForCheckValid, } } @@ -106,7 +108,7 @@ export const useChecklist = (nodes: Node[], edges: Edge[]) => { } return list - }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, agentStrategies]) + }, [nodes, edges, isChatMode, buildInTools, customTools, workflowTools, language, nodesExtraData, t, strategyProviders]) return needWarningNodes } diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/index.ts b/web/app/components/workflow/hooks/use-workflow-run-event/index.ts index 61216017e0..70528f7e79 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/index.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/index.ts @@ -9,3 +9,4 @@ export * from './use-workflow-node-iteration-finished' export * from './use-workflow-node-retry' export * from './use-workflow-text-chunk' export * from './use-workflow-text-replace' +export * from './use-workflow-agent-log' diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts new file mode 100644 index 0000000000..9a9fa628c0 --- /dev/null +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-agent-log.ts @@ -0,0 +1,50 @@ +import { useCallback } from 'react' +import produce from 'immer' +import type { AgentLogResponse } from '@/types/workflow' +import { useWorkflowStore } from '@/app/components/workflow/store' + +export const useWorkflowAgentLog = () => { + const workflowStore = useWorkflowStore() + + const handleWorkflowAgentLog = useCallback((params: AgentLogResponse) => { + const { data } = params + const { + workflowRunningData, + setWorkflowRunningData, + } = workflowStore.getState() + + setWorkflowRunningData(produce(workflowRunningData!, (draft) => { + const currentIndex = draft.tracing!.findIndex(item => item.node_id === data.node_id) + if (currentIndex > -1) { + const current = draft.tracing![currentIndex] + + if (current.execution_metadata) { + if (current.execution_metadata.agent_log) { + const currentLogIndex = current.execution_metadata.agent_log.findIndex(log => log.id === data.id) + if (currentLogIndex > -1) { + current.execution_metadata.agent_log[currentLogIndex] = { + ...current.execution_metadata.agent_log[currentLogIndex], + ...data, + } + } + else { + current.execution_metadata.agent_log.push(data) + } + } + else { + current.execution_metadata.agent_log = [data] + } + } + else { + current.execution_metadata = { + agent_log: [data], + } as any + } + } + })) + }, [workflowStore]) + + return { + handleWorkflowAgentLog, + } +} diff --git a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event.ts b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event.ts index 82b76da22d..8ba6220818 100644 --- a/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event.ts +++ b/web/app/components/workflow/hooks/use-workflow-run-event/use-workflow-run-event.ts @@ -1,4 +1,5 @@ import { + useWorkflowAgentLog, useWorkflowFailed, useWorkflowFinished, useWorkflowNodeFinished, @@ -24,6 +25,7 @@ export const useWorkflowRunEvent = () => { const { handleWorkflowNodeRetry } = useWorkflowNodeRetry() const { handleWorkflowTextChunk } = useWorkflowTextChunk() const { handleWorkflowTextReplace } = useWorkflowTextReplace() + const { handleWorkflowAgentLog } = useWorkflowAgentLog() return { handleWorkflowStarted, @@ -37,5 +39,6 @@ export const useWorkflowRunEvent = () => { handleWorkflowNodeRetry, handleWorkflowTextChunk, handleWorkflowTextReplace, + handleWorkflowAgentLog, } } diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index f49ffc6b5b..6fe02d91da 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -37,6 +37,7 @@ export const useWorkflowRun = () => { handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeRetry, + handleWorkflowAgentLog, handleWorkflowTextChunk, handleWorkflowTextReplace, } = useWorkflowRunEvent() @@ -118,6 +119,7 @@ export const useWorkflowRun = () => { onIterationNext, onIterationFinish, onNodeRetry, + onAgentLog, onError, ...restCallback } = callback || {} @@ -234,6 +236,12 @@ export const useWorkflowRun = () => { if (onNodeRetry) onNodeRetry(params) }, + onAgentLog: (params) => { + handleWorkflowAgentLog(params) + + if (onAgentLog) + onAgentLog(params) + }, onTextChunk: (params) => { handleWorkflowTextChunk(params) }, @@ -252,7 +260,7 @@ export const useWorkflowRun = () => { ...restCallback, }, ) - }, [store, workflowStore, doSyncWorkflowDraft, handleWorkflowStarted, handleWorkflowFinished, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeRetry, handleWorkflowTextChunk, handleWorkflowTextReplace, pathname]) + }, [store, workflowStore, doSyncWorkflowDraft, handleWorkflowStarted, handleWorkflowFinished, handleWorkflowFailed, handleWorkflowNodeStarted, handleWorkflowNodeFinished, handleWorkflowNodeIterationStarted, handleWorkflowNodeIterationNext, handleWorkflowNodeIterationFinished, handleWorkflowNodeRetry, handleWorkflowTextChunk, handleWorkflowTextReplace, handleWorkflowAgentLog, pathname]) const handleStopRun = useCallback((taskId: string) => { const appId = useAppStore.getState().appDetail?.id diff --git a/web/app/components/workflow/nodes/agent/components/model-bar.tsx b/web/app/components/workflow/nodes/agent/components/model-bar.tsx index dffbf40d36..1b2007070f 100644 --- a/web/app/components/workflow/nodes/agent/components/model-bar.tsx +++ b/web/app/components/workflow/nodes/agent/components/model-bar.tsx @@ -35,14 +35,22 @@ export const ModelBar: FC = (props) => { const { t } = useTranslation() const modelList = useAllModel() if (!('provider' in props)) { - return + return +
+ + +
+
} const modelInstalled = modelList?.some( provider => provider.provider === props.provider && provider.models.some(model => model.model === props.model)) diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index 0d2d3c2837..07c20e0f80 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -18,6 +18,7 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() + const isDataReady = !!buildInTools && !!customTools && !!workflowTools const currentProvider = useMemo(() => { const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] return mergedTools.find((toolWithProvider) => { @@ -33,10 +34,11 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { return iconFromMarketPlace }, [author, currentProvider, name]) const status: Status = useMemo(() => { + if (!isDataReady) return undefined if (!currentProvider) return 'not-installed' if (currentProvider.is_team_authorization === false) return 'not-authorized' return undefined - }, [currentProvider]) + }, [currentProvider, isDataReady]) const indicator = status === 'not-installed' ? 'red' : status === 'not-authorized' ? 'yellow' : undefined const notSuccess = (['not-installed', 'not-authorized'] as Array).includes(status) const { t } = useTranslation() diff --git a/web/app/components/workflow/nodes/agent/default.ts b/web/app/components/workflow/nodes/agent/default.ts index 26c004b188..3ece9d44bc 100644 --- a/web/app/components/workflow/nodes/agent/default.ts +++ b/web/app/components/workflow/nodes/agent/default.ts @@ -21,8 +21,15 @@ const nodeDefault: NodeDefault = { strategyProvider?: StrategyPluginDetail, strategy?: StrategyDetail language: string + isReadyForCheckValid: boolean }) { - const { strategy, language } = moreDataForCheckValid + const { strategy, language, isReadyForCheckValid } = moreDataForCheckValid + if (!isReadyForCheckValid) { + return { + isValid: true, + errorMessage: '', + } + } if (!strategy) { return { isValid: false, diff --git a/web/app/components/workflow/nodes/agent/node.tsx b/web/app/components/workflow/nodes/agent/node.tsx index 0bc2fb4caf..b3101c3d88 100644 --- a/web/app/components/workflow/nodes/agent/node.tsx +++ b/web/app/components/workflow/nodes/agent/node.tsx @@ -24,7 +24,6 @@ const AgentNode: FC> = (props) => { .filter(param => param.type === FormTypeEnum.modelSelector) .reduce((acc, param) => { const item = inputs.agent_parameters?.[param.name]?.value - console.log({ item }) if (!item) { if (param.required) { acc.push({ param: param.name }) @@ -68,20 +67,26 @@ const AgentNode: FC> = (props) => { {inputs.agent_strategy_name ? {inputs.agent_strategy_label} : } - 0 && {t('workflow.nodes.agent.model')} } @@ -92,7 +97,7 @@ const AgentNode: FC> = (props) => { key={model.param} /> })} - + } {tools.length > 0 && {t('workflow.nodes.agent.toolbox')} }> diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index d91cf1082d..81cccd9cc7 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -404,6 +404,63 @@ export const useChat = ( })) } }, + onAgentLog: ({ data }) => { + const currentNodeIndex = responseItem.workflowProcess!.tracing!.findIndex(item => item.node_id === data.node_id) + if (currentNodeIndex > -1) { + const current = responseItem.workflowProcess!.tracing![currentNodeIndex] + + if (current.execution_metadata) { + if (current.execution_metadata.agent_log) { + const currentLogIndex = current.execution_metadata.agent_log.findIndex(log => log.id === data.id) + if (currentLogIndex > -1) { + current.execution_metadata.agent_log[currentLogIndex] = { + ...current.execution_metadata.agent_log[currentLogIndex], + ...data, + } + } + else { + current.execution_metadata.agent_log.push(data) + } + } + else { + current.execution_metadata.agent_log = [data] + } + } + else { + current.execution_metadata = { + agent_log: [data], + } as any + } + // if (current.agentLog) { + // const currentLogIndex = current.agentLog.findIndex(log => log.id === data.id) + + // if (currentLogIndex > -1) { + // current.agentLog[currentLogIndex] = { + // ...current.agentLog[currentLogIndex], + // ...data, + // } + // } + // else { + // current.agentLog.push(data) + // } + // } + // else { + // current.agentLog = [data] + // } + + responseItem.workflowProcess!.tracing[currentNodeIndex] = { + ...current, + } + + handleUpdateChatList(produce(chatListRef.current, (draft) => { + const currentIndex = draft.findIndex(item => item.id === responseItem.id) + draft[currentIndex] = { + ...draft[currentIndex], + ...responseItem, + } + })) + } + }, }, ) }, [handleRun, handleResponding, handleUpdateChatList, notify, t, updateCurrentQA, config.suggested_questions_after_answer?.enabled, formSettings]) diff --git a/web/app/components/workflow/run/agent-log/agent-log-item.tsx b/web/app/components/workflow/run/agent-log/agent-log-item.tsx index 49c279d58a..0b84827500 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-item.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-item.tsx @@ -1,4 +1,7 @@ -import { useState } from 'react' +import { + useMemo, + useState, +} from 'react' import { RiArrowRightSLine, RiListView, @@ -9,6 +12,9 @@ import type { AgentLogItemWithChildren } from '@/types/workflow' import NodeStatusIcon from '@/app/components/workflow/nodes/_base/components/node-status-icon' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import BlockIcon from '@/app/components/workflow/block-icon' +import { BlockEnum } from '@/app/components/workflow/types' +import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' type AgentLogItemProps = { item: AgentLogItemWithChildren @@ -26,6 +32,26 @@ const AgentLogItem = ({ metadata, } = item const [expanded, setExpanded] = useState(false) + const { getIconUrl } = useGetIcon() + const toolIcon = useMemo(() => { + const icon = metadata?.icon + + if (icon) { + if (icon.includes('http')) + return icon + + return getIconUrl(icon) + } + + return '' + }, [getIconUrl, metadata?.icon]) + + const mergeStatus = useMemo(() => { + if (status === 'start') + return 'running' + + return status + }, [status]) return (
@@ -41,7 +67,11 @@ const AgentLogItem = ({ ? : } -
+
{metadata?.elapsed_time?.toFixed(3)}s
) } - +
{ expanded && ( diff --git a/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx index 589624f559..86418a1c69 100644 --- a/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx +++ b/web/app/components/workflow/run/agent-log/agent-log-trigger.tsx @@ -14,21 +14,29 @@ const AgentLogTrigger = ({ onShowAgentOrToolLog, }: AgentLogTriggerProps) => { const { t } = useTranslation() - const { agentLog } = nodeInfo + const { agentLog, execution_metadata } = nodeInfo + const agentStrategy = execution_metadata?.tool_info?.agent_strategy return ( -
+
{ + onShowAgentOrToolLog({ id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren) + }} + >
{t('workflow.nodes.agent.strategy.label')}
-
-
+ { + agentStrategy && ( +
+ {agentStrategy} +
+ ) + }
{ - onShowAgentOrToolLog({ id: nodeInfo.id, children: agentLog || [] } as AgentLogItemWithChildren) - }} > {t('runLog.detail')} diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index bbdcac19ac..53c50073c0 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -735,6 +735,7 @@ const translation = { strategyNotSet: 'Agentic strategy Not Set', tools: 'Tools', maxIterations: 'Max Iterations', + modelNotSelected: 'Model not selected', modelNotInstallTooltip: 'This model is not installed', toolNotInstallTooltip: '{{tool}} is not installed', toolNotAuthorizedTooltip: '{{tool}} Not Authorized', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ea4ae6654a..c622581e2d 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -736,6 +736,7 @@ const translation = { tools: '工具', maxIterations: '最大迭代次数', modelNotInstallTooltip: '此模型未安装', + modelNotSelected: '未选择模型', toolNotInstallTooltip: '{{tool}} 未安装', toolNotAuthorizedTooltip: '{{tool}} 未授权', strategyNotInstallTooltip: '{{strategy}} 未安装', diff --git a/web/service/base.ts b/web/service/base.ts index c34a1f0e9c..38aaae0b1c 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -4,6 +4,7 @@ import Toast from '@/app/components/base/toast' import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type' import type { VisionFile } from '@/types/app' import type { + AgentLogResponse, IterationFinishedResponse, IterationNextResponse, IterationStartedResponse, @@ -53,6 +54,7 @@ export type IOnTextChunk = (textChunk: TextChunkResponse) => void export type IOnTTSChunk = (messageId: string, audioStr: string, audioType?: string) => void export type IOnTTSEnd = (messageId: string, audioStr: string, audioType?: string) => void export type IOnTextReplace = (textReplace: TextReplaceResponse) => void +export type IOnAgentLog = (agentLog: AgentLogResponse) => void export type IOtherOptions = { isPublicAPI?: boolean @@ -84,6 +86,7 @@ export type IOtherOptions = { onTTSChunk?: IOnTTSChunk onTTSEnd?: IOnTTSEnd onTextReplace?: IOnTextReplace + onAgentLog?: IOnAgentLog } function unicodeToChar(text: string) { @@ -129,6 +132,7 @@ const handleStream = ( onTTSChunk?: IOnTTSChunk, onTTSEnd?: IOnTTSEnd, onTextReplace?: IOnTextReplace, + onAgentLog?: IOnAgentLog, ) => { if (!response.ok) throw new Error('Network response was not ok') @@ -229,6 +233,9 @@ const handleStream = ( else if (bufferObj.event === 'text_replace') { onTextReplace?.(bufferObj as TextReplaceResponse) } + else if (bufferObj.event === 'agent_log') { + onAgentLog?.(bufferObj as AgentLogResponse) + } else if (bufferObj.event === 'tts_message') { onTTSChunk?.(bufferObj.message_id, bufferObj.audio, bufferObj.audio_type) } @@ -322,6 +329,7 @@ export const ssePost = ( onTTSChunk, onTTSEnd, onTextReplace, + onAgentLog, onError, getAbortController, } = otherOptions @@ -392,7 +400,7 @@ export const ssePost = ( return } onData?.(str, isFirstMessage, moreInfo) - }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onNodeRetry, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace) + }, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onIterationStart, onIterationNext, onIterationFinish, onNodeRetry, onParallelBranchStarted, onParallelBranchFinished, onTextChunk, onTTSChunk, onTTSEnd, onTextReplace, onAgentLog) }).catch((e) => { if (e.toString() !== 'AbortError: The user aborted a request.' && !e.toString().errorMessage.includes('TypeError: Cannot assign to read only property')) Toast.notify({ type: 'error', message: e }) diff --git a/web/types/workflow.ts b/web/types/workflow.ts index 9cc1cbac15..31ea759582 100644 --- a/web/types/workflow.ts +++ b/web/types/workflow.ts @@ -6,6 +6,7 @@ import type { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/ export type AgentLogItem = { node_execution_id: string, id: string, + node_id: string, parent_id?: string, label: string, data: object, // debug data @@ -14,6 +15,7 @@ export type AgentLogItem = { metadata?: { elapsed_time?: number provider?: string + icon?: string }, } @@ -51,6 +53,10 @@ export type NodeTracing = { iteration_duration_map?: IterationDurationMap error_strategy?: ErrorHandleTypeEnum agent_log?: AgentLogItem[] + tool_info?: { + agent_strategy?: string + icon?: string + } } metadata: { iterator_length: number @@ -230,6 +236,12 @@ export type TextReplaceResponse = { } } +export type AgentLogResponse = { + task_id: string + event: string + data: AgentLogItemWithChildren +} + export type WorkflowRunHistory = { id: string sequence_number: number