Skip to content
Open
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
8 changes: 8 additions & 0 deletions packages/web/public/locales/translation/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ agent_builder:
description_placeholder: Describe what your agent does...
description_too_long: Description must be 500 characters or less
edit_agent: Edit Agent
editor: Editor
enable_code_execution: Enabling code execution
enter_agent_name: Enter the agent name
enter_system_prompt: Enter a system prompt that defines the agent behavior
Expand All @@ -59,6 +60,9 @@ agent_builder:
failed_to_update_agent: Failed to update agent
favorites: Favorites
filter_by_tag: Filter by tag
generate_short: Generate
generate_with_ai: Generate with AI
generating_prompt: Generating system prompt...
loading_agent: Loading agent...
mcp_server_configuration: MCP Server Configuration
mcp_server_description: >-
Expand All @@ -78,6 +82,10 @@ agent_builder:
no_mcp_servers_match_filter: No MCP servers match your current filter.
no_mcp_servers_selected: No MCP servers selected. Your agent will have basic functionality only.
no_public_agents_available: No public agents available
overwrite_confirm: Overwrite
overwrite_prompt_message: A system prompt already exists. Do you want to overwrite it with AI-generated prompt?
overwrite_prompt_title: Overwrite System Prompt Confirmation
preview: Preview
public: Public Agents
public_sharing_description: >-
Make this agent available on public agent directories and be discovered and
Expand Down
8 changes: 8 additions & 0 deletions packages/web/public/locales/translation/ja.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ agent_builder:
description_placeholder: エージェントの機能を説明してください...
description_too_long: 説明は500文字以下である必要があります
edit_agent: エージェントを編集
editor: エディター
enable_code_execution: コード実行を有効にする
enter_agent_name: エージェント名を入力
enter_system_prompt: エージェントの動作を定義するシステムプロンプトを入力
Expand All @@ -59,6 +60,9 @@ agent_builder:
failed_to_update_agent: エージェントの更新に失敗しました
favorites: お気に入り
filter_by_tag: タグでフィルター
generate_short: 生成
generate_with_ai: AIにより生成する
generating_prompt: システムプロンプトを生成中...
loading_agent: エージェントを読み込み中...
mcp_server_configuration: MCPサーバー設定
mcp_server_description: エージェントに追加機能を提供するMCPサーバーを選択してください。これらのサーバーはセキュリティのため管理者によって事前設定されています。
Expand All @@ -76,6 +80,10 @@ agent_builder:
no_mcp_servers_match_filter: 現在のフィルターに一致するMCPサーバーがありません。
no_mcp_servers_selected: MCPサーバーが選択されていません。エージェントは基本機能のみ利用できます。
no_public_agents_available: 利用可能な公開エージェントがありません
overwrite_confirm: 上書きする
overwrite_prompt_message: すでにシステムプロンプトが入力されています。AIで生成したプロンプトで上書きしますか?
overwrite_prompt_title: システムプロンプトの上書き確認
preview: プレビュー
public: 公開エージェント
public_sharing_description: >-
このエージェントを公開し、他のユーザーが発見して利用できるようにします。エージェントはすべてのユーザーに表示されますが、オリジナルを変更することはできません。
Expand Down
131 changes: 120 additions & 11 deletions packages/web/src/components/agentBuilder/AgentForm.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React, { useState, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import InputText from '../InputText';
import Textarea from '../Textarea';
import Select from '../Select';
import MCPServerManager from './MCPServerManager';
import ModalDialog from '../ModalDialog';
import { MODELS } from '../../hooks/useModel';
import { AgentConfiguration } from 'generative-ai-use-cases';
import usePromptGeneration from '../../hooks/usePromptGeneration';
import useMCPServers from '../../hooks/useMCPServers';
import { PiSparkle, PiStop } from 'react-icons/pi';

export interface AgentFormData {
name: string;
description: string;
systemPrompt: string;
modelId: string;
mcpServers: string[]; // Changed to string array
mcpServers: string[];
codeExecutionEnabled: boolean;
isPublic: boolean;
tags: string[];
Expand Down Expand Up @@ -55,6 +60,38 @@ const AgentForm: React.FC<AgentFormProps> = ({
});

const [tagsInput, setTagsInput] = useState('');
const [showOverwriteDialog, setShowOverwriteDialog] = useState(false);

// Load available MCP servers using the shared hook
const availableMCPServers = useMCPServers();

// Use the prompt generation hook
const {
generatedPrompt,
suggestedMCPServers,
isGenerating,
generate: generatePrompt,
cancel: cancelGeneration,
} = usePromptGeneration({
modelId: formData.modelId,
agentName: formData.name,
agentDescription: formData.description,
availableMCPServers,
});

// Update systemPrompt when generation produces new content
useEffect(() => {
if (generatedPrompt) {
setFormData((prev) => ({ ...prev, systemPrompt: generatedPrompt }));
}
}, [generatedPrompt]);

// Update MCP servers when AI suggests them
useEffect(() => {
if (suggestedMCPServers.length > 0) {
setFormData((prev) => ({ ...prev, mcpServers: suggestedMCPServers }));
}
}, [suggestedMCPServers]);

// Update formData.modelId when availableModels becomes available
useEffect(() => {
Expand Down Expand Up @@ -86,7 +123,6 @@ const AgentForm: React.FC<AgentFormProps> = ({
// Notify parent component when form data changes
useEffect(() => {
if (onFormDataChange) {
// Parse tags from input for real-time updates
const tags = tagsInput
.split(',')
.map((tag) => tag.trim())
Expand All @@ -103,7 +139,6 @@ const AgentForm: React.FC<AgentFormProps> = ({

const handleSave = useCallback(async () => {
try {
// Parse tags from input
const tags = tagsInput
.split(',')
.map((tag) => tag.trim())
Expand All @@ -115,11 +150,34 @@ const AgentForm: React.FC<AgentFormProps> = ({
};

await onSave(agentData);
} catch (error) {
console.error('Error saving agent:', error);
} catch (err) {
console.error('Error saving agent:', err);
}
}, [formData, tagsInput, onSave]);

const handleGenerateClick = useCallback(() => {
if (formData.systemPrompt.trim()) {
setShowOverwriteDialog(true);
} else {
setFormData((prev) => ({ ...prev, systemPrompt: '' }));
generatePrompt();
}
}, [formData.systemPrompt, generatePrompt]);

const handleConfirmOverwrite = useCallback(() => {
setShowOverwriteDialog(false);
setFormData((prev) => ({ ...prev, systemPrompt: '' }));
generatePrompt();
}, [generatePrompt]);

const handleCancelGeneration = useCallback(() => {
cancelGeneration();
}, [cancelGeneration]);

// Check if generate button should be disabled
const isGenerateDisabled =
!formData.name.trim() || !formData.description.trim() || loading;

const isFormValid =
formData.name && formData.systemPrompt && formData.modelId;

Expand Down Expand Up @@ -202,10 +260,33 @@ const AgentForm: React.FC<AgentFormProps> = ({

{/* System Prompt */}
<div className="rounded-lg border bg-white p-6">
{/* eslint-disable-next-line @shopify/jsx-no-hardcoded-content */}
<h2 className="mb-4 text-lg font-semibold">
{t('agent_builder.system_prompt')} {'*'}
</h2>
<div className="mb-4 flex items-center justify-between">
{/* eslint-disable-next-line @shopify/jsx-no-hardcoded-content */}
<h2 className="text-lg font-semibold">
{t('agent_builder.system_prompt')} {'*'}
</h2>
<div className="flex items-center gap-2">
{isGenerating ? (
<ButtonIcon onClick={handleCancelGeneration}>
<PiStop className="text-red-500" />
</ButtonIcon>
) : (
<Button
onClick={handleGenerateClick}
outlined
disabled={isGenerateDisabled || isGenerating}
className="flex items-center gap-1 text-sm">
<PiSparkle />
<span className="hidden sm:inline">
{t('agent_builder.generate_with_ai')}
</span>
<span className="sm:hidden">
{t('agent_builder.generate_short')}
</span>
</Button>
)}
</div>
</div>

<Textarea
value={formData.systemPrompt}
Expand All @@ -214,9 +295,35 @@ const AgentForm: React.FC<AgentFormProps> = ({
}
placeholder={t('agent_builder.enter_system_prompt')}
rows={12}
disabled={isGenerating || loading}
/>
{isGenerating && (
<p className="mt-2 text-sm text-gray-500">
{t('agent_builder.generating_prompt')}
</p>
)}
</div>

{/* Overwrite Confirmation Dialog */}
<ModalDialog
isOpen={showOverwriteDialog}
title={t('agent_builder.overwrite_prompt_title')}
onClose={() => setShowOverwriteDialog(false)}>
<div className="space-y-4">
<p className="text-gray-700">
{t('agent_builder.overwrite_prompt_message')}
</p>
<div className="flex justify-end gap-3">
<Button outlined onClick={() => setShowOverwriteDialog(false)}>
{t('common.cancel')}
</Button>
<Button onClick={handleConfirmOverwrite}>
{t('agent_builder.overwrite_confirm')}
</Button>
</div>
</div>
</ModalDialog>

{/* MCP Server Configuration */}
<div className="rounded-lg border bg-white p-6">
<MCPServerManager
Expand Down Expand Up @@ -288,10 +395,12 @@ const AgentForm: React.FC<AgentFormProps> = ({

{/* Form Actions */}
<div className="flex justify-end gap-3">
<Button outlined onClick={onCancel}>
<Button outlined onClick={onCancel} disabled={isGenerating}>
{t('common.cancel')}
</Button>
<Button onClick={handleSave} disabled={loading || !isFormValid}>
<Button
onClick={handleSave}
disabled={loading || !isFormValid || isGenerating}>
{isEditMode ? t('common.update') : t('common.create')}
</Button>
</div>
Expand Down
29 changes: 3 additions & 26 deletions packages/web/src/components/agentBuilder/MCPServerManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,13 @@ import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiInfo, PiMagnifyingGlass, PiX } from 'react-icons/pi';
import { AvailableMCPServer } from 'generative-ai-use-cases';
import useMCPServers from '../../hooks/useMCPServers';

interface MCPServerManagerProps {
servers: string[];
onChange: (servers: string[]) => void;
}

// Load MCP servers from environment variable
const loadMCPServersFromEnv = (): AvailableMCPServer[] => {
try {
const mcpConfig = import.meta.env.VITE_APP_MCP_SERVERS_CONFIG;
if (!mcpConfig) {
console.warn('VITE_APP_MCP_SERVERS_CONFIG not found, using fallback');
return [];
}

const parsedConfig = JSON.parse(mcpConfig);
return Object.keys(parsedConfig).map((serverName) => {
const metadata = parsedConfig[serverName]?.metadata || {};
return {
name: serverName,
description: metadata.description || `MCP server: ${serverName}`,
category: metadata.category || 'Other',
};
});
} catch (error) {
console.error('Error parsing MCP servers config:', error);
return [];
}
};

const MCPServerManager: React.FC<MCPServerManagerProps> = ({
servers,
onChange,
Expand All @@ -43,8 +20,8 @@ const MCPServerManager: React.FC<MCPServerManagerProps> = ({
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string>('all');

// Load available MCP servers from environment
const AVAILABLE_MCP_SERVERS = useMemo(() => loadMCPServersFromEnv(), []);
// Load available MCP servers from environment using the shared hook
const AVAILABLE_MCP_SERVERS = useMCPServers();

// Initialize selected servers from props
useEffect(() => {
Expand Down
48 changes: 48 additions & 0 deletions packages/web/src/hooks/useMCPServers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* useMCPServers Hook
*
* Provides MCP server configuration loaded from environment variables.
* Consolidates the logic previously duplicated in AgentForm and MCPServerManager.
*/

import { useMemo } from 'react';
import { AvailableMCPServer } from 'generative-ai-use-cases';

/**
* Loads MCP servers configuration from the VITE_APP_MCP_SERVERS_CONFIG environment variable.
*
* @returns Array of available MCP servers with name, description, and category
*/
export const loadMCPServersFromEnv = (): AvailableMCPServer[] => {
try {
const mcpConfig = import.meta.env.VITE_APP_MCP_SERVERS_CONFIG;
if (!mcpConfig) {
console.warn('VITE_APP_MCP_SERVERS_CONFIG not found');
return [];
}

const parsedConfig = JSON.parse(mcpConfig);
return Object.keys(parsedConfig).map((serverName) => {
const metadata = parsedConfig[serverName]?.metadata || {};
return {
name: serverName,
description: metadata.description || `MCP server: ${serverName}`,
category: metadata.category || 'Other',
};
});
} catch (error) {
console.error('Error parsing MCP servers config:', error);
return [];
}
};

/**
* Custom hook that provides memoized MCP server configurations.
*
* @returns Memoized array of available MCP servers
*/
export const useMCPServers = (): AvailableMCPServer[] => {
return useMemo(() => loadMCPServersFromEnv(), []);
};

export default useMCPServers;
Loading