feat: update frontend runtime team controls
This commit is contained in:
@@ -24,7 +24,7 @@ That gives you:
|
|||||||
- trading service at `http://localhost:8001`
|
- trading service at `http://localhost:8001`
|
||||||
- news service at `http://localhost:8002`
|
- news service at `http://localhost:8002`
|
||||||
- runtime service at `http://localhost:8003/api/runtime`
|
- runtime service at `http://localhost:8003/api/runtime`
|
||||||
- gateway WebSocket at `ws://localhost:8765`
|
- gateway WebSocket at `ws://localhost:8765` started directly by `start-dev.sh`
|
||||||
|
|
||||||
## Frontend Environment Variables
|
## Frontend Environment Variables
|
||||||
|
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ export default function RuntimeSettingsPanel({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="daily">每日定时</option>
|
<option value="daily">每日定时</option>
|
||||||
<option value="intraday">盘中轮询</option>
|
<option value="interval">间隔轮询</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,32 @@ export const WS_URL =
|
|||||||
? `${FALLBACK_WS_PROTOCOL}//${FALLBACK_WS_HOST}:8765`
|
? `${FALLBACK_WS_PROTOCOL}//${FALLBACK_WS_HOST}:8765`
|
||||||
: `${FALLBACK_WS_PROTOCOL}//${FALLBACK_WS_HOST}${FALLBACK_WS_PORT}/ws`);
|
: `${FALLBACK_WS_PROTOCOL}//${FALLBACK_WS_HOST}${FALLBACK_WS_PORT}/ws`);
|
||||||
|
|
||||||
|
// Dynamic Team Management API
|
||||||
|
const DEFAULT_DYNAMIC_TEAM_API_BASE = isLocalDevHost()
|
||||||
|
? "http://localhost:8003/api/dynamic-team"
|
||||||
|
: `${DEFAULT_CONTROL_API_BASE}/dynamic-team`;
|
||||||
|
export const DYNAMIC_TEAM_API_BASE =
|
||||||
|
trimTrailingSlash(import.meta.env.VITE_DYNAMIC_TEAM_API_BASE_URL || "") ||
|
||||||
|
DEFAULT_DYNAMIC_TEAM_API_BASE;
|
||||||
|
|
||||||
|
// Dynamic Team API Endpoints
|
||||||
|
export const DYNAMIC_TEAM_ENDPOINTS = {
|
||||||
|
// Get all available analyst types (builtin + runtime registered)
|
||||||
|
listTypes: () => `${DYNAMIC_TEAM_API_BASE}/types`,
|
||||||
|
// Get personas from personas.yaml
|
||||||
|
getPersonas: () => `${DYNAMIC_TEAM_API_BASE}/personas`,
|
||||||
|
// Create a new analyst
|
||||||
|
createAnalyst: (runId) => `${DYNAMIC_TEAM_API_BASE}/runs/${runId}/analysts`,
|
||||||
|
// Clone an existing analyst
|
||||||
|
cloneAnalyst: (runId) => `${DYNAMIC_TEAM_API_BASE}/runs/${runId}/analysts/clone`,
|
||||||
|
// Remove an analyst
|
||||||
|
removeAnalyst: (runId, agentId) => `${DYNAMIC_TEAM_API_BASE}/runs/${runId}/analysts/${agentId}`,
|
||||||
|
// Get analyst info
|
||||||
|
getAnalystInfo: (runId, agentId) => `${DYNAMIC_TEAM_API_BASE}/runs/${runId}/analysts/${agentId}`,
|
||||||
|
// Get team summary
|
||||||
|
getTeamSummary: (runId) => `${DYNAMIC_TEAM_API_BASE}/runs/${runId}/summary`,
|
||||||
|
};
|
||||||
|
|
||||||
// Initial ticker symbols for the production watchlist
|
// Initial ticker symbols for the production watchlist
|
||||||
export const INITIAL_TICKERS = [
|
export const INITIAL_TICKERS = [
|
||||||
{ symbol: "AAPL", price: null, change: null },
|
{ symbol: "AAPL", price: null, change: null },
|
||||||
@@ -170,3 +196,191 @@ export const INITIAL_TICKERS = [
|
|||||||
{ symbol: "COIN", price: null, change: null }
|
{ symbol: "COIN", price: null, change: null }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Dynamic Analyst Team Management
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Built-in analyst types that can be used as base for dynamic analysts
|
||||||
|
*
|
||||||
|
* IMPORTANT: When creating dynamic analysts, the agent_id MUST end with '_analyst'
|
||||||
|
* to receive analysis tool groups (fundamentals, technical, sentiment, valuation tools).
|
||||||
|
* Example: 'crypto_specialist_analyst' (correct) vs 'crypto_specialist' (incorrect)
|
||||||
|
*/
|
||||||
|
export const BUILTIN_ANALYST_TYPES = [
|
||||||
|
{
|
||||||
|
typeId: "fundamentals_analyst",
|
||||||
|
name: "基本面分析师",
|
||||||
|
description: "Uses LLM to intelligently select analysis tools, focuses on financial data and company fundamental analysis",
|
||||||
|
icon: "fundamentals",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeId: "technical_analyst",
|
||||||
|
name: "技术分析师",
|
||||||
|
description: "Uses LLM to intelligently select analysis tools, focuses on technical indicators and chart analysis",
|
||||||
|
icon: "technical",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeId: "sentiment_analyst",
|
||||||
|
name: "情绪分析师",
|
||||||
|
description: "Uses LLM to intelligently select analysis tools, analyzes market sentiment and news sentiment",
|
||||||
|
icon: "sentiment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeId: "valuation_analyst",
|
||||||
|
name: "估值分析师",
|
||||||
|
description: "Uses LLM to intelligently select analysis tools, focuses on company valuation and value assessment",
|
||||||
|
icon: "valuation",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default colors for dynamically created analysts
|
||||||
|
* Cycles through these colors for new analysts
|
||||||
|
*/
|
||||||
|
export const DYNAMIC_ANALYST_COLORS = [
|
||||||
|
{ bg: "#F9FDFF", text: "#1565C0", accent: "#1565C0" }, // Blue
|
||||||
|
{ bg: "#FFF8F8", text: "#C62828", accent: "#C62828" }, // Red
|
||||||
|
{ bg: "#FAFFFA", text: "#2E7D32", accent: "#2E7D32" }, // Green
|
||||||
|
{ bg: "#FCFAFF", text: "#6A1B9A", accent: "#6A1B9A" }, // Purple
|
||||||
|
{ bg: "#FFFCF7", text: "#E65100", accent: "#E65100" }, // Orange
|
||||||
|
{ bg: "#F9FEFF", text: "#00838F", accent: "#00838F" }, // Cyan
|
||||||
|
{ bg: "#FFF9F5", text: "#D84315", accent: "#D84315" }, // Deep Orange
|
||||||
|
{ bg: "#F5F5FF", text: "#4527A0", accent: "#4527A0" }, // Deep Purple
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a color scheme for a dynamic analyst based on index
|
||||||
|
* @param {number} index - The index of the analyst
|
||||||
|
* @returns {Object} Color scheme object
|
||||||
|
*/
|
||||||
|
export const getDynamicAnalystColors = (index) => {
|
||||||
|
return DYNAMIC_ANALYST_COLORS[index % DYNAMIC_ANALYST_COLORS.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a default avatar URL for dynamic analysts
|
||||||
|
* Uses a hash of the agentId to select from available avatars
|
||||||
|
* @param {string} agentId - The agent ID
|
||||||
|
* @returns {string} Avatar URL
|
||||||
|
*/
|
||||||
|
export const getDynamicAnalystAvatar = (agentId) => {
|
||||||
|
const avatars = [
|
||||||
|
CDN_ASSETS.companyRoom.agent_1,
|
||||||
|
CDN_ASSETS.companyRoom.agent_2,
|
||||||
|
CDN_ASSETS.companyRoom.agent_3,
|
||||||
|
CDN_ASSETS.companyRoom.agent_4,
|
||||||
|
CDN_ASSETS.companyRoom.agent_5,
|
||||||
|
CDN_ASSETS.companyRoom.agent_6,
|
||||||
|
];
|
||||||
|
// Simple hash function to consistently map agentId to an avatar
|
||||||
|
const hash = agentId.split("").reduce((acc, char) => {
|
||||||
|
return acc + char.charCodeAt(0);
|
||||||
|
}, 0);
|
||||||
|
return avatars[hash % avatars.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a dynamic analyst configuration object
|
||||||
|
* @param {Object} config - Configuration object
|
||||||
|
* @param {string} config.agentId - Unique identifier
|
||||||
|
* @param {string} config.baseType - Base analyst type (e.g., "technical_analyst")
|
||||||
|
* @param {string} config.name - Display name
|
||||||
|
* @param {string[]} config.focus - Focus areas
|
||||||
|
* @param {string} config.description - Description
|
||||||
|
* @param {number} index - Index for color assignment
|
||||||
|
* @returns {Object} Complete agent configuration
|
||||||
|
*/
|
||||||
|
export const createDynamicAnalystConfig = ({
|
||||||
|
agentId,
|
||||||
|
baseType,
|
||||||
|
name,
|
||||||
|
focus = [],
|
||||||
|
description = "",
|
||||||
|
index = 0,
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
id: agentId,
|
||||||
|
name: name || agentId,
|
||||||
|
role: name || agentId,
|
||||||
|
baseType,
|
||||||
|
focus,
|
||||||
|
description,
|
||||||
|
avatar: getDynamicAnalystAvatar(agentId),
|
||||||
|
colors: getDynamicAnalystColors(index),
|
||||||
|
isDynamic: true,
|
||||||
|
isCustom: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an agent is a dynamic analyst
|
||||||
|
* @param {Object} agent - Agent object
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export const isDynamicAnalyst = (agent) => {
|
||||||
|
return agent?.isDynamic === true || agent?.id?.includes("_");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate agent ID format for dynamic analysts
|
||||||
|
* @param {string} agentId - Agent ID to validate
|
||||||
|
* @returns {Object} Validation result
|
||||||
|
*/
|
||||||
|
export const validateAgentId = (agentId) => {
|
||||||
|
const errors = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
if (!agentId) {
|
||||||
|
errors.push("Agent ID is required");
|
||||||
|
} else if (typeof agentId !== "string") {
|
||||||
|
errors.push("Agent ID must be a string");
|
||||||
|
} else {
|
||||||
|
if (agentId.length < 3) {
|
||||||
|
errors.push("Agent ID must be at least 3 characters");
|
||||||
|
}
|
||||||
|
if (agentId.length > 50) {
|
||||||
|
errors.push("Agent ID must be at most 50 characters");
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(agentId)) {
|
||||||
|
errors.push("Agent ID can only contain letters, numbers, and underscores");
|
||||||
|
}
|
||||||
|
// Reserved IDs that cannot be used
|
||||||
|
const reservedIds = ["portfolio_manager", "risk_manager"];
|
||||||
|
if (reservedIds.includes(agentId)) {
|
||||||
|
errors.push(`"${agentId}" is a reserved ID and cannot be used`);
|
||||||
|
}
|
||||||
|
// Warning: agent_id should end with '_analyst' to get analysis tools
|
||||||
|
if (!agentId.endsWith("_analyst")) {
|
||||||
|
warnings.push(
|
||||||
|
"Agent ID should end with '_analyst' to receive analysis tool groups"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: errors.length === 0,
|
||||||
|
errors,
|
||||||
|
warnings,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a suggested agent ID from a name
|
||||||
|
* IMPORTANT: Agent ID must end with '_analyst' to receive analysis tools
|
||||||
|
* @param {string} name - Display name
|
||||||
|
* @param {string} baseType - Base analyst type
|
||||||
|
* @returns {string} Suggested agent ID (guaranteed to end with '_analyst')
|
||||||
|
*/
|
||||||
|
export const suggestAgentId = (name, baseType) => {
|
||||||
|
const timestamp = Date.now().toString(36).slice(-4);
|
||||||
|
const normalized = name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9\s]/g, "")
|
||||||
|
.replace(/\s+/g, "_")
|
||||||
|
.replace(/_analyst$/, "") // Remove '_analyst' suffix if present to avoid duplication
|
||||||
|
.slice(0, 20);
|
||||||
|
// Must end with '_analyst' to get analysis tools registered
|
||||||
|
return `${normalized || baseType}_${timestamp}_analyst`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -49,11 +49,24 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
return runId;
|
return runId;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const sendWs = useCallback((payload) => {
|
||||||
|
const client = clientRef.current;
|
||||||
|
if (!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return client.send(payload);
|
||||||
|
}, [clientRef]);
|
||||||
|
|
||||||
const requestAgentSkills = useCallback((agentId) => {
|
const requestAgentSkills = useCallback((agentId) => {
|
||||||
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
|
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
setIsAgentSkillsLoading(true);
|
setIsAgentSkillsLoading(true);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'get_agent_skills', agent_id: normalized });
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => fetchAgentSkills(runId, normalized))
|
.then((runId) => fetchAgentSkills(runId, normalized))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -61,22 +74,19 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
setIsAgentSkillsLoading(false);
|
setIsAgentSkillsLoading(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setIsAgentSkillsLoading(false);
|
setIsAgentSkillsLoading(false);
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST agent skills request failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'get_agent_skills', agent_id: normalized });
|
|
||||||
if (!success) {
|
|
||||||
setIsAgentSkillsLoading(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}, [clientRef, resolveRunId, setAgentSkillsByAgent, setIsAgentSkillsLoading, setAgentSkillsFeedback]);
|
}, [resolveRunId, sendWs, setAgentSkillsByAgent, setIsAgentSkillsLoading, setAgentSkillsFeedback]);
|
||||||
|
|
||||||
const requestAgentProfile = useCallback((agentId) => {
|
const requestAgentProfile = useCallback((agentId) => {
|
||||||
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
|
const normalized = typeof agentId === 'string' ? agentId.trim() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
|
const sent = sendWs({ type: 'get_agent_profile', agent_id: normalized });
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => fetchAgentProfile(runId, normalized))
|
.then((runId) => fetchAgentProfile(runId, normalized))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -85,20 +95,20 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
[normalized]: payload?.profile && typeof payload.profile === 'object' ? payload.profile : {}
|
[normalized]: payload?.profile && typeof payload.profile === 'object' ? payload.profile : {}
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {});
|
||||||
if (clientRef.current) {
|
|
||||||
console.debug('REST agent profile request failed, falling back to websocket compatibility path');
|
|
||||||
clientRef.current.send({ type: 'get_agent_profile', agent_id: normalized });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
return true;
|
||||||
}, [clientRef, resolveRunId, setAgentProfilesByAgent]);
|
}, [resolveRunId, sendWs, setAgentProfilesByAgent]);
|
||||||
|
|
||||||
const requestSkillDetail = useCallback((skillName) => {
|
const requestSkillDetail = useCallback((skillName) => {
|
||||||
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
|
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
const detailKey = `${selectedSkillAgentId}:${normalized}`;
|
const detailKey = `${selectedSkillAgentId}:${normalized}`;
|
||||||
setSkillDetailLoadingKey(detailKey);
|
setSkillDetailLoadingKey(detailKey);
|
||||||
|
const sent = sendWs({ type: 'get_skill_detail', agent_id: selectedSkillAgentId, skill_name: normalized });
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => fetchAgentSkillDetail(runId, selectedSkillAgentId, normalized))
|
.then((runId) => fetchAgentSkillDetail(runId, selectedSkillAgentId, normalized))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -110,18 +120,10 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
setSkillDetailLoadingKey(null);
|
setSkillDetailLoadingKey(null);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setSkillDetailLoadingKey(null);
|
setSkillDetailLoadingKey(null);
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST skill detail request failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'get_skill_detail', agent_id: selectedSkillAgentId, skill_name: normalized });
|
|
||||||
if (!success) {
|
|
||||||
setSkillDetailLoadingKey(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}, [clientRef, resolveRunId, selectedSkillAgentId, setSkillDetailLoadingKey, setSkillDetailsByName]);
|
}, [resolveRunId, selectedSkillAgentId, sendWs, setSkillDetailLoadingKey, setSkillDetailsByName]);
|
||||||
|
|
||||||
const handleCreateLocalSkill = useCallback((skillName) => {
|
const handleCreateLocalSkill = useCallback((skillName) => {
|
||||||
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
|
const normalized = typeof skillName === 'string' ? skillName.trim() : '';
|
||||||
@@ -131,6 +133,11 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
}
|
}
|
||||||
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${normalized}:create`);
|
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${normalized}:create`);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'create_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: normalized });
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => createAgentLocalSkill(runId, selectedSkillAgentId, normalized))
|
.then((runId) => createAgentLocalSkill(runId, selectedSkillAgentId, normalized))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -140,19 +147,10 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
requestSkillDetail(normalized);
|
requestSkillDetail(normalized);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST local skill create failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'create_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: normalized });
|
|
||||||
if (!success) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clientRef, requestAgentSkills, requestSkillDetail, resolveRunId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
}, [requestAgentSkills, requestSkillDetail, resolveRunId, selectedSkillAgentId, sendWs, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
||||||
|
|
||||||
const handleLocalSkillDraftChange = useCallback((skillName, content) => {
|
const handleLocalSkillDraftChange = useCallback((skillName, content) => {
|
||||||
const detailKey = `${selectedSkillAgentId}:${skillName}`;
|
const detailKey = `${selectedSkillAgentId}:${skillName}`;
|
||||||
@@ -165,6 +163,11 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
if (typeof content !== 'string') return;
|
if (typeof content !== 'string') return;
|
||||||
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:content`);
|
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:content`);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'update_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName, content });
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => updateAgentLocalSkill(runId, selectedSkillAgentId, skillName, content))
|
.then((runId) => updateAgentLocalSkill(runId, selectedSkillAgentId, skillName, content))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -173,23 +176,19 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
requestSkillDetail(skillName);
|
requestSkillDetail(skillName);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST local skill save failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'update_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName, content });
|
|
||||||
if (!success) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clientRef, localSkillDraftsByKey, requestSkillDetail, resolveRunId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
}, [localSkillDraftsByKey, requestSkillDetail, resolveRunId, selectedSkillAgentId, sendWs, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
||||||
|
|
||||||
const handleLocalSkillDelete = useCallback((skillName) => {
|
const handleLocalSkillDelete = useCallback((skillName) => {
|
||||||
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:delete`);
|
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:delete`);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'delete_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => deleteAgentLocalSkill(runId, selectedSkillAgentId, skillName))
|
.then((runId) => deleteAgentLocalSkill(runId, selectedSkillAgentId, skillName))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -198,23 +197,19 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
requestAgentSkills(selectedSkillAgentId);
|
requestAgentSkills(selectedSkillAgentId);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST local skill delete failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'delete_agent_local_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
|
|
||||||
if (!success) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clientRef, requestAgentSkills, resolveRunId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
}, [requestAgentSkills, resolveRunId, selectedSkillAgentId, sendWs, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
||||||
|
|
||||||
const handleRemoveSharedSkill = useCallback((skillName) => {
|
const handleRemoveSharedSkill = useCallback((skillName) => {
|
||||||
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:remove`);
|
setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:remove`);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'remove_agent_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => disableAgentSkill(runId, selectedSkillAgentId, skillName))
|
.then((runId) => disableAgentSkill(runId, selectedSkillAgentId, skillName))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -223,24 +218,20 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
requestAgentSkills(selectedSkillAgentId);
|
requestAgentSkills(selectedSkillAgentId);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST shared skill remove failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'remove_agent_skill', agent_id: selectedSkillAgentId, skill_name: skillName });
|
|
||||||
if (!success) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clientRef, requestAgentSkills, resolveRunId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
}, [requestAgentSkills, resolveRunId, selectedSkillAgentId, sendWs, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
||||||
|
|
||||||
const handleAgentSkillToggle = useCallback((skillName, enabled) => {
|
const handleAgentSkillToggle = useCallback((skillName, enabled) => {
|
||||||
const agentId = selectedSkillAgentId;
|
const agentId = selectedSkillAgentId;
|
||||||
setAgentSkillsSavingKey(`${agentId}:${skillName}`);
|
setAgentSkillsSavingKey(`${agentId}:${skillName}`);
|
||||||
setAgentSkillsFeedback(null);
|
setAgentSkillsFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'update_agent_skill', agent_id: agentId, skill_name: skillName, enabled });
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => enabled
|
.then((runId) => enabled
|
||||||
? enableAgentSkill(runId, agentId, skillName)
|
? enableAgentSkill(runId, agentId, skillName)
|
||||||
@@ -251,19 +242,10 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
requestAgentSkills(agentId);
|
requestAgentSkills(agentId);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setAgentSkillsFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST skill toggle failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'update_agent_skill', agent_id: agentId, skill_name: skillName, enabled });
|
|
||||||
if (!success) {
|
|
||||||
setAgentSkillsSavingKey(null);
|
|
||||||
setAgentSkillsFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [clientRef, requestAgentSkills, resolveRunId, selectedSkillAgentId, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
}, [requestAgentSkills, resolveRunId, selectedSkillAgentId, sendWs, setAgentSkillsFeedback, setAgentSkillsSavingKey]);
|
||||||
|
|
||||||
const handleSkillAgentChange = useCallback((agentId) => {
|
const handleSkillAgentChange = useCallback((agentId) => {
|
||||||
setSelectedSkillAgentId(agentId);
|
setSelectedSkillAgentId(agentId);
|
||||||
@@ -278,6 +260,11 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
if (!normalizedAgentId || !normalizedFilename) return false;
|
if (!normalizedAgentId || !normalizedFilename) return false;
|
||||||
setIsWorkspaceFileLoading(true);
|
setIsWorkspaceFileLoading(true);
|
||||||
setWorkspaceFileFeedback(null);
|
setWorkspaceFileFeedback(null);
|
||||||
|
const sent = sendWs({ type: 'get_agent_workspace_file', agent_id: normalizedAgentId, filename: normalizedFilename });
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => fetchAgentWorkspaceFile(runId, normalizedAgentId, normalizedFilename))
|
.then((runId) => fetchAgentWorkspaceFile(runId, normalizedAgentId, normalizedFilename))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -292,18 +279,10 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
setIsWorkspaceFileLoading(false);
|
setIsWorkspaceFileLoading(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setIsWorkspaceFileLoading(false);
|
setIsWorkspaceFileLoading(false);
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST workspace file read failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({ type: 'get_agent_workspace_file', agent_id: normalizedAgentId, filename: normalizedFilename });
|
|
||||||
if (!success) {
|
|
||||||
setIsWorkspaceFileLoading(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}, [clientRef, resolveRunId, setIsWorkspaceFileLoading, setWorkspaceDraftContent, setWorkspaceFileFeedback, setWorkspaceFilesByAgent]);
|
}, [resolveRunId, sendWs, setIsWorkspaceFileLoading, setWorkspaceDraftContent, setWorkspaceFileFeedback, setWorkspaceFilesByAgent]);
|
||||||
|
|
||||||
const handleWorkspaceFileChange = useCallback((filename) => {
|
const handleWorkspaceFileChange = useCallback((filename) => {
|
||||||
useAgentStore.getState().setSelectedWorkspaceFile(filename);
|
useAgentStore.getState().setSelectedWorkspaceFile(filename);
|
||||||
@@ -314,6 +293,16 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
const key = `${selectedSkillAgentId}:${selectedWorkspaceFile}`;
|
const key = `${selectedSkillAgentId}:${selectedWorkspaceFile}`;
|
||||||
setWorkspaceFileSavingKey(key);
|
setWorkspaceFileSavingKey(key);
|
||||||
setWorkspaceFileFeedback(null);
|
setWorkspaceFileFeedback(null);
|
||||||
|
const sent = sendWs({
|
||||||
|
type: 'update_agent_workspace_file',
|
||||||
|
agent_id: selectedSkillAgentId,
|
||||||
|
filename: selectedWorkspaceFile,
|
||||||
|
content: workspaceDraftContent
|
||||||
|
});
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void resolveRunId()
|
void resolveRunId()
|
||||||
.then((runId) => updateAgentWorkspaceFile(runId, selectedSkillAgentId, selectedWorkspaceFile, workspaceDraftContent))
|
.then((runId) => updateAgentWorkspaceFile(runId, selectedSkillAgentId, selectedWorkspaceFile, workspaceDraftContent))
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -328,24 +317,10 @@ export function useAgentDataRequests(clientRef) {
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (!clientRef.current) {
|
|
||||||
setWorkspaceFileSavingKey(null);
|
setWorkspaceFileSavingKey(null);
|
||||||
setWorkspaceFileFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
setWorkspaceFileFeedback({ type: 'error', text: '连接未就绪,稍后重试' });
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.debug('REST workspace file save failed, falling back to websocket compatibility path');
|
|
||||||
const success = clientRef.current.send({
|
|
||||||
type: 'update_agent_workspace_file',
|
|
||||||
agent_id: selectedSkillAgentId,
|
|
||||||
filename: selectedWorkspaceFile,
|
|
||||||
content: workspaceDraftContent
|
|
||||||
});
|
});
|
||||||
if (!success) {
|
}, [resolveRunId, selectedSkillAgentId, selectedWorkspaceFile, sendWs, setWorkspaceFileFeedback, setWorkspaceFileSavingKey, setWorkspaceFilesByAgent, workspaceDraftContent]);
|
||||||
setWorkspaceFileSavingKey(null);
|
|
||||||
setWorkspaceFileFeedback({ type: 'error', text: '发送失败,请检查连接状态' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [clientRef, resolveRunId, selectedSkillAgentId, selectedWorkspaceFile, setWorkspaceFileFeedback, setWorkspaceFileSavingKey, setWorkspaceFilesByAgent, workspaceDraftContent]);
|
|
||||||
|
|
||||||
const handleUploadExternalSkill = useCallback(async (file) => {
|
const handleUploadExternalSkill = useCallback(async (file) => {
|
||||||
if (!(file instanceof File)) {
|
if (!(file instanceof File)) {
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
const { setOhlcHistoryByTicker, setPriceHistoryByTicker, setHistorySourceByTicker,
|
const { setOhlcHistoryByTicker, setPriceHistoryByTicker, setHistorySourceByTicker,
|
||||||
setNewsByTicker, setInsiderTradesByTicker } = useMarketStore();
|
setNewsByTicker, setInsiderTradesByTicker } = useMarketStore();
|
||||||
|
|
||||||
|
const sendWs = useCallback((payload) => {
|
||||||
|
const client = clientRef.current;
|
||||||
|
if (!client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return client.send(payload);
|
||||||
|
}, [clientRef]);
|
||||||
|
|
||||||
const requestStockHistory = useCallback((symbol, { force = false } = {}) => {
|
const requestStockHistory = useCallback((symbol, { force = false } = {}) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
@@ -40,6 +48,13 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
start.setDate(start.getDate() - 120);
|
start.setDate(start.getDate() - 120);
|
||||||
const startDate = start.toISOString().slice(0, 10);
|
const startDate = start.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const wsPayload = { type: 'get_stock_history', ticker: normalized, lookback_days: 120 };
|
||||||
|
const wsSent = sendWs(wsPayload);
|
||||||
|
if (wsSent) {
|
||||||
|
requestedStockHistoryRef.current.add(normalized);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectTradingService()) {
|
if (hasDirectTradingService()) {
|
||||||
void fetchStockHistoryDirect(normalized, startDate, endDate)
|
void fetchStockHistoryDirect(normalized, startDate, endDate)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -59,42 +74,36 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
setHistorySourceByTicker((prev) => ({ ...prev, [normalized]: 'trading_service' }));
|
setHistorySourceByTicker((prev) => ({ ...prev, [normalized]: 'trading_service' }));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct stock-history fetch failed, falling back to websocket:', error);
|
console.error('Direct stock-history fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
const success = clientRef.current.send({
|
|
||||||
type: 'get_stock_history',
|
|
||||||
ticker: normalized,
|
|
||||||
lookback_days: 120
|
|
||||||
});
|
|
||||||
if (success) requestedStockHistoryRef.current.add(normalized);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
requestedStockHistoryRef.current.add(normalized);
|
requestedStockHistoryRef.current.add(normalized);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
const success = clientRef.current.send({ type: 'get_stock_history', ticker: normalized, lookback_days: 120 });
|
}, [currentDate, hasDirectTradingService, sendWs, setOhlcHistoryByTicker, setPriceHistoryByTicker, setHistorySourceByTicker]);
|
||||||
if (success) requestedStockHistoryRef.current.add(normalized);
|
|
||||||
return success;
|
|
||||||
}, [clientRef, currentDate, setOhlcHistoryByTicker, setPriceHistoryByTicker, setHistorySourceByTicker]);
|
|
||||||
|
|
||||||
const requestStockExplainEvents = useCallback((symbol) => {
|
const requestStockExplainEvents = useCallback((symbol) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !clientRef.current) return false;
|
if (!normalized) return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_explain_events', ticker: normalized });
|
return sendWs({ type: 'get_stock_explain_events', ticker: normalized });
|
||||||
}, [clientRef]);
|
}, [sendWs]);
|
||||||
|
|
||||||
const requestStockNews = useCallback((symbol) => {
|
const requestStockNews = useCallback((symbol) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !clientRef.current) return false;
|
if (!normalized) return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_news', ticker: normalized, lookback_days: 45, limit: 12 });
|
return sendWs({ type: 'get_stock_news', ticker: normalized, lookback_days: 45, limit: 12 });
|
||||||
}, [clientRef]);
|
}, [sendWs]);
|
||||||
|
|
||||||
const requestStockNewsForDate = useCallback((symbol, date) => {
|
const requestStockNewsForDate = useCallback((symbol, date) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !date) return false;
|
if (!normalized || !date) return false;
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_news_for_date', ticker: normalized, date, limit: 20 });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectNewsService()) {
|
if (hasDirectNewsService()) {
|
||||||
void fetchNewsForDateDirect(normalized, date, 20)
|
void fetchNewsForDateDirect(normalized, date, 20)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -111,23 +120,19 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct news-for-date fetch failed, falling back to websocket:', error);
|
console.error('Direct news-for-date fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_news_for_date', ticker: normalized, date, limit: 20 });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_news_for_date', ticker: normalized, date, limit: 20 });
|
}, [hasDirectNewsService, sendWs, setNewsByTicker]);
|
||||||
}, [clientRef, setNewsByTicker]);
|
|
||||||
|
|
||||||
const requestStockNewsTimeline = useCallback((symbol) => {
|
const requestStockNewsTimeline = useCallback((symbol) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !clientRef.current) return false;
|
if (!normalized) return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_news_timeline', ticker: normalized, lookback_days: 90 });
|
return sendWs({ type: 'get_stock_news_timeline', ticker: normalized, lookback_days: 90 });
|
||||||
}, [clientRef]);
|
}, [sendWs]);
|
||||||
|
|
||||||
const requestStockNewsCategories = useCallback((symbol) => {
|
const requestStockNewsCategories = useCallback((symbol) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
@@ -141,6 +146,11 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
start.setDate(start.getDate() - 90);
|
start.setDate(start.getDate() - 90);
|
||||||
const startDate = start.toISOString().slice(0, 10);
|
const startDate = start.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_news_categories', ticker: normalized, lookback_days: 90 });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectNewsService()) {
|
if (hasDirectNewsService()) {
|
||||||
void fetchNewsCategoriesDirect(normalized, startDate, endDate, 200)
|
void fetchNewsCategoriesDirect(normalized, startDate, endDate, 200)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -157,22 +167,23 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct news-categories fetch failed, falling back to websocket:', error);
|
console.error('Direct news-categories fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_news_categories', ticker: normalized, lookback_days: 90 });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_news_categories', ticker: normalized, lookback_days: 90 });
|
}, [currentDate, hasDirectNewsService, sendWs, setNewsByTicker]);
|
||||||
}, [clientRef, currentDate, setNewsByTicker]);
|
|
||||||
|
|
||||||
const requestStockInsiderTrades = useCallback((symbol, startDate = null, endDate = null) => {
|
const requestStockInsiderTrades = useCallback((symbol, startDate = null, endDate = null) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_insider_trades', ticker: normalized, start_date: startDate, end_date: endDate, limit: 50 });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectTradingService()) {
|
if (hasDirectTradingService()) {
|
||||||
void fetchInsiderTradesDirect(normalized, startDate, endDate, 50)
|
void fetchInsiderTradesDirect(normalized, startDate, endDate, 50)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -183,28 +194,29 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct insider-trades fetch failed, falling back to websocket:', error);
|
console.error('Direct insider-trades fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_insider_trades', ticker: normalized, start_date: startDate, end_date: endDate, limit: 50 });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_insider_trades', ticker: normalized, start_date: startDate, end_date: endDate, limit: 50 });
|
}, [hasDirectTradingService, sendWs, setInsiderTradesByTicker]);
|
||||||
}, [clientRef, setInsiderTradesByTicker]);
|
|
||||||
|
|
||||||
const requestStockTechnicalIndicators = useCallback((symbol) => {
|
const requestStockTechnicalIndicators = useCallback((symbol) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !clientRef.current) return false;
|
if (!normalized) return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_technical_indicators', ticker: normalized });
|
return sendWs({ type: 'get_stock_technical_indicators', ticker: normalized });
|
||||||
}, [clientRef]);
|
}, [sendWs]);
|
||||||
|
|
||||||
const requestStockRangeExplain = useCallback((symbol, startDate, endDate, articleIds = []) => {
|
const requestStockRangeExplain = useCallback((symbol, startDate, endDate, articleIds = []) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !startDate || !endDate) return false;
|
if (!normalized || !startDate || !endDate) return false;
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_range_explain', ticker: normalized, start_date: startDate, end_date: endDate, article_ids: Array.isArray(articleIds) ? articleIds : [] });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectNewsService()) {
|
if (hasDirectNewsService()) {
|
||||||
void fetchRangeExplainDirect(normalized, startDate, endDate, articleIds)
|
void fetchRangeExplainDirect(normalized, startDate, endDate, articleIds)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -224,22 +236,23 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct range explain fetch failed, falling back to websocket:', error);
|
console.error('Direct range explain fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_range_explain', ticker: normalized, start_date: startDate, end_date: endDate, article_ids: Array.isArray(articleIds) ? articleIds : [] });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_range_explain', ticker: normalized, start_date: startDate, end_date: endDate, article_ids: Array.isArray(articleIds) ? articleIds : [] });
|
}, [hasDirectNewsService, sendWs, setNewsByTicker]);
|
||||||
}, [clientRef, setNewsByTicker]);
|
|
||||||
|
|
||||||
const requestStockStory = useCallback((symbol, asOfDate = null) => {
|
const requestStockStory = useCallback((symbol, asOfDate = null) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized) return false;
|
if (!normalized) return false;
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_story', ticker: normalized, as_of_date: asOfDate });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectNewsService()) {
|
if (hasDirectNewsService()) {
|
||||||
void fetchStockStoryDirect(normalized, asOfDate)
|
void fetchStockStoryDirect(normalized, asOfDate)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -258,22 +271,23 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct story fetch failed, falling back to websocket:', error);
|
console.error('Direct story fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_story', ticker: normalized, as_of_date: asOfDate });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_story', ticker: normalized, as_of_date: asOfDate });
|
}, [hasDirectNewsService, sendWs, setNewsByTicker]);
|
||||||
}, [clientRef, setNewsByTicker]);
|
|
||||||
|
|
||||||
const requestStockSimilarDays = useCallback((symbol, date, topK = 8) => {
|
const requestStockSimilarDays = useCallback((symbol, date, topK = 8) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !date) return false;
|
if (!normalized || !date) return false;
|
||||||
|
|
||||||
|
const wsSent = sendWs({ type: 'get_stock_similar_days', ticker: normalized, date, top_k: topK });
|
||||||
|
if (wsSent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasDirectNewsService()) {
|
if (hasDirectNewsService()) {
|
||||||
void fetchSimilarDaysDirect(normalized, date, topK)
|
void fetchSimilarDaysDirect(normalized, date, topK)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
@@ -291,21 +305,17 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Direct similar-days fetch failed, falling back to websocket:', error);
|
console.error('Direct similar-days fetch failed:', error);
|
||||||
if (clientRef.current) {
|
|
||||||
clientRef.current.send({ type: 'get_stock_similar_days', ticker: normalized, date, top_k: topK });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientRef.current) return false;
|
return false;
|
||||||
return clientRef.current.send({ type: 'get_stock_similar_days', ticker: normalized, date, top_k: topK });
|
}, [hasDirectNewsService, sendWs, setNewsByTicker]);
|
||||||
}, [clientRef, setNewsByTicker]);
|
|
||||||
|
|
||||||
const requestStockEnrich = useCallback((symbol, options = {}) => {
|
const requestStockEnrich = useCallback((symbol, options = {}) => {
|
||||||
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
|
||||||
if (!normalized || !clientRef.current) return false;
|
if (!normalized) return false;
|
||||||
const startDate = typeof options.startDate === 'string' ? options.startDate.trim() : '';
|
const startDate = typeof options.startDate === 'string' ? options.startDate.trim() : '';
|
||||||
const endDate = typeof options.endDate === 'string' ? options.endDate.trim() : '';
|
const endDate = typeof options.endDate === 'string' ? options.endDate.trim() : '';
|
||||||
if (!startDate || !endDate) return false;
|
if (!startDate || !endDate) return false;
|
||||||
@@ -316,7 +326,7 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
maintenanceStatus: { running: true, error: null, updatedAt: new Date().toISOString(), stats: null }
|
maintenanceStatus: { running: true, error: null, updatedAt: new Date().toISOString(), stats: null }
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
return clientRef.current.send({
|
return sendWs({
|
||||||
type: 'run_stock_enrich',
|
type: 'run_stock_enrich',
|
||||||
ticker: normalized,
|
ticker: normalized,
|
||||||
start_date: startDate,
|
start_date: startDate,
|
||||||
@@ -328,7 +338,7 @@ export function useStockDataRequests(clientRef, { setRequestStockHistory, setReq
|
|||||||
story_date: options.storyDate || null,
|
story_date: options.storyDate || null,
|
||||||
target_date: options.targetDate || null
|
target_date: options.targetDate || null
|
||||||
});
|
});
|
||||||
}, [clientRef, setNewsByTicker]);
|
}, [sendWs, setNewsByTicker]);
|
||||||
|
|
||||||
// Register request functions with WebSocket connection hook
|
// Register request functions with WebSocket connection hook
|
||||||
if (setRequestStockHistory) setRequestStockHistory(requestStockHistory);
|
if (setRequestStockHistory) setRequestStockHistory(requestStockHistory);
|
||||||
|
|||||||
@@ -652,6 +652,7 @@ export function useWebSocketConnection({
|
|||||||
type: 'success',
|
type: 'success',
|
||||||
text: `${agentId} ${e.enabled ? '已启用' : '已禁用'} ${skillName}`
|
text: `${agentId} ${e.enabled ? '已启用' : '已禁用'} ${skillName}`
|
||||||
});
|
});
|
||||||
|
clientRef.current?.send({ type: 'get_agent_skills', agent_id: agentId });
|
||||||
},
|
},
|
||||||
|
|
||||||
agent_local_skill_created: (e) => {
|
agent_local_skill_created: (e) => {
|
||||||
@@ -660,6 +661,8 @@ export function useWebSocketConnection({
|
|||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
if (!agentId || !skillName) return;
|
if (!agentId || !skillName) return;
|
||||||
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 已创建本地技能 ${skillName}` });
|
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 已创建本地技能 ${skillName}` });
|
||||||
|
clientRef.current?.send({ type: 'get_agent_skills', agent_id: agentId });
|
||||||
|
clientRef.current?.send({ type: 'get_skill_detail', agent_id: agentId, skill_name: skillName });
|
||||||
},
|
},
|
||||||
|
|
||||||
agent_local_skill_updated: (e) => {
|
agent_local_skill_updated: (e) => {
|
||||||
@@ -668,6 +671,7 @@ export function useWebSocketConnection({
|
|||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
if (!agentId || !skillName) return;
|
if (!agentId || !skillName) return;
|
||||||
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 的本地技能 ${skillName} 已保存` });
|
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 的本地技能 ${skillName} 已保存` });
|
||||||
|
clientRef.current?.send({ type: 'get_skill_detail', agent_id: agentId, skill_name: skillName });
|
||||||
},
|
},
|
||||||
|
|
||||||
agent_local_skill_deleted: (e) => {
|
agent_local_skill_deleted: (e) => {
|
||||||
@@ -686,6 +690,7 @@ export function useWebSocketConnection({
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 的本地技能 ${skillName} 已删除` });
|
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 的本地技能 ${skillName} 已删除` });
|
||||||
|
clientRef.current?.send({ type: 'get_agent_skills', agent_id: agentId });
|
||||||
},
|
},
|
||||||
|
|
||||||
agent_skill_removed: (e) => {
|
agent_skill_removed: (e) => {
|
||||||
@@ -694,6 +699,7 @@ export function useWebSocketConnection({
|
|||||||
setAgentSkillsSavingKey(null);
|
setAgentSkillsSavingKey(null);
|
||||||
if (!agentId || !skillName) return;
|
if (!agentId || !skillName) return;
|
||||||
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 已移除共享技能 ${skillName}` });
|
setAgentSkillsFeedback({ type: 'success', text: `${agentId} 已移除共享技能 ${skillName}` });
|
||||||
|
clientRef.current?.send({ type: 'get_agent_skills', agent_id: agentId });
|
||||||
},
|
},
|
||||||
|
|
||||||
agent_workspace_file_loaded: (e) => {
|
agent_workspace_file_loaded: (e) => {
|
||||||
@@ -716,6 +722,7 @@ export function useWebSocketConnection({
|
|||||||
const filename = typeof e.filename === 'string' ? e.filename.trim() : '';
|
const filename = typeof e.filename === 'string' ? e.filename.trim() : '';
|
||||||
if (!agentId || !filename) return;
|
if (!agentId || !filename) return;
|
||||||
setWorkspaceFileFeedback({ type: 'success', text: `${agentId} 的 ${filename} 已保存` });
|
setWorkspaceFileFeedback({ type: 'success', text: `${agentId} 的 ${filename} 已保存` });
|
||||||
|
clientRef.current?.send({ type: 'get_agent_workspace_file', agent_id: agentId, filename });
|
||||||
},
|
},
|
||||||
|
|
||||||
watchlist_updated: (e) => {
|
watchlist_updated: (e) => {
|
||||||
|
|||||||
147
frontend/src/services/dynamicTeamApi.js
Normal file
147
frontend/src/services/dynamicTeamApi.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Dynamic Team API Service
|
||||||
|
*
|
||||||
|
* Provides methods for managing analyst team dynamically:
|
||||||
|
* - Create new analysts with custom configuration
|
||||||
|
* - Clone existing analysts
|
||||||
|
* - Remove analysts
|
||||||
|
* - List available analyst types
|
||||||
|
* - Get analyst information
|
||||||
|
*/
|
||||||
|
import { DYNAMIC_TEAM_ENDPOINTS } from "../config/constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch wrapper with error handling
|
||||||
|
*/
|
||||||
|
async function fetchJson(url, options = {}) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.text();
|
||||||
|
throw new Error(`API error: ${response.status} - ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all available analyst types (builtin + runtime registered)
|
||||||
|
* @returns {Promise<Array>} List of analyst types
|
||||||
|
*/
|
||||||
|
export async function listAnalystTypes() {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.listTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get personas from personas.yaml
|
||||||
|
* @returns {Promise<Object>} Personas configuration
|
||||||
|
*/
|
||||||
|
export async function getPersonas() {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.getPersonas());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new analyst
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @param {Object} config - Analyst configuration
|
||||||
|
* @param {string} config.agent_id - Unique identifier
|
||||||
|
* @param {string} config.analyst_type - Base type or custom identifier
|
||||||
|
* @param {Object} [config.persona] - Custom persona definition
|
||||||
|
* @param {string} [config.soul_md] - Custom SOUL.md content
|
||||||
|
* @param {string} [config.agents_md] - Custom AGENTS.md content
|
||||||
|
* @param {string} [config.profile_md] - Custom PROFILE.md content
|
||||||
|
* @param {string} [config.model_name] - Override default model
|
||||||
|
* @param {string[]} [config.skills] - List of skill IDs
|
||||||
|
* @param {string[]} [config.tags] - Classification tags
|
||||||
|
* @returns {Promise<Object>} Creation result
|
||||||
|
*/
|
||||||
|
export async function createAnalyst(runId, config) {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.createAnalyst(runId), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(config),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone an existing analyst
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @param {Object} config - Clone configuration
|
||||||
|
* @param {string} config.source_id - Source analyst ID
|
||||||
|
* @param {string} config.new_id - New analyst ID
|
||||||
|
* @param {string} [config.name] - New display name
|
||||||
|
* @param {string[]} [config.focus_additions] - Additional focus areas
|
||||||
|
* @param {string} [config.description_override] - New description
|
||||||
|
* @param {string} [config.model_name] - Override model
|
||||||
|
* @returns {Promise<Object>} Clone result
|
||||||
|
*/
|
||||||
|
export async function cloneAnalyst(runId, config) {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.cloneAnalyst(runId), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(config),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a dynamically created analyst
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @param {string} agentId - The analyst to remove
|
||||||
|
* @returns {Promise<Object>} Removal result
|
||||||
|
*/
|
||||||
|
export async function removeAnalyst(runId, agentId) {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.removeAnalyst(runId, agentId), {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about a specific analyst
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @param {string} agentId - The analyst ID
|
||||||
|
* @returns {Promise<Object>} Analyst information
|
||||||
|
*/
|
||||||
|
export async function getAnalystInfo(runId, agentId) {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.getAnalystInfo(runId, agentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a summary of the current analyst team
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @returns {Promise<Object>} Team summary
|
||||||
|
*/
|
||||||
|
export async function getTeamSummary(runId) {
|
||||||
|
return fetchJson(DYNAMIC_TEAM_ENDPOINTS.getTeamSummary(runId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for using dynamic team API in React components
|
||||||
|
* @param {string} runId - The run configuration ID
|
||||||
|
* @returns {Object} API methods
|
||||||
|
*/
|
||||||
|
export function useDynamicTeamApi(runId) {
|
||||||
|
return {
|
||||||
|
listTypes: () => listAnalystTypes(),
|
||||||
|
getPersonas: () => getPersonas(),
|
||||||
|
createAnalyst: (config) => createAnalyst(runId, config),
|
||||||
|
cloneAnalyst: (config) => cloneAnalyst(runId, config),
|
||||||
|
removeAnalyst: (agentId) => removeAnalyst(runId, agentId),
|
||||||
|
getAnalystInfo: (agentId) => getAnalystInfo(runId, agentId),
|
||||||
|
getTeamSummary: () => getTeamSummary(runId),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default export for convenience
|
||||||
|
export default {
|
||||||
|
listAnalystTypes,
|
||||||
|
getPersonas,
|
||||||
|
createAnalyst,
|
||||||
|
cloneAnalyst,
|
||||||
|
removeAnalyst,
|
||||||
|
getAnalystInfo,
|
||||||
|
getTeamSummary,
|
||||||
|
useDynamicTeamApi,
|
||||||
|
};
|
||||||
@@ -64,13 +64,14 @@ export const buildRuntimeSummaryLabel = (runtimeConfig) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scheduleMode = String(runtimeConfig.schedule_mode || "daily");
|
const rawScheduleMode = String(runtimeConfig.schedule_mode || "daily");
|
||||||
|
const scheduleMode = rawScheduleMode === "intraday" ? "interval" : rawScheduleMode;
|
||||||
const intervalMinutes = Number(runtimeConfig.interval_minutes || 60);
|
const intervalMinutes = Number(runtimeConfig.interval_minutes || 60);
|
||||||
const triggerTime = String(runtimeConfig.trigger_time || "now");
|
const triggerTime = String(runtimeConfig.trigger_time || "now");
|
||||||
const maxCommCycles = Number(runtimeConfig.max_comm_cycles || 2);
|
const maxCommCycles = Number(runtimeConfig.max_comm_cycles || 2);
|
||||||
|
|
||||||
if (scheduleMode === "intraday") {
|
if (scheduleMode === "interval") {
|
||||||
return `调度 intraday / ${intervalMinutes}m / 讨论 ${maxCommCycles} 轮`;
|
return `调度 interval / ${intervalMinutes}m / 讨论 ${maxCommCycles} 轮`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triggerTime.toLowerCase() === "now") {
|
if (triggerTime.toLowerCase() === "now") {
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ describe("runtimeControls", () => {
|
|||||||
})).toBe("调度 daily / 09:30 ET / 讨论 3 轮");
|
})).toBe("调度 daily / 09:30 ET / 讨论 3 轮");
|
||||||
|
|
||||||
expect(buildRuntimeSummaryLabel({
|
expect(buildRuntimeSummaryLabel({
|
||||||
schedule_mode: "intraday",
|
schedule_mode: "interval",
|
||||||
interval_minutes: 15,
|
interval_minutes: 15,
|
||||||
max_comm_cycles: 2
|
max_comm_cycles: 2
|
||||||
})).toBe("调度 intraday / 15m / 讨论 2 轮");
|
})).toBe("调度 interval / 15m / 讨论 2 轮");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user