Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"use client";

import { useState, useEffect, useCallback, useMemo } from "react";
import { useState, useEffect, useCallback } from "react";
import { useTranslation } from "react-i18next";
import ToolConfigModal from "./tool/ToolConfigModal";
import { ToolGroup, Tool, ToolParam } from "@/types/agentConfig";
import { Tabs, Collapse, message, Tooltip } from "antd";
import { useAgentConfigStore } from "@/stores/agentConfigStore";
import { useToolList } from "@/hooks/agent/useToolList";
import { useModelList } from "@/hooks/model/useModelList";
import { usePrefetchKnowledgeBases } from "@/hooks/useKnowledgeBaseSelector";
import { useConfig } from "@/hooks/useConfig";
import { updateToolConfig } from "@/services/agentConfigService";
Expand Down Expand Up @@ -100,73 +99,7 @@ export default function ToolManagement({
// Use tool list hook for data management
const { availableTools } = useToolList();

// Get config for model checks
const { modelConfig: tenantModelConfig } = useConfig();

// Get VLM models to check availability
const { availableVlmModels, models } = useModelList();

// Check if VLM is properly configured:
// 1. Must have at least one VLM model that passed health check (available)
// 2. Must have a VLM model selected in tenant configuration
const isVlmConfigured = useMemo(() => {
// Check if there's any available VLM model
if (!availableVlmModels || availableVlmModels.length === 0) {
return false;
}

// Check if tenant configuration has selected a VLM model
try {
const selectedVlmModelName = tenantModelConfig?.vlm?.modelName || tenantModelConfig?.vlm?.displayName;

if (!selectedVlmModelName) {
return false;
}

// Check if the selected VLM model exists in available models
const isSelectedModelAvailable = availableVlmModels.some(
(model) => model.name === selectedVlmModelName || model.displayName === selectedVlmModelName
);

return isSelectedModelAvailable;
} catch (error) {
return false;
}
}, [availableVlmModels, models, tenantModelConfig]);

// Get Embedding models to check availability
const { availableEmbeddingModels } = useModelList();

// Check if Embedding is properly configured:
// 1. Must have at least one Embedding model that passed health check (available)
// 2. Must have an Embedding model selected in tenant configuration
const isEmbeddingConfigured = useMemo(() => {
// Check if there's any available Embedding model
if (!availableEmbeddingModels || availableEmbeddingModels.length === 0) {
return false;
}

// Check if tenant configuration has selected an Embedding model
try {
const selectedEmbeddingModelName =
tenantModelConfig?.embedding?.modelName || tenantModelConfig?.embedding?.displayName;

if (!selectedEmbeddingModelName) {
return false;
}

// Check if the selected Embedding model exists in available models
const isSelectedModelAvailable = availableEmbeddingModels.some(
(model) =>
model.name === selectedEmbeddingModelName ||
model.displayName === selectedEmbeddingModelName
);

return isSelectedModelAvailable;
} catch (error) {
return false;
}
}, [availableEmbeddingModels, models, tenantModelConfig]);
const { isVlmAvailable, isEmbeddingAvailable } = useConfig();

// Prefetch knowledge bases for KB tools
const { prefetchKnowledgeBases } = usePrefetchKnowledgeBases();
Expand Down Expand Up @@ -237,9 +170,7 @@ export default function ToolManagement({
(t) => parseInt(t.id) === parseInt(tool.id)
);
// Merge configured tool with original tool to ensure all fields are present
const toolToUse = configuredTool
? { ...tool, ...configuredTool, initParams: configuredTool.initParams }
: tool;
const toolToUse = configuredTool ? { ...tool, ...configuredTool, initParams: configuredTool.initParams } : tool;

// Get merged parameters (for editing mode, merge with instance params)
const mergedParams = await mergeToolParamsWithInstance(
Expand All @@ -266,62 +197,18 @@ export default function ToolManagement({
}

// Get latest tools directly from store to avoid stale closure issues
const currentSelectdTools =
useAgentConfigStore.getState().editedAgent.tools;
const currentSelectdTools = useAgentConfigStore.getState().editedAgent.tools;
const isCurrentlySelected = currentSelectdTools.some(
(t) => parseInt(t.id) === numericId
);

if (isCurrentlySelected) {
// If already selected, deselect it
const newSelectedTools = currentSelectdTools.filter(
(t) => parseInt(t.id) !== numericId
);
const newSelectedTools = currentSelectdTools.filter((t) => parseInt(t.id) !== numericId);
updateTools(newSelectedTools);
} else {
// If not selected, check for duplicate tool names first
const duplicateTool = currentSelectdTools.find(
(selectedTool) => selectedTool.name === tool.name
);

if (duplicateTool) {
// Show confirmation modal for duplicate tool name
return new Promise<void>((resolve) => {
confirm({
title: t("toolPool.duplicateToolName.title"),
content: t("toolPool.duplicateToolName.content", {
toolName: tool.name,
}),
okText: t("toolPool.duplicateToolName.confirm"),
cancelText: t("toolPool.duplicateToolName.cancel"),
danger: true,
onOk: async () => {
// User confirmed, proceed with tool selection
await proceedWithToolSelection();
resolve();
},
onCancel: () => {
// User cancelled, do nothing
resolve();
},
});
});
}

// No duplicate, proceed with normal tool selection
await proceedWithToolSelection();
}

// Helper function to proceed with tool selection after duplicate check
async function proceedWithToolSelection() {
// Get latest tools again to ensure we have the most up-to-date list
const currentSelectdTools =
useAgentConfigStore.getState().editedAgent.tools;

// Determine tool params and check if modal is needed
const configuredTool = currentSelectdTools.find(
(t) => parseInt(t.id) === numericId
);
// If not selected, determine tool params and check if modal is needed
const configuredTool = currentSelectdTools.find((t) => parseInt(t.id) === numericId);
// Merge configured tool with original tool to ensure all fields are present
const toolToUse = configuredTool
? { ...tool, ...configuredTool, initParams: configuredTool.initParams }
Expand Down Expand Up @@ -358,42 +245,6 @@ export default function ToolManagement({
},
];
updateTools(newSelectedTools);

// In non-creating mode, immediately save tool config to backend
if (!isCreatingMode && currentAgentId) {
try {
// Convert params to backend format
const paramsObj = mergedParams.reduce(
(acc, param) => {
acc[param.name] = param.value;
return acc;
},
{} as Record<string, any>
);

const isEnabled = true; // New tool is enabled by default
const result = await updateToolConfig(
numericId,
currentAgentId,
paramsObj,
isEnabled
);

if (result.success) {
// Invalidate queries to refresh tool info
queryClient.invalidateQueries({
queryKey: ["toolInfo", numericId, currentAgentId],
});
} else {
message.error(
result.message || t("toolConfig.message.saveError")
);
}
} catch (error) {
console.error("Failed to save tool config:", error);
message.error(t("toolConfig.message.saveError"));
}
}
}
}
};
Expand Down Expand Up @@ -469,8 +320,8 @@ export default function ToolManagement({
const isSelected = originalSelectedToolIdsSet.has(
tool.id
);
const isDisabledDueToVlm = isToolDisabledDueToVlm(tool.name, isVlmConfigured);
const isDisabledDueToEmbedding = isToolDisabledDueToEmbedding(tool.name, isEmbeddingConfigured);
const isDisabledDueToVlm = isToolDisabledDueToVlm(tool.name, isVlmAvailable);
const isDisabledDueToEmbedding = isToolDisabledDueToEmbedding(tool.name, isEmbeddingAvailable);
const isDisabled = isDisabledDueToVlm || isDisabledDueToEmbedding || isReadOnly;
// Tooltip priority: permission > VLM > Embedding
const tooltipTitle = isReadOnly
Expand Down Expand Up @@ -574,8 +425,8 @@ export default function ToolManagement({
>
{group.tools.map((tool) => {
const isSelected = originalSelectedToolIdsSet.has(tool.id);
const isDisabledDueToVlm = isToolDisabledDueToVlm(tool.name, isVlmConfigured);
const isDisabledDueToEmbedding = isToolDisabledDueToEmbedding(tool.name, isEmbeddingConfigured);
const isDisabledDueToVlm = isToolDisabledDueToVlm(tool.name, isVlmAvailable);
const isDisabledDueToEmbedding = isToolDisabledDueToEmbedding(tool.name, isEmbeddingAvailable);
const isDisabled = isDisabledDueToVlm || isDisabledDueToEmbedding || isReadOnly;
// Tooltip priority: permission > VLM > Embedding
const tooltipTitle = isReadOnly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -746,51 +746,10 @@ export default function ToolConfigModal({
newSelectedTools = [...currentTools, updatedTool];
}

// For editing mode (when currentAgentId exists), always call API
// For creating mode (isCreatingMode=true), update local state only
if (isCreatingMode) {
// In creating mode, just update local state
updateTools(newSelectedTools);
message.success(t("toolConfig.message.saveSuccess"));
handleClose(); // Close modal
return;
}

if (!currentAgentId) {
// Should not happen in normal editing mode, but handle gracefully
updateTools(newSelectedTools);
message.success(t("toolConfig.message.saveSuccess"));
handleClose(); // Close modal
return;
}

// Edit mode: call API to persist changes
try {
setIsLoading(true);
const isEnabled = true; // New tool is enabled by default
const result = await updateToolConfig(
parseInt(toolToSave.id),
currentAgentId,
paramsObj,
isEnabled
);
setIsLoading(false);

if (result.success) {
// Update local state and invalidate queries
updateTools(newSelectedTools);
queryClient.invalidateQueries({
queryKey: ["toolInfo", parseInt(toolToSave.id), currentAgentId],
});
message.success(t("toolConfig.message.saveSuccess"));
handleClose(); // Close modal
} else {
message.error(result.message || t("toolConfig.message.saveError"));
}
} catch (error) {
setIsLoading(false);
message.error(t("toolConfig.message.saveError"));
}
// Update local state only - actual save will happen when user clicks "Save Agent"
updateTools(newSelectedTools);
message.success(t("toolConfig.message.saveSuccess"));
handleClose(); // Close modal

// Call original onSave if provided
if (onSave) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { generatePromptStream } from "@/services/promptService";
import { useAuthorizationContext } from "@/components/providers/AuthorizationProvider";
import { useDeployment } from "@/components/providers/deploymentProvider";
import { useModelList } from "@/hooks/model/useModelList";
import { useConfig } from "@/hooks/useConfig";
import { useTenantList } from "@/hooks/tenant/useTenantList";
import { useGroupList } from "@/hooks/group/useGroupList";
import { USER_ROLES } from "@/const/auth";
Expand Down Expand Up @@ -62,8 +63,24 @@ export default function AgentGenerateDetail({
const updateBusinessInfo = useAgentConfigStore((state) => state.updateBusinessInfo);
const updateProfileInfo = useAgentConfigStore((state) => state.updateProfileInfo);

// Model data from React Query
const { availableLlmModels, defaultLlmModel, isLoading: loadingModels } = useModelList();
// Model data: default LLM name from config, resolve to full model from model list
const { defaultLlmModelName } = useConfig();
const { availableLlmModels, models, isLoading: loadingModels } = useModelList();
const defaultLlmModel = useMemo(() => {
if (defaultLlmModelName) {
const found = availableLlmModels.find(
(m) => m.name === defaultLlmModelName || m.displayName === defaultLlmModelName
);
if (found) return found;
return models.find(
(m) =>
m.type === "llm" &&
(m.name === defaultLlmModelName || m.displayName === defaultLlmModelName)
);
}
// No default configured: use the first available LLM, or undefined if none
return availableLlmModels[0];
}, [defaultLlmModelName, availableLlmModels, models]);

// Tenant & group data for group selection
const { data: tenantData } = useTenantList();
Expand Down
8 changes: 8 additions & 0 deletions frontend/app/[locale]/agents/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function AgentSetupOrchestrator() {
const { pageVariants, pageTransition } = useSetupFlow();
const searchParams = useSearchParams();
const enterCreateMode = useAgentConfigStore((state) => state.enterCreateMode);
const reset = useAgentConfigStore((state) => state.reset);

// Local UI state for version panel
const [isShowVersionManagePanel, setIsShowVersionManagePanel] = useState(false);
Expand All @@ -32,6 +33,13 @@ export default function AgentSetupOrchestrator() {
}
}, [searchParams, enterCreateMode]);

// Reset agent selection state when leaving the page
useEffect(() => {
return () => {
reset();
};
}, [reset]);

return (
<div className="w-full h-full p-8">
<motion.div
Expand Down
Loading
Loading