import React, { useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; import JSZip from 'jszip'; import { getModelIcon, getShortModelName } from '../utils/modelIcons'; import LobeModelLogo from './LobeModelLogo.jsx'; export default function TraderView({ agents, agentProfilesByAgent, agentSkillsByAgent, workspaceFilesByAgent, selectedAgentId, selectedAgentProfile, selectedAgentSkills, skillDetailsByName, localSkillDraftsByKey, skillDetailLoadingKey, editableFiles, selectedWorkspaceFile, workspaceFileContent, workspaceDraftContent, isConnected, isAgentSkillsLoading, agentSkillsSavingKey, agentSkillsFeedback, isWorkspaceFileLoading, workspaceFileSavingKey, workspaceFileFeedback, onAgentChange, onCreateLocalSkill, onSkillDetailRequest, onLocalSkillDraftChange, onLocalSkillDelete, onLocalSkillSave, onRemoveSharedSkill, onSkillToggle, onWorkspaceFileChange, onWorkspaceDraftChange, onWorkspaceFileSave, onUploadExternalSkill }) { const srOnlyStyle = { position: 'absolute', width: 1, height: 1, padding: 0, margin: -1, overflow: 'hidden', clip: 'rect(0, 0, 0, 0)', whiteSpace: 'nowrap', border: 0 }; const [expandedSkillKey, setExpandedSkillKey] = useState(null); const [newLocalSkillName, setNewLocalSkillName] = useState(''); const [externalSkillFile, setExternalSkillFile] = useState(null); const [isExternalSkillChecking, setIsExternalSkillChecking] = useState(false); const [externalSkillCheck, setExternalSkillCheck] = useState({ type: null, text: '' }); const [isSkillPickerOpen, setIsSkillPickerOpen] = useState(false); const selectedAgent = useMemo( () => agents.find((agent) => agent.id === selectedAgentId) || agents[0] || null, [agents, selectedAgentId] ); useEffect(() => { setExpandedSkillKey(null); }, [selectedAgentId]); if (!selectedAgent) { return null; } const profile = selectedAgentProfile || {}; const modelInfo = getModelIcon(profile.model_name, profile.model_provider); const activeSkills = selectedAgentSkills.filter((item) => item.status === 'enabled' || item.status === 'active'); const installedSkills = selectedAgentSkills.filter((item) => item.status !== 'available'); const availableSkills = selectedAgentSkills.filter((item) => item.status === 'available'); const validateExternalSkillZip = async (file) => { if (!(file instanceof File)) { setExternalSkillCheck({ type: 'error', text: '请选择 zip 文件' }); return false; } if (!file.name.toLowerCase().endsWith('.zip')) { setExternalSkillCheck({ type: 'error', text: '仅支持 .zip 文件' }); return false; } setIsExternalSkillChecking(true); setExternalSkillCheck({ type: null, text: '' }); try { const zip = await JSZip.loadAsync(file); const entries = Object.keys(zip.files); const skillFilePath = entries.find((entry) => { const item = zip.files[entry]; return !item.dir && /(^|\/)SKILL\.md$/i.test(entry); }); if (!skillFilePath) { setExternalSkillCheck({ type: 'error', text: '压缩包中未检测到 SKILL.md,请检查目录结构' }); return false; } setExternalSkillCheck({ type: 'success', text: `预检通过,检测到: ${skillFilePath}` }); return true; } catch (error) { setExternalSkillCheck({ type: 'error', text: `无法解析 zip: ${error?.message || '未知错误'}` }); return false; } finally { setIsExternalSkillChecking(false); } }; return (
交易员档案
聚焦查看每个 Agent 的模型、工具组、技能编排和工作区记忆,不展示交易表现数据
{/* Left: agent avatar list */}
{agents.map((agent) => { const isSelected = agent.id === selectedAgentId; return ( ); })}
{/* Right: agent detail content */}
{selectedAgent.name}
{selectedAgent.name}
{selectedAgent.role}
当前档案已展开
模型
{getShortModelName(profile.model_name)}
技能
已启用: {activeSkills.length} / 已安装: {installedSkills.length}
{isAgentSkillsLoading ? (
加载技能中...
) : installedSkills.length === 0 ? (
暂无技能
) : installedSkills.map((skill) => { const isEnabled = skill.status === 'enabled' || skill.status === 'active'; const saving = agentSkillsSavingKey === `${selectedAgentId}:${skill.skill_name}` || agentSkillsSavingKey === `${selectedAgentId}:${skill.skill_name}:content` || agentSkillsSavingKey === `${selectedAgentId}:${skill.skill_name}:delete` || agentSkillsSavingKey === `${selectedAgentId}:${skill.skill_name}:remove`; const isExpanded = expandedSkillKey === skill.skill_name; const detailKey = `${selectedAgentId}:${skill.skill_name}`; const skillDetail = skillDetailsByName?.[detailKey] || null; const skillDraft = localSkillDraftsByKey?.[detailKey] ?? ''; const isDetailLoading = skillDetailLoadingKey === detailKey; const isLocalSkill = skill.source === 'local'; return (
{isLocalSkill ? ( ) : ( )}
{isExpanded && (
{isDetailLoading ? '加载技能说明中...' : (skillDetail?.content || '暂无更详细的技能说明')}
{isLocalSkill && !isDetailLoading && (
本地技能 SKILL.md