Skip to content

Commit 9151630

Browse files
authored
⭐️ Add MCP Server Install hint and fix tool test display
2 parents a7d5798 + bc58f43 commit 9151630

File tree

11 files changed

+821
-896
lines changed

11 files changed

+821
-896
lines changed

frontend/app/[locale]/agents/components/tool/ToolConfigModal.tsx

Lines changed: 57 additions & 524 deletions
Large diffs are not rendered by default.

frontend/app/[locale]/agents/components/tool/ToolTestPanel.tsx

Lines changed: 608 additions & 0 deletions
Large diffs are not rendered by default.

frontend/app/[locale]/market/MarketContent.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
import marketService, { MarketApiError } from "@/services/marketService";
1919
import { AgentMarketCard } from "./components/AgentMarketCard";
2020
import MarketAgentDetailModal from "./components/MarketAgentDetailModal";
21-
import AgentInstallModal from "./components/AgentInstallModal";
21+
import AgentImportWizard from "@/components/agent/AgentImportWizard";
22+
import { ImportAgentData } from "@/hooks/useAgentImport";
2223
import MarketErrorState from "./components/MarketErrorState";
2324

2425
interface MarketContentProps {
@@ -261,12 +262,7 @@ export default function MarketContent({
261262
},
262263
...categories.map((cat) => ({
263264
key: cat.name,
264-
label: (
265-
<span className="flex items-center gap-2">
266-
<span>{cat.icon}</span>
267-
<span>{isZh ? cat.display_name_zh : cat.display_name}</span>
268-
</span>
269-
),
265+
label: isZh ? cat.display_name_zh : cat.display_name,
270266
})),
271267
];
272268

@@ -451,11 +447,22 @@ export default function MarketContent({
451447
/>
452448

453449
{/* Agent Install Modal */}
454-
<AgentInstallModal
450+
<AgentImportWizard
455451
visible={installModalVisible}
456-
agentDetails={installAgent}
457452
onCancel={handleInstallCancel}
458-
onInstallComplete={handleInstallComplete}
453+
initialData={
454+
installAgent?.agent_json
455+
? ({
456+
agent_id: installAgent.agent_id,
457+
agent_info: installAgent.agent_json.agent_info,
458+
mcp_info: installAgent.agent_json.mcp_info,
459+
} as ImportAgentData)
460+
: null
461+
}
462+
onImportComplete={handleInstallComplete}
463+
title={undefined} // Use default title
464+
agentDisplayName={installAgent?.display_name}
465+
agentDescription={installAgent?.description}
459466
/>
460467
</motion.div>
461468
) : null}

frontend/app/[locale]/market/components/AgentInstallModal.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

frontend/app/[locale]/market/components/AgentMarketCard.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Download, Tag, Wrench } from "lucide-react";
66
import { MarketAgentListItem } from "@/types/market";
77
import { useTranslation } from "react-i18next";
88
import { getGenericLabel } from "@/lib/agentLabelMapper";
9+
import { getCategoryIcon } from "@/const/marketConfig";
910

1011
interface AgentMarketCardProps {
1112
agent: MarketAgentListItem;
@@ -34,6 +35,11 @@ export function AgentMarketCard({
3435
onViewDetails(agent);
3536
};
3637

38+
// Get category icon: prefer API icon, then fallback to default mapping by name
39+
const categoryIcon = agent.category
40+
? agent.category.icon || getCategoryIcon(agent.category.name)
41+
: "📦";
42+
3743
return (
3844
<motion.div
3945
whileHover={{ y: -4 }}
@@ -45,7 +51,7 @@ export function AgentMarketCard({
4551
<div className="flex items-center justify-between mb-2">
4652
<div className="flex items-center gap-2">
4753
<span className="text-2xl">
48-
{agent.category?.icon || "📦"}
54+
{categoryIcon}
4955
</span>
5056
<span className="text-xs font-medium text-purple-600 dark:text-purple-400">
5157
{agent.category

frontend/app/[locale]/market/components/MarketAgentDetailModal.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "lucide-react";
1414
import { MarketAgentDetail } from "@/types/market";
1515
import { getToolSourceLabel, getGenericLabel } from "@/lib/agentLabelMapper";
16+
import { getCategoryIcon } from "@/const/marketConfig";
1617

1718
interface MarketAgentDetailModalProps {
1819
visible: boolean;
@@ -97,7 +98,10 @@ export default function MarketAgentDetailModal({
9798
>
9899
{agentDetails?.category ? (
99100
<Tag color="purple" className="inline-flex items-center gap-1">
100-
<span>{agentDetails.category.icon || "📦"}</span>
101+
<span>
102+
{agentDetails.category.icon ||
103+
getCategoryIcon(agentDetails.category.name)}
104+
</span>
101105
<span>
102106
{isZh
103107
? agentDetails.category.display_name_zh

frontend/app/[locale]/page.tsx

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,18 +116,6 @@ export default function Home() {
116116
setLoginPromptOpen(false);
117117
};
118118

119-
// Handle login button click
120-
const handleLoginClick = () => {
121-
setLoginPromptOpen(false);
122-
openLoginModal();
123-
};
124-
125-
// Handle register button click
126-
const handleRegisterClick = () => {
127-
setLoginPromptOpen(false);
128-
openRegisterModal();
129-
};
130-
131119
// Handle operations that require admin privileges
132120
const handleAdminRequired = () => {
133121
if (!isSpeedMode && user?.role !== "admin") {

frontend/components/agent/AgentImportWizard.tsx

Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,44 @@ const extractPromptHint = (value: string): string | undefined => {
5353
return match ? match[1] : undefined;
5454
};
5555

56+
// Parse Markdown links in text and convert to React elements
57+
const parseMarkdownLinks = (text: string): React.ReactNode[] => {
58+
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
59+
const parts: React.ReactNode[] = [];
60+
let lastIndex = 0;
61+
let match;
62+
let key = 0;
63+
64+
while ((match = linkRegex.exec(text)) !== null) {
65+
// Add text before the link
66+
if (match.index > lastIndex) {
67+
parts.push(text.substring(lastIndex, match.index));
68+
}
69+
// Add the link
70+
parts.push(
71+
<a
72+
key={key++}
73+
href={match[2]}
74+
target="_blank"
75+
rel="noopener noreferrer"
76+
className="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 underline"
77+
onClick={(e) => {
78+
e.stopPropagation();
79+
}}
80+
>
81+
{match[1]}
82+
</a>
83+
);
84+
lastIndex = match.index + match[0].length;
85+
}
86+
// Add remaining text
87+
if (lastIndex < text.length) {
88+
parts.push(text.substring(lastIndex));
89+
}
90+
91+
return parts.length > 0 ? parts : [text];
92+
};
93+
5694
export default function AgentImportWizard({
5795
visible,
5896
onCancel,
@@ -409,7 +447,6 @@ export default function AgentImportWizard({
409447

410448
// Clone agent data structure
411449
const agentJson = JSON.parse(JSON.stringify(initialData));
412-
const mainAgentId = String(initialData.agent_id);
413450

414451
// Update model information based on selection mode
415452
if (modelSelectionMode === "unified") {
@@ -819,47 +856,22 @@ export default function AgentImportWizard({
819856
{mcpServers.map((mcp, index) => (
820857
<div
821858
key={`${mcp.mcp_server_name}-${index}`}
822-
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 min-h-[120px] flex items-center"
859+
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4"
823860
>
824-
<div className="flex items-center justify-between w-full gap-4">
825-
<div className="flex-1 flex flex-col justify-center">
826-
<div className="flex items-center gap-2 mb-3">
827-
<span className="font-medium text-base">
828-
{mcp.mcp_server_name}
829-
</span>
830-
{mcp.isInstalled ? (
831-
<Tag icon={<CheckCircleOutlined />} color="success" className="text-sm">
832-
{t("market.install.mcp.installed", "Installed")}
833-
</Tag>
834-
) : (
835-
<Tag icon={<CloseCircleOutlined />} color="default" className="text-sm">
836-
{t("market.install.mcp.notInstalled", "Not Installed")}
837-
</Tag>
838-
)}
839-
</div>
840-
841-
<div className="flex items-center gap-2">
842-
<span className="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">
843-
MCP URL:
844-
</span>
845-
{(mcp.isUrlEditable || !mcp.isInstalled) ? (
846-
<Input
847-
value={mcp.editedUrl || ""}
848-
onChange={(e) => handleMcpUrlChange(index, e.target.value)}
849-
placeholder={mcp.isUrlEditable
850-
? t("market.install.mcp.urlPlaceholder", "Enter MCP server URL")
851-
: mcp.mcp_url
852-
}
853-
size="middle"
854-
disabled={mcp.isInstalled}
855-
style={{ maxWidth: "400px" }}
856-
/>
857-
) : (
858-
<span className="text-sm text-gray-700 dark:text-gray-300 break-all">
859-
{mcp.editedUrl || mcp.mcp_url}
860-
</span>
861-
)}
862-
</div>
861+
<div className="flex items-center justify-between w-full gap-4 mb-3">
862+
<div className="flex items-center gap-2">
863+
<span className="font-medium text-base">
864+
{mcp.mcp_server_name}
865+
</span>
866+
{mcp.isInstalled ? (
867+
<Tag icon={<CheckCircleOutlined />} color="success" className="text-xs">
868+
{t("market.install.mcp.installed", "Installed")}
869+
</Tag>
870+
) : (
871+
<Tag icon={<CloseCircleOutlined />} color="default" className="text-xs">
872+
{t("market.install.mcp.notInstalled", "Not Installed")}
873+
</Tag>
874+
)}
863875
</div>
864876

865877
{!mcp.isInstalled && (
@@ -876,6 +888,44 @@ export default function AgentImportWizard({
876888
</Button>
877889
)}
878890
</div>
891+
892+
<div className="flex flex-col gap-2">
893+
<div className="flex items-center gap-2">
894+
<span className="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap">
895+
MCP URL:
896+
</span>
897+
{(mcp.isUrlEditable || !mcp.isInstalled) ? (
898+
<Input
899+
value={mcp.editedUrl || ""}
900+
onChange={(e) => handleMcpUrlChange(index, e.target.value)}
901+
placeholder={mcp.isUrlEditable
902+
? t("market.install.mcp.urlPlaceholder", "Enter MCP server URL")
903+
: mcp.mcp_url
904+
}
905+
size="middle"
906+
disabled={mcp.isInstalled}
907+
style={{ maxWidth: "400px" }}
908+
className={mcp.isUrlEditable && needsConfig(mcp.mcp_url) ? "bg-gray-100 dark:bg-gray-800" : ""}
909+
/>
910+
) : (
911+
<span className="text-sm text-gray-700 dark:text-gray-300 break-all">
912+
{mcp.editedUrl || mcp.mcp_url}
913+
</span>
914+
)}
915+
</div>
916+
{/* Show hint if URL needs configuration */}
917+
{mcp.isUrlEditable && needsConfig(mcp.mcp_url) && (() => {
918+
const hint = extractPromptHint(mcp.mcp_url);
919+
const hintText = hint || t("market.install.mcp.defaultConfigHint", "Please enter the MCP server URL");
920+
return (
921+
<div className="ml-0 text-xs text-gray-500 dark:text-gray-400 max-w-md">
922+
<span className="text-gray-600 dark:text-gray-400 inline-flex flex-wrap items-center gap-1">
923+
{parseMarkdownLinks(hintText)}
924+
</span>
925+
</div>
926+
);
927+
})()}
928+
</div>
879929
</div>
880930
))}
881931
</div>

frontend/const/marketConfig.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// ========== Market Configuration Constants ==========
2+
3+
/**
4+
* Default icons for market agent categories
5+
* Maps category name field to their corresponding icons
6+
*/
7+
export const MARKET_CATEGORY_ICONS: Record<string, string> = {
8+
research: "🔬",
9+
content: "✍️",
10+
development: "💻",
11+
business: "📈",
12+
automation: "⚙️",
13+
education: "📚",
14+
communication: "💬",
15+
data: "📊",
16+
creative: "🎨",
17+
other: "📦",
18+
} as const;
19+
20+
/**
21+
* Get icon for a category by name field
22+
* @param categoryName - Category name field (e.g., "research", "content")
23+
* @param fallbackIcon - Fallback icon if category not found (default: 📦)
24+
* @returns Icon emoji string
25+
*/
26+
export function getCategoryIcon(
27+
categoryName: string | null | undefined,
28+
fallbackIcon: string = "📦"
29+
): string {
30+
if (!categoryName) {
31+
return fallbackIcon;
32+
}
33+
34+
return MARKET_CATEGORY_ICONS[categoryName] || fallbackIcon;
35+
}
36+

0 commit comments

Comments
 (0)