Skip to content

Commit a708015

Browse files
committed
Merge branch 'develop' into csr-1204
# Conflicts: # frontend/app/[locale]/agents/components/AgentSetupOrchestrator.tsx
2 parents fbc9cb9 + ed72b99 commit a708015

File tree

8 files changed

+339
-152
lines changed

8 files changed

+339
-152
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ docker/openssh-server
2020
docker/volumes/db/data
2121
docker/.env
2222
docker/.run
23+
docker/deploy.options
2324

2425
frontend_standalone/
2526
.pnpm-store/

frontend/app/[locale]/agents/components/AgentSetupOrchestrator.tsx

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from "@/types/agentConfig";
2727
import AgentImportWizard from "@/components/agent/AgentImportWizard";
2828
import log from "@/lib/logger";
29+
import { useConfirmModal } from "@/hooks/useConfirmModal";
2930
import { useAuth } from "@/hooks/useAuth";
3031

3132
import SubAgentPool from "./agent/SubAgentPool";
@@ -154,7 +155,9 @@ export default function AgentSetupOrchestrator({
154155
detailReasons.length > 0 ? detailReasons : fallbackReasons;
155156

156157
const normalizedAvailability =
157-
typeof detail?.is_available === "boolean"
158+
normalizedReasons.length > 0
159+
? false
160+
: typeof detail?.is_available === "boolean"
158161
? detail.is_available
159162
: typeof fallback?.is_available === "boolean"
160163
? fallback.is_available
@@ -200,6 +203,7 @@ export default function AgentSetupOrchestrator({
200203

201204
const { t } = useTranslation("common");
202205
const { message } = App.useApp();
206+
const { confirm } = useConfirmModal();
203207

204208
// Common refresh agent list function, moved to the front to avoid hoisting issues
205209
const refreshAgentList = async (t: TFunction, clearTools: boolean = true) => {
@@ -1871,6 +1875,138 @@ export default function AgentSetupOrchestrator({
18711875
}
18721876
};
18731877

1878+
// Handle copy agent from list
1879+
const handleCopyAgentFromList = async (agent: Agent) => {
1880+
try {
1881+
// Fetch source agent detail before duplicating
1882+
const detailResult = await searchAgentInfo(Number(agent.id));
1883+
if (!detailResult.success || !detailResult.data) {
1884+
message.error(detailResult.message);
1885+
return;
1886+
}
1887+
const detail = detailResult.data;
1888+
1889+
// Prepare copy names
1890+
const copyName = `${detail.name || "agent"}_copy`;
1891+
const copyDisplayName = `${
1892+
detail.display_name || t("agentConfig.agents.defaultDisplayName")
1893+
}${t("agent.copySuffix")}`;
1894+
1895+
// Gather tool and sub-agent identifiers from the source agent
1896+
const tools = Array.isArray(detail.tools) ? detail.tools : [];
1897+
const unavailableTools = tools.filter(
1898+
(tool: any) => tool && tool.is_available === false
1899+
);
1900+
const unavailableToolNames = unavailableTools
1901+
.map(
1902+
(tool: any) =>
1903+
tool?.display_name || tool?.name || tool?.tool_name || ""
1904+
)
1905+
.filter((name: string) => Boolean(name));
1906+
1907+
const enabledToolIds = tools
1908+
.filter((tool: any) => tool && tool.is_available !== false)
1909+
.map((tool: any) => Number(tool.id))
1910+
.filter((id: number) => Number.isFinite(id));
1911+
const subAgentIds = (Array.isArray(detail.sub_agent_id_list)
1912+
? detail.sub_agent_id_list
1913+
: []
1914+
)
1915+
.map((id: any) => Number(id))
1916+
.filter((id: number) => Number.isFinite(id));
1917+
1918+
// Create a new agent using the source agent fields
1919+
const createResult = await updateAgent(
1920+
undefined,
1921+
copyName,
1922+
detail.description,
1923+
detail.model,
1924+
detail.max_step,
1925+
detail.provide_run_summary,
1926+
detail.enabled,
1927+
detail.business_description,
1928+
detail.duty_prompt,
1929+
detail.constraint_prompt,
1930+
detail.few_shots_prompt,
1931+
copyDisplayName,
1932+
detail.model_id ?? undefined,
1933+
detail.business_logic_model_name ?? undefined,
1934+
detail.business_logic_model_id ?? undefined,
1935+
enabledToolIds,
1936+
subAgentIds
1937+
);
1938+
if (!createResult.success || !createResult.data?.agent_id) {
1939+
message.error(
1940+
createResult.message ||
1941+
t("agentConfig.agents.copyFailed")
1942+
);
1943+
return;
1944+
}
1945+
const newAgentId = Number(createResult.data.agent_id);
1946+
const copiedAgentFallback: Agent = {
1947+
...detail,
1948+
id: String(newAgentId),
1949+
name: copyName,
1950+
display_name: copyDisplayName,
1951+
sub_agent_id_list: subAgentIds,
1952+
};
1953+
1954+
// Copy tool configuration to the new agent
1955+
for (const tool of tools) {
1956+
if (!tool || tool.is_available === false) {
1957+
continue;
1958+
}
1959+
const params =
1960+
tool.initParams?.reduce((acc: Record<string, any>, param: any) => {
1961+
acc[param.name] = param.value;
1962+
return acc;
1963+
}, {}) || {};
1964+
try {
1965+
await updateToolConfig(Number(tool.id), newAgentId, params, true);
1966+
} catch (error) {
1967+
log.error("Failed to copy tool configuration while duplicating agent:", error);
1968+
message.error(
1969+
t("agentConfig.agents.copyFailed")
1970+
);
1971+
return;
1972+
}
1973+
}
1974+
1975+
// Refresh UI state and notify user about copy result
1976+
await refreshAgentList(t, false);
1977+
message.success(t("agentConfig.agents.copySuccess"));
1978+
if (unavailableTools.length > 0) {
1979+
const names =
1980+
unavailableToolNames.join(", ") ||
1981+
unavailableTools
1982+
.map((tool: any) => Number(tool?.id))
1983+
.filter((id: number) => !Number.isNaN(id))
1984+
.join(", ");
1985+
message.warning(
1986+
t("agentConfig.agents.copyUnavailableTools", {
1987+
count: unavailableTools.length,
1988+
names,
1989+
})
1990+
);
1991+
}
1992+
// Auto select the newly copied agent for editing
1993+
await handleEditAgent(copiedAgentFallback, t);
1994+
} catch (error) {
1995+
log.error("Failed to copy agent:", error);
1996+
message.error(t("agentConfig.agents.copyFailed"));
1997+
}
1998+
};
1999+
2000+
const handleCopyAgentWithConfirm = (agent: Agent) => {
2001+
confirm({
2002+
title: t("agentConfig.agents.copyConfirmTitle"),
2003+
content: t("agentConfig.agents.copyConfirmContent", {
2004+
name: agent?.display_name || agent?.name || "",
2005+
}),
2006+
onConfirm: () => handleCopyAgentFromList(agent),
2007+
});
2008+
};
2009+
18742010
// Handle delete agent from list
18752011
const handleDeleteAgentFromList = (agent: Agent) => {
18762012
setAgentToDelete(agent);
@@ -1990,6 +2126,7 @@ export default function AgentSetupOrchestrator({
19902126
isGeneratingAgent={isGeneratingAgent}
19912127
editingAgent={editingAgent}
19922128
isCreatingNewAgent={isCreatingNewAgent}
2129+
onCopyAgent={handleCopyAgentWithConfirm}
19932130
onExportAgent={handleExportAgentFromList}
19942131
onDeleteAgent={handleDeleteAgentFromList}
19952132
unsavedAgentId={

frontend/app/[locale]/agents/components/agent/SubAgentPool.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
55

66
import { Button, Row, Col } from "antd";
77
import { ExclamationCircleOutlined } from "@ant-design/icons";
8-
import { FileOutput, Network, FileInput, Trash2, Plus, X } from "lucide-react";
8+
import { Copy, FileOutput, Network, FileInput, Trash2, Plus, X } from "lucide-react";
99

1010
import { ScrollArea } from "@/components/ui/scrollArea";
1111
import {
@@ -37,6 +37,7 @@ export default function SubAgentPool({
3737
isGeneratingAgent = false,
3838
editingAgent = null,
3939
isCreatingNewAgent = false,
40+
onCopyAgent,
4041
onExportAgent,
4142
onDeleteAgent,
4243
unsavedAgentId = null,
@@ -250,7 +251,7 @@ export default function SubAgentPool({
250251
isImporting ? "bg-gray-100" : "bg-green-100"
251252
}`}
252253
>
253-
<FileOutput
254+
<FileInput
254255
className={`w-4 h-4 ${
255256
isImporting ? "text-gray-400" : "text-green-600"
256257
}`}
@@ -357,6 +358,27 @@ export default function SubAgentPool({
357358

358359
{/* Operation button area */}
359360
<div className="flex items-center gap-1 ml-2 flex-shrink-0">
361+
{/* Copy agent button */}
362+
{onCopyAgent && (
363+
<Tooltip>
364+
<TooltipTrigger asChild>
365+
<Button
366+
type="text"
367+
size="small"
368+
icon={<Copy className="w-4 h-4" />}
369+
onClick={(e) => {
370+
e.preventDefault();
371+
e.stopPropagation();
372+
onCopyAgent(agent);
373+
}}
374+
className="agent-action-button agent-action-button-blue"
375+
/>
376+
</TooltipTrigger>
377+
<TooltipContent>
378+
{t("agent.contextMenu.copy")}
379+
</TooltipContent>
380+
</Tooltip>
381+
)}
360382
{/* View call relationship button */}
361383
<Tooltip>
362384
<TooltipTrigger asChild>
@@ -383,7 +405,7 @@ export default function SubAgentPool({
383405
<Button
384406
type="text"
385407
size="small"
386-
icon={<FileInput className="w-4 h-4" />}
408+
icon={<FileOutput className="w-4 h-4" />}
387409
onClick={(e) => {
388410
e.preventDefault();
389411
e.stopPropagation();

0 commit comments

Comments
 (0)