feat(system):优化后端
1.新增后端测试 2.增加了后端的加密 3.增加了i18n(国际化)
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box, Plus, X, Server } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Plus, X, Server, Loader2, Boxes } from 'lucide-react';
|
||||
import type { Provider } from '../../types';
|
||||
import apiClient from '../../api/client';
|
||||
|
||||
export function ProvidersSettings() {
|
||||
const { t } = useTranslation();
|
||||
const [providers, setProviders] = useState<Provider[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
@@ -29,7 +31,7 @@ export function ProvidersSettings() {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!formData.provider_title || !formData.provider_url || !formData.provider_apikey) {
|
||||
setError('Please fill in all fields.');
|
||||
setError(t('agent.providerFillAll'));
|
||||
return;
|
||||
}
|
||||
setSubmitLoading(true);
|
||||
@@ -39,7 +41,7 @@ export function ProvidersSettings() {
|
||||
await fetchProviders();
|
||||
setIsModalOpen(false);
|
||||
} catch (err) {
|
||||
setError('Failed to add provider. Please check your inputs and try again.');
|
||||
setError(t('agent.providerAddFailed'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
@@ -49,20 +51,24 @@ export function ProvidersSettings() {
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-text-primary">Provider Management</h3>
|
||||
<p className="text-sm text-text-muted mt-0.5">Configure external AI model providers</p>
|
||||
<h3 className="text-lg font-bold text-text-primary">{t('agent.providerManagement')}</h3>
|
||||
<p className="text-sm text-text-muted mt-0.5">{t('agent.providerDesc')}</p>
|
||||
</div>
|
||||
<button onClick={() => { setFormData({ provider_type: 'openai', provider_title: '', provider_url: '', provider_apikey: '' }); setError(''); setIsModalOpen(true); }}
|
||||
className="flex items-center gap-2 px-4 py-2.5 bg-accent text-white rounded-xl hover:bg-accent-hover transition-all shadow-lg shadow-accent/15 text-sm font-medium">
|
||||
<Plus size={14} /> Add Provider
|
||||
<Plus size={14} /> {t('agent.addProvider')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="text-center text-text-muted py-12 text-sm">Loading providers...</div>
|
||||
<div className="flex flex-col items-center justify-center py-12 text-text-muted">
|
||||
<Loader2 size={24} className="animate-spin mb-3" />
|
||||
<span className="text-sm">{t('common.loading')}</span>
|
||||
</div>
|
||||
) : providers.length === 0 ? (
|
||||
<div className="text-center text-text-muted py-12 bg-bg-card rounded-2xl border border-border-primary border-dashed text-sm">
|
||||
No providers configured yet
|
||||
<div className="flex flex-col items-center justify-center py-12 bg-bg-card rounded-2xl border border-border-primary border-dashed text-text-muted">
|
||||
<Boxes size={32} className="mb-3 opacity-40" />
|
||||
<span className="text-sm">{t('agent.noProviders')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -80,19 +86,19 @@ export function ProvidersSettings() {
|
||||
</div>
|
||||
<span className={`flex items-center gap-1 text-[10px] font-medium px-2 py-1 rounded-lg border ${provider.status === 'Connected' ? 'bg-success-bg text-success border-success/20' : 'bg-bg-secondary text-text-muted border-border-primary'}`}>
|
||||
{provider.status === 'Connected' && <span className="w-1 h-1 rounded-full bg-success" />}
|
||||
{provider.status || 'Unknown'}
|
||||
{provider.status || t('common.unknown')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-bg-secondary rounded-lg px-3 py-2 mb-4">
|
||||
<p className="text-[10px] text-text-muted mb-0.5">Endpoint</p>
|
||||
<p className="text-xs font-mono text-text-secondary truncate">{provider.provider_url || 'Default'}</p>
|
||||
<p className="text-[10px] text-text-muted mb-0.5">{t('agent.endpoint')}</p>
|
||||
<p className="text-xs font-mono text-text-secondary truncate">{provider.provider_url || t('common.default')}</p>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button className="px-3 py-1.5 text-xs font-medium text-text-secondary bg-bg-secondary hover:bg-bg-hover rounded-lg transition-colors border border-border-primary">Edit</button>
|
||||
<button className="px-3 py-1.5 text-xs font-medium text-text-secondary bg-bg-secondary hover:bg-bg-hover rounded-lg transition-colors border border-border-primary">{t('common.edit')}</button>
|
||||
<button onClick={async () => {
|
||||
if (!confirm('Delete this provider?')) return;
|
||||
try { await apiClient.delete(`/api/v1/provider/${provider.provider_title}`); fetchProviders(); } catch { alert('Failed'); }
|
||||
}} className="px-3 py-1.5 text-xs font-medium text-danger bg-danger-bg hover:bg-danger-bg/80 rounded-lg transition-colors border border-danger/20">Delete</button>
|
||||
if (!confirm(t('agent.deleteProviderConfirm'))) return;
|
||||
try { await apiClient.delete(`/api/v1/provider/${provider.provider_title}`); fetchProviders(); } catch { alert(t('common.deleteFailed')); }
|
||||
}} className="px-3 py-1.5 text-xs font-medium text-danger bg-danger-bg hover:bg-danger-bg/80 rounded-lg transition-colors border border-danger/20">{t('common.delete')}</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -106,14 +112,14 @@ export function ProvidersSettings() {
|
||||
<div className="flex justify-between items-center p-5 border-b border-border-primary">
|
||||
<div className="flex items-center gap-2">
|
||||
<Server size={16} className="text-accent" />
|
||||
<h3 className="text-base font-bold text-text-primary">Add New Provider</h3>
|
||||
<h3 className="text-base font-bold text-text-primary">{t('agent.addNewProvider')}</h3>
|
||||
</div>
|
||||
<button onClick={() => setIsModalOpen(false)} className="p-1 text-text-muted hover:text-text-primary rounded-lg transition-colors"><X size={18} /></button>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="p-5 space-y-4">
|
||||
{error && <div className="p-3 bg-danger-bg text-danger text-sm rounded-xl border border-danger/20">{error}</div>}
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Type</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.providerType')}</label>
|
||||
<select name="provider_type" value={formData.provider_type} onChange={(e) => setFormData({...formData, provider_type: e.target.value})}
|
||||
className="w-full bg-bg-input border border-border-primary text-sm rounded-xl px-3.5 py-2.5 focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent text-text-primary">
|
||||
<option value="openai">OpenAI</option>
|
||||
@@ -125,18 +131,18 @@ export function ProvidersSettings() {
|
||||
{['provider_title', 'provider_url', 'provider_apikey'].map((field) => (
|
||||
<div key={field}>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">
|
||||
{field === 'provider_title' ? 'Title' : field === 'provider_url' ? 'Base URL' : 'API Key'}
|
||||
{field === 'provider_title' ? t('agent.providerTitle') : field === 'provider_url' ? t('agent.baseUrl') : t('agent.apiKey')}
|
||||
</label>
|
||||
<input type={field === 'provider_apikey' ? 'password' : field === 'provider_url' ? 'url' : 'text'}
|
||||
name={field} value={(formData as any)[field]}
|
||||
onChange={(e) => setFormData({...formData, [field]: e.target.value})}
|
||||
placeholder={field === 'provider_title' ? 'My OpenAI' : field === 'provider_url' ? 'https://api.openai.com/v1' : 'sk-...'}
|
||||
placeholder={field === 'provider_title' ? t('agent.providerTitlePlaceholder') : field === 'provider_url' ? t('agent.baseUrlPlaceholder') : t('agent.apiKeyPlaceholder')}
|
||||
className="w-full bg-bg-input border border-border-primary text-sm rounded-xl px-3.5 py-2.5 focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent text-text-primary placeholder:text-text-muted/50 font-mono" />
|
||||
</div>
|
||||
))}
|
||||
<div className="pt-2 flex justify-end gap-2">
|
||||
<button type="button" onClick={() => setIsModalOpen(false)} className="px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-hover rounded-xl transition-colors">Cancel</button>
|
||||
<button type="submit" disabled={submitLoading} className="px-4 py-2 text-sm font-medium text-white bg-accent rounded-xl hover:bg-accent-hover transition-colors disabled:opacity-50">{submitLoading ? 'Saving...' : 'Add Provider'}</button>
|
||||
<button type="button" onClick={() => setIsModalOpen(false)} className="px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-hover rounded-xl transition-colors">{t('common.cancel')}</button>
|
||||
<button type="submit" disabled={submitLoading} className="px-4 py-2 text-sm font-medium text-white bg-accent rounded-xl hover:bg-accent-hover transition-colors disabled:opacity-50">{submitLoading ? t('common.saving') : t('agent.addProvider')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import apiClient from '../../api/client';
|
||||
import { Save, Plus, Edit2, Trash2, X, Bot } from 'lucide-react';
|
||||
import { Save, Plus, Edit2, Trash2, X, Bot, Loader2, Users } from 'lucide-react';
|
||||
import type { Provider } from '../../types';
|
||||
|
||||
interface WorkerIndividual {
|
||||
@@ -18,6 +19,7 @@ interface WorkerIndividual {
|
||||
}
|
||||
|
||||
export function WorkerIndividualSettings() {
|
||||
const { t } = useTranslation();
|
||||
const [providers, setProviders] = useState<Provider[]>([]);
|
||||
const [workers, setWorkers] = useState<WorkerIndividual[]>([]);
|
||||
const [systemNodes, setSystemNodes] = useState<any[]>([]);
|
||||
@@ -62,7 +64,7 @@ export function WorkerIndividualSettings() {
|
||||
};
|
||||
}));
|
||||
} catch (err: any) {
|
||||
setError('Failed to load data');
|
||||
setError(t('agent.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -93,8 +95,8 @@ export function WorkerIndividualSettings() {
|
||||
};
|
||||
|
||||
const handleDelete = async (agent_id: string) => {
|
||||
if (!confirm('Delete this agent?')) return;
|
||||
try { await apiClient.delete(`/api/v1/agent/worker/${agent_id}`); fetchData(); } catch { alert('Failed'); }
|
||||
if (!confirm(t('agent.deleteWorkerConfirm'))) return;
|
||||
try { await apiClient.delete(`/api/v1/agent/worker/${agent_id}`); fetchData(); } catch { alert(t('common.deleteFailed')); }
|
||||
};
|
||||
|
||||
const handleModalSave = async (e: React.FormEvent) => {
|
||||
@@ -123,31 +125,31 @@ export function WorkerIndividualSettings() {
|
||||
setIsEditing(false);
|
||||
fetchData();
|
||||
} catch (err: any) {
|
||||
setModalMessage(err.response?.data?.detail || err.message || 'Failed to save');
|
||||
setModalMessage(err.response?.data?.detail || err.message || t('common.saveFailed'));
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeBadge = (type: string, isSystem?: boolean) => {
|
||||
if (isSystem) return <span className="px-2 py-0.5 rounded-md text-[10px] font-bold bg-accent-light text-accent uppercase tracking-wider">System</span>;
|
||||
if (isSystem) return <span className="px-2 py-0.5 rounded-md text-[10px] font-bold bg-accent-light text-accent uppercase tracking-wider">{t('agent.system')}</span>;
|
||||
const colors: Record<string, string> = {
|
||||
ordinary_individual: 'bg-bg-secondary text-text-muted',
|
||||
skill_individual: 'bg-success-bg text-success',
|
||||
special_individual: 'bg-warning-bg text-warning',
|
||||
};
|
||||
return <span className={`px-2 py-0.5 rounded-md text-[10px] font-medium ${colors[type] || colors.ordinary_individual}`}>{type.replace('_', ' ')}</span>;
|
||||
return <span className={`px-2 py-0.5 rounded-md text-[10px] font-medium ${colors[type] || colors.ordinary_individual}`}>{t(`agent.type.${type}`, type.replace('_', ' '))}</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-5xl space-y-6">
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-text-primary">Individual</h1>
|
||||
<p className="text-sm text-text-muted mt-0.5">Manage system nodes and custom workers</p>
|
||||
<h1 className="text-lg font-bold text-text-primary">{t('agent.individual')}</h1>
|
||||
<p className="text-sm text-text-muted mt-0.5">{t('agent.individualDesc')}</p>
|
||||
</div>
|
||||
<button onClick={handleAddNew} className="flex items-center gap-2 px-4 py-2.5 bg-accent text-white rounded-xl hover:bg-accent-hover transition-all shadow-lg shadow-accent/15 text-sm font-medium">
|
||||
<Plus size={14} /> Add Worker
|
||||
<Plus size={14} /> {t('agent.addWorker')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -155,17 +157,23 @@ export function WorkerIndividualSettings() {
|
||||
|
||||
<div className="bg-bg-card rounded-2xl border border-border-primary shadow-sm overflow-hidden">
|
||||
{loading ? (
|
||||
<div className="p-6 text-text-muted text-sm">Loading...</div>
|
||||
<div className="flex flex-col items-center justify-center py-12 text-text-muted">
|
||||
<Loader2 size={24} className="animate-spin mb-3" />
|
||||
<span className="text-sm">{t('common.loading')}</span>
|
||||
</div>
|
||||
) : (workers.length === 0 && systemNodes.length === 0) ? (
|
||||
<div className="p-6 text-text-muted text-sm">No individuals found.</div>
|
||||
<div className="flex flex-col items-center justify-center py-12 text-text-muted">
|
||||
<Users size={32} className="mb-3 opacity-40" />
|
||||
<span className="text-sm">{t('agent.noIndividuals')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="bg-bg-secondary border-b border-border-primary text-text-muted text-xs uppercase tracking-wider">
|
||||
<th className="px-5 py-3 font-semibold">Name</th>
|
||||
<th className="px-5 py-3 font-semibold">Type</th>
|
||||
<th className="px-5 py-3 font-semibold">Provider / Model</th>
|
||||
<th className="px-5 py-3 font-semibold text-right">Actions</th>
|
||||
<th className="px-5 py-3 font-semibold">{t('agent.name')}</th>
|
||||
<th className="px-5 py-3 font-semibold">{t('agent.type')}</th>
|
||||
<th className="px-5 py-3 font-semibold">{t('agent.providerModel')}</th>
|
||||
<th className="px-5 py-3 font-semibold text-right">{t('common.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border-secondary">
|
||||
@@ -212,47 +220,47 @@ export function WorkerIndividualSettings() {
|
||||
{/* Modal */}
|
||||
{isEditing && (
|
||||
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
|
||||
<div className="bg-bg-card rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto border border-border-primary">
|
||||
<div className="bg-bg-card rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto border border-border-primary animate-fade-in-scale">
|
||||
<div className="flex justify-between items-center p-5 border-b border-border-primary sticky top-0 bg-bg-card z-10">
|
||||
<h2 className="text-base font-bold text-text-primary">{(editData as any).is_system ? 'Edit System Node' : (isNew ? 'Create Worker' : 'Edit Worker')}</h2>
|
||||
<h2 className="text-base font-bold text-text-primary">{(editData as any).is_system ? t('agent.editSystemNode') : (isNew ? t('agent.createWorker') : t('agent.editWorker'))}</h2>
|
||||
<button onClick={() => setIsEditing(false)} className="p-1 text-text-muted hover:text-text-primary rounded-lg transition-colors"><X size={20} /></button>
|
||||
</div>
|
||||
<form onSubmit={handleModalSave} className="p-5 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Name</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.name')}</label>
|
||||
<input type="text" required value={editData.agent_name || ''} onChange={(e) => setEditData({...editData, agent_name: e.target.value})}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" disabled={(editData as any).is_system} />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Type</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.type')}</label>
|
||||
<select value={editData.agent_type || 'ordinary_individual'} onChange={(e) => setEditData({...editData, agent_type: e.target.value})}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" disabled={(editData as any).is_system}>
|
||||
<option value="ordinary_individual">Ordinary</option>
|
||||
<option value="skill_individual">Skill</option>
|
||||
<option value="special_individual">Special</option>
|
||||
{(editData as any).is_system && <option value="System Node">System Node</option>}
|
||||
<option value="ordinary_individual">{t('agent.type.ordinary_individual')}</option>
|
||||
<option value="skill_individual">{t('agent.type.skill_individual')}</option>
|
||||
<option value="special_individual">{t('agent.type.special_individual')}</option>
|
||||
{(editData as any).is_system && <option value="System Node">{t('agent.system')}</option>}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Provider</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.provider')}</label>
|
||||
<select value={editData.provider_title || ''} onChange={(e) => setEditData({...editData, provider_title: e.target.value, model_id: ''})} required
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent">
|
||||
<option value="" disabled>Select</option>
|
||||
<option value="" disabled>{t('common.select')}</option>
|
||||
{providers.map((p) => (<option key={p.provider_title} value={p.provider_title}>{p.provider_title}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Model</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.model')}</label>
|
||||
{(() => {
|
||||
const sp = providers.find(p => p.provider_title === editData.provider_title);
|
||||
const models = sp?.provider_models || [];
|
||||
return (
|
||||
<select value={editData.model_id || ''} onChange={(e) => setEditData({...editData, model_id: e.target.value})} required
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent">
|
||||
<option value="" disabled>Select</option>
|
||||
<option value="" disabled>{t('common.select')}</option>
|
||||
{models.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
</select>
|
||||
);
|
||||
@@ -262,40 +270,40 @@ export function WorkerIndividualSettings() {
|
||||
{!(editData as any).is_system && (
|
||||
<>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Description</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.description')}</label>
|
||||
<textarea value={editData.description || ''} onChange={(e) => setEditData({...editData, description: e.target.value})} rows={2}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">System Prompt</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.systemPrompt')}</label>
|
||||
<textarea value={editData.system_prompt || ''} onChange={(e) => setEditData({...editData, system_prompt: e.target.value})} rows={3}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary font-mono focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Output Template (JSON)</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.outputTemplate')}</label>
|
||||
<textarea value={editData.output_template || '{}'} onChange={(e) => setEditData({...editData, output_template: e.target.value})} rows={3}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary font-mono focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Bound Skill</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.boundSkill')}</label>
|
||||
<select value={(() => { try { return Object.keys(JSON.parse(editData.bound_skill || '{}'))[0] || ''; } catch { return ''; } })()}
|
||||
onChange={(e) => setEditData({...editData, bound_skill: JSON.stringify(e.target.value ? { [e.target.value]: [] } : {})})}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" disabled={editData.agent_type !== 'skill_individual'}>
|
||||
<option value="">None</option>
|
||||
<option value="">{t('common.none')}</option>
|
||||
{availableSkills.map(s => <option key={s} value={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Workspace (JSON)</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.workspace')}</label>
|
||||
<textarea value={editData.workspace || '[]'} onChange={(e) => setEditData({...editData, workspace: e.target.value})} rows={2}
|
||||
className="w-full px-3 py-2 bg-bg-input border border-border-primary rounded-xl text-sm text-text-primary font-mono focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">Tools</label>
|
||||
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('agent.tools')}</label>
|
||||
<div className="flex flex-wrap gap-1.5 p-3 bg-bg-input border border-border-primary rounded-xl max-h-40 overflow-y-auto">
|
||||
{availableTools.map(tool => {
|
||||
let currentTools: string[] = [];
|
||||
@@ -311,14 +319,14 @@ export function WorkerIndividualSettings() {
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{availableTools.length === 0 && <span className="text-xs text-text-muted">No tools</span>}
|
||||
{availableTools.length === 0 && <span className="text-xs text-text-muted">{t('agent.noTools')}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{modalMessage && <div className="p-3 bg-danger-bg text-danger text-sm rounded-xl border border-danger/20">{modalMessage}</div>}
|
||||
<div className="pt-3 flex justify-end gap-2 border-t border-border-primary">
|
||||
<button type="button" onClick={() => setIsEditing(false)} className="px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-hover rounded-xl transition-colors">Cancel</button>
|
||||
<button type="button" onClick={() => setIsEditing(false)} className="px-4 py-2 text-sm font-medium text-text-secondary hover:bg-bg-hover rounded-xl transition-colors">{t('common.cancel')}</button>
|
||||
<button type="submit" disabled={submitLoading} className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-accent rounded-xl hover:bg-accent-hover transition-colors disabled:opacity-50">
|
||||
<Save size={14} /> {submitLoading ? 'Saving...' : 'Save'}
|
||||
<Save size={14} /> {submitLoading ? t('common.saving') : t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user