mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 20:34:39 +08:00
feat(agents): pixelart icons, per-role colors, runtime identity merge, locale templates
This commit is contained in:
parent
2a8f6774e7
commit
be1fc86836
@ -918,11 +918,26 @@ public class AgentGraphBuilder {
|
|||||||
|
|
||||||
private String buildEnhancedPrompt(AgentEntity entity, boolean builtinSearchEnabled,
|
private String buildEnhancedPrompt(AgentEntity entity, boolean builtinSearchEnabled,
|
||||||
Set<String> boundTools, Integer maxInputTokens) {
|
Set<String> boundTools, Integer maxInputTokens) {
|
||||||
// 通过 MemoryManager 从所有 MemoryProvider 组装系统提示词(快照冻结)
|
// The agent's own systemPrompt encodes its identity (role / goal /
|
||||||
|
// backstory). The memory block from workspace files (AGENTS.md, SOUL.md,
|
||||||
|
// PROFILE.md, MEMORY.md, ...) augments that identity with durable
|
||||||
|
// context. Both are independently optional, but when both exist they
|
||||||
|
// must be joined — earlier this branch picked memory and silently
|
||||||
|
// dropped the identity prompt, so editor-side identity changes never
|
||||||
|
// reached runtime if the agent had any workspace files.
|
||||||
|
String identityPrompt = entity.getSystemPrompt() != null ? entity.getSystemPrompt().trim() : "";
|
||||||
String memoryPrompt = memoryManager.buildSystemPromptBlock(entity.getId());
|
String memoryPrompt = memoryManager.buildSystemPromptBlock(entity.getId());
|
||||||
String basePrompt = (memoryPrompt != null && !memoryPrompt.isBlank())
|
StringBuilder basePromptBuilder = new StringBuilder();
|
||||||
? memoryPrompt
|
if (!identityPrompt.isEmpty()) {
|
||||||
: (entity.getSystemPrompt() != null ? entity.getSystemPrompt() : "");
|
basePromptBuilder.append(identityPrompt);
|
||||||
|
}
|
||||||
|
if (memoryPrompt != null && !memoryPrompt.isBlank()) {
|
||||||
|
if (basePromptBuilder.length() > 0) {
|
||||||
|
basePromptBuilder.append("\n\n");
|
||||||
|
}
|
||||||
|
basePromptBuilder.append(memoryPrompt);
|
||||||
|
}
|
||||||
|
String basePrompt = basePromptBuilder.toString();
|
||||||
|
|
||||||
// 使用 skill runtime 构建技能增强(per-agent 绑定过滤)
|
// 使用 skill runtime 构建技能增强(per-agent 绑定过滤)
|
||||||
Set<Long> boundSkillIds = agentBindingService.getBoundSkillIds(entity.getId());
|
Set<Long> boundSkillIds = agentBindingService.getBoundSkillIds(entity.getId());
|
||||||
|
|||||||
@ -42,10 +42,15 @@ public class TemplateController {
|
|||||||
public R<AgentEntity> apply(
|
public R<AgentEntity> apply(
|
||||||
@PathVariable String id,
|
@PathVariable String id,
|
||||||
@RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId,
|
@RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId,
|
||||||
|
// Accept-Language is forwarded by the frontend (zh-CN, zh, en, en-US, ...)
|
||||||
|
// so the new agent's display name matches the user's locale —
|
||||||
|
// a Chinese user hiring "客服助理" should not get an English
|
||||||
|
// "Customer Support" agent in their list.
|
||||||
|
@RequestHeader(value = "Accept-Language", required = false) String acceptLanguage,
|
||||||
Authentication auth) {
|
Authentication auth) {
|
||||||
long wsId = workspaceId != null ? workspaceId : 1L;
|
long wsId = workspaceId != null ? workspaceId : 1L;
|
||||||
Long userId = resolveUserId(auth);
|
Long userId = resolveUserId(auth);
|
||||||
return R.ok(templateService.applyTemplate(id, wsId, userId));
|
return R.ok(templateService.applyTemplate(id, wsId, userId, acceptLanguage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long resolveUserId(Authentication auth) {
|
private Long resolveUserId(Authentication auth) {
|
||||||
|
|||||||
@ -63,24 +63,45 @@ public class TemplateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用模板创建 Agent 及其工作区文件
|
* Backwards-compatible overload that defaults to the template's English
|
||||||
*
|
* display strings (existing callers without locale context).
|
||||||
* @param templateId 模板 ID
|
|
||||||
* @param workspaceId 目标工作区 ID(来自 X-Workspace-Id header)
|
|
||||||
* @param creatorUserId 当前用户 ID(用于 RFC-077 创建者归属)
|
|
||||||
* @return 创建的 AgentEntity
|
|
||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public AgentEntity applyTemplate(String templateId, Long workspaceId, Long creatorUserId) {
|
public AgentEntity applyTemplate(String templateId, Long workspaceId, Long creatorUserId) {
|
||||||
|
return applyTemplate(templateId, workspaceId, creatorUserId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a template, picking name/description in the caller's preferred
|
||||||
|
* language so the resulting agent reads natively in their locale. Falls
|
||||||
|
* back to the template's primary (English) fields when the localized
|
||||||
|
* variant is missing or no locale was supplied.
|
||||||
|
*
|
||||||
|
* @param templateId template ID
|
||||||
|
* @param workspaceId target workspace ID (from X-Workspace-Id header)
|
||||||
|
* @param creatorUserId current user ID (creator attribution)
|
||||||
|
* @param acceptLanguage raw Accept-Language header; null/blank → English
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public AgentEntity applyTemplate(String templateId, Long workspaceId, Long creatorUserId, String acceptLanguage) {
|
||||||
TemplateDTO template = listTemplates().stream()
|
TemplateDTO template = listTemplates().stream()
|
||||||
.filter(t -> t.getId().equals(templateId))
|
.filter(t -> t.getId().equals(templateId))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow(() -> new MateClawException("err.agent.template_not_found", "模板不存在: " + templateId));
|
.orElseThrow(() -> new MateClawException("err.agent.template_not_found", "模板不存在: " + templateId));
|
||||||
|
|
||||||
// 1. 创建 Agent — RFC-077: 显式注入 workspaceId/creatorUserId,避免 DB 默认值兜底成 1(issue #26 Bug A)
|
boolean preferZh = isChineseLocale(acceptLanguage);
|
||||||
|
String displayName = preferZh && template.getNameZh() != null && !template.getNameZh().isBlank()
|
||||||
|
? template.getNameZh()
|
||||||
|
: template.getName();
|
||||||
|
String displayDesc = preferZh && template.getDescriptionZh() != null && !template.getDescriptionZh().isBlank()
|
||||||
|
? template.getDescriptionZh()
|
||||||
|
: template.getDescription();
|
||||||
|
|
||||||
|
// 1. Create the Agent. workspaceId/creatorUserId are passed in
|
||||||
|
// explicitly so the DB default does not silently fall back to 1.
|
||||||
AgentEntity agent = new AgentEntity();
|
AgentEntity agent = new AgentEntity();
|
||||||
agent.setName(template.getName());
|
agent.setName(displayName);
|
||||||
agent.setDescription(template.getDescription());
|
agent.setDescription(displayDesc);
|
||||||
agent.setAgentType(template.getAgentType());
|
agent.setAgentType(template.getAgentType());
|
||||||
agent.setIcon(template.getIcon());
|
agent.setIcon(template.getIcon());
|
||||||
agent.setTags(template.getTags());
|
agent.setTags(template.getTags());
|
||||||
@ -116,4 +137,15 @@ public class TemplateService {
|
|||||||
|
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the raw Accept-Language header best-matches a Chinese locale.
|
||||||
|
* Implementation is intentionally simple — we only need to disambiguate
|
||||||
|
* "Chinese vs not" for picking nameZh / descriptionZh.
|
||||||
|
*/
|
||||||
|
private boolean isChineseLocale(String acceptLanguage) {
|
||||||
|
if (acceptLanguage == null || acceptLanguage.isBlank()) return false;
|
||||||
|
String first = acceptLanguage.split(",")[0].trim().toLowerCase();
|
||||||
|
return first.startsWith("zh");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,21 +10,21 @@ MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_n
|
|||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
||||||
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
||||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- Default Agent: Task Planner (Plan-Execute mode)
|
-- Default Agent: Task Planner (Plan-Execute mode)
|
||||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
||||||
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
||||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- StateGraph ReAct Agent (StateGraph architecture)
|
-- StateGraph ReAct Agent (StateGraph architecture)
|
||||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
||||||
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
||||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:cpu', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- ==================== Local Model Providers (displayed first) ====================
|
-- ==================== Local Model Providers (displayed first) ====================
|
||||||
|
|
||||||
|
|||||||
@ -9,21 +9,21 @@ ON DUPLICATE KEY UPDATE username=VALUES(username), password=VALUES(password), ni
|
|||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
VALUES (1000000001, 'MateClaw Assistant', 'Default AI assistant with ReAct mode and tool calling', 'react',
|
||||||
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
'You are MateClaw, an intelligent AI assistant. You can help users answer questions, analyze data, and execute tasks. Please respond professionally and in a friendly manner.',
|
||||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- Default Agent: Task Planner (Plan-Execute mode)
|
-- Default Agent: Task Planner (Plan-Execute mode)
|
||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
VALUES (1000000002, 'Task Planner', 'Task planning assistant for complex multi-step tasks', 'plan_execute',
|
||||||
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
'You are a professional task planning and execution assistant. You excel at breaking complex goals into executable steps and completing them systematically.',
|
||||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- StateGraph ReAct Agent (StateGraph architecture)
|
-- StateGraph ReAct Agent (StateGraph architecture)
|
||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
VALUES (1000000003, 'StateGraph ReAct', 'StateGraph-based ReAct Agent with explicit reasoning loops and tool calling', 'react',
|
||||||
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
'You are an intelligent assistant based on the StateGraph architecture. You can use tools to help users solve problems. Please respond professionally and in a friendly manner.',
|
||||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:cpu', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- ==================== Local Model Providers (displayed first) ====================
|
-- ==================== Local Model Providers (displayed first) ====================
|
||||||
|
|||||||
@ -9,21 +9,21 @@ ON DUPLICATE KEY UPDATE username=VALUES(username), password=VALUES(password), ni
|
|||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
||||||
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
||||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
||||||
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
||||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
||||||
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
INSERT INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
||||||
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
||||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0)
|
NULL, 100, TRUE, 'pi:cpu', 'react,stategraph,tools', NOW(), NOW(), 0)
|
||||||
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
ON DUPLICATE KEY UPDATE name=VALUES(name), description=VALUES(description), agent_type=VALUES(agent_type), system_prompt=VALUES(system_prompt), model_name=VALUES(model_name), max_iterations=VALUES(max_iterations), enabled=VALUES(enabled), icon=VALUES(icon), tags=VALUES(tags), update_time=VALUES(update_time), deleted=VALUES(deleted);
|
||||||
|
|
||||||
-- ==================== 本地模型 Provider(优先展示) ====================
|
-- ==================== 本地模型 Provider(优先展示) ====================
|
||||||
|
|||||||
@ -10,21 +10,21 @@ MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_n
|
|||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
VALUES (1000000001, 'MateClaw Assistant', '默认 AI 助手,基于 ReAct 模式,支持工具调用', 'react',
|
||||||
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
'你是 MateClaw,一个智能 AI 助手。你可以帮助用户回答问题、分析数据、执行任务。请用中文回复,保持专业、友好的态度。',
|
||||||
NULL, 100, TRUE, '🤖', 'default,assistant', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:robot-face-happy', 'default,assistant', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
-- 默认 Agent:任务规划助手(Plan-Execute 模式)
|
||||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
VALUES (1000000002, 'Task Planner', '任务规划助手,适合复杂多步骤任务', 'plan_execute',
|
||||||
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
'你是一个专业的任务规划和执行助手。你擅长将复杂目标分解为可执行的步骤,并逐步完成。请用中文回复。',
|
||||||
NULL, 100, TRUE, '📋', 'planning,task', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:clipboard-note', 'planning,task', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
-- StateGraph ReAct Agent(支持 StateGraph 架构)
|
||||||
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
MERGE INTO mate_agent (id, name, description, agent_type, system_prompt, model_name, max_iterations, enabled, icon, tags, create_time, update_time, deleted)
|
||||||
KEY (id)
|
KEY (id)
|
||||||
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
VALUES (1000000003, 'StateGraph ReAct', '基于 StateGraph 的 ReAct Agent,支持显式推理循环和工具调用', 'react',
|
||||||
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
'你是基于 StateGraph 架构的智能助手。你可以使用工具来帮助用户解决问题。请用中文回复,保持专业、友好的态度。',
|
||||||
NULL, 100, TRUE, '🔄', 'react,stategraph,tools', NOW(), NOW(), 0);
|
NULL, 100, TRUE, 'pi:cpu', 'react,stategraph,tools', NOW(), NOW(), 0);
|
||||||
|
|
||||||
-- ==================== 本地模型 Provider(优先展示) ====================
|
-- ==================== 本地模型 Provider(优先展示) ====================
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
-- Convert the three default seed agents from emoji to pixelarticons icons.
|
||||||
|
-- The card UI now treats `pi:<name>` as an inline pixel-art SVG, so the
|
||||||
|
-- defaults need to match. Guarded with the original emoji so a user who
|
||||||
|
-- already customised the icon keeps their choice; non-seed (user-created)
|
||||||
|
-- agents are intentionally untouched.
|
||||||
|
UPDATE mate_agent SET icon = 'pi:robot-face-happy' WHERE id = 1000000001 AND icon = '🤖';
|
||||||
|
UPDATE mate_agent SET icon = 'pi:clipboard-note' WHERE id = 1000000002 AND icon = '📋';
|
||||||
|
UPDATE mate_agent SET icon = 'pi:cpu' WHERE id = 1000000003 AND icon = '🔄';
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
-- Convert the three default seed agents from emoji to pixelarticons icons.
|
||||||
|
-- The card UI now treats `pi:<name>` as an inline pixel-art SVG, so the
|
||||||
|
-- defaults need to match. Guarded with the original emoji so a user who
|
||||||
|
-- already customised the icon keeps their choice; non-seed (user-created)
|
||||||
|
-- agents are intentionally untouched.
|
||||||
|
UPDATE mate_agent SET icon = 'pi:robot-face-happy' WHERE id = 1000000001 AND icon = '🤖';
|
||||||
|
UPDATE mate_agent SET icon = 'pi:clipboard-note' WHERE id = 1000000002 AND icon = '📋';
|
||||||
|
UPDATE mate_agent SET icon = 'pi:cpu' WHERE id = 1000000003 AND icon = '🔄';
|
||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "代码审查员",
|
"nameZh": "代码审查员",
|
||||||
"description": "Expert code reviewer. Reads code, identifies issues, suggests improvements.",
|
"description": "Expert code reviewer. Reads code, identifies issues, suggests improvements.",
|
||||||
"descriptionZh": "代码审查专家。阅读代码、发现问题、提出改进建议。",
|
"descriptionZh": "代码审查专家。阅读代码、发现问题、提出改进建议。",
|
||||||
"icon": "🔍",
|
"icon": "pi:bug",
|
||||||
"agentType": "react",
|
"agentType": "react",
|
||||||
"tags": "code,review,developer",
|
"tags": "code,review,developer",
|
||||||
"maxIterations": 10,
|
"maxIterations": 10,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "客服助理",
|
"nameZh": "客服助理",
|
||||||
"description": "Empathy first, solution second — answers customers in 5 minutes without sounding like a script.",
|
"description": "Empathy first, solution second — answers customers in 5 minutes without sounding like a script.",
|
||||||
"descriptionZh": "先共情,再解决。5 分钟内给客户一个不像机器的答复。",
|
"descriptionZh": "先共情,再解决。5 分钟内给客户一个不像机器的答复。",
|
||||||
"icon": "💬",
|
"icon": "pi:headphone",
|
||||||
"agentType": "react",
|
"agentType": "react",
|
||||||
"tags": "support,customer,operations",
|
"tags": "support,customer,operations",
|
||||||
"maxIterations": 10,
|
"maxIterations": 10,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "数据分析师",
|
"nameZh": "数据分析师",
|
||||||
"description": "Turns business data into actionable insights — asks the right question first, writes SQL second.",
|
"description": "Turns business data into actionable insights — asks the right question first, writes SQL second.",
|
||||||
"descriptionZh": "把业务数据变成可执行的洞察。先问对问题,再写 SQL。",
|
"descriptionZh": "把业务数据变成可执行的洞察。先问对问题,再写 SQL。",
|
||||||
"icon": "📈",
|
"icon": "pi:chart-bar-big",
|
||||||
"agentType": "react",
|
"agentType": "react",
|
||||||
"tags": "data,analysis,sql",
|
"tags": "data,analysis,sql",
|
||||||
"maxIterations": 12,
|
"maxIterations": 12,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "通用助手",
|
"nameZh": "通用助手",
|
||||||
"description": "A versatile AI assistant for everyday tasks — search, write, analyze, and more.",
|
"description": "A versatile AI assistant for everyday tasks — search, write, analyze, and more.",
|
||||||
"descriptionZh": "通用 AI 助手,适合日常任务——搜索、写作、分析等。",
|
"descriptionZh": "通用 AI 助手,适合日常任务——搜索、写作、分析等。",
|
||||||
"icon": "🤖",
|
"icon": "pi:robot-face-happy",
|
||||||
"agentType": "react",
|
"agentType": "react",
|
||||||
"tags": "general,assistant,default",
|
"tags": "general,assistant,default",
|
||||||
"maxIterations": 10,
|
"maxIterations": 10,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "产品助理",
|
"nameZh": "产品助理",
|
||||||
"description": "Turns fuzzy requests into clear PRDs — always asks 'who is the user and what do they want' first.",
|
"description": "Turns fuzzy requests into clear PRDs — always asks 'who is the user and what do they want' first.",
|
||||||
"descriptionZh": "把模糊需求理清楚。永远先问\"用户是谁、他想干嘛\"。",
|
"descriptionZh": "把模糊需求理清楚。永远先问\"用户是谁、他想干嘛\"。",
|
||||||
"icon": "📝",
|
"icon": "pi:notes",
|
||||||
"agentType": "react",
|
"agentType": "react",
|
||||||
"tags": "product,prd,requirements",
|
"tags": "product,prd,requirements",
|
||||||
"maxIterations": 12,
|
"maxIterations": 12,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"nameZh": "研究分析师",
|
"nameZh": "研究分析师",
|
||||||
"description": "Breaks down complex research tasks into steps. Uses web search and Wiki for deep analysis.",
|
"description": "Breaks down complex research tasks into steps. Uses web search and Wiki for deep analysis.",
|
||||||
"descriptionZh": "将复杂研究任务分解为步骤。使用网络搜索和 Wiki 进行深度分析。",
|
"descriptionZh": "将复杂研究任务分解为步骤。使用网络搜索和 Wiki 进行深度分析。",
|
||||||
"icon": "📊",
|
"icon": "pi:book-open",
|
||||||
"agentType": "plan_execute",
|
"agentType": "plan_execute",
|
||||||
"tags": "research,analysis,planning",
|
"tags": "research,analysis,planning",
|
||||||
"maxIterations": 20,
|
"maxIterations": 20,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export const http = axios.create({
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 请求拦截器:注入 Token + Workspace ID
|
// 请求拦截器:注入 Token + Workspace ID + Accept-Language
|
||||||
http.interceptors.request.use((config) => {
|
http.interceptors.request.use((config) => {
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -17,6 +17,15 @@ http.interceptors.request.use((config) => {
|
|||||||
if (workspaceId) {
|
if (workspaceId) {
|
||||||
config.headers['X-Workspace-Id'] = workspaceId
|
config.headers['X-Workspace-Id'] = workspaceId
|
||||||
}
|
}
|
||||||
|
// Forward the user's UI locale so locale-sensitive endpoints (e.g.
|
||||||
|
// template apply) can pick the right display strings. Native browsers
|
||||||
|
// already send Accept-Language, but the user's chosen UI language may
|
||||||
|
// differ from the OS default — explicitly setting it keeps the two
|
||||||
|
// in sync.
|
||||||
|
const locale = localStorage.getItem('mateclaw_locale')
|
||||||
|
if (locale) {
|
||||||
|
config.headers['Accept-Language'] = locale
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -76,7 +76,10 @@ const sizeClass = computed(() => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: var(--mc-text-primary);
|
/* Inherit color so parents (e.g. an agent card avatar) can tint the
|
||||||
|
* pixelart SVG via `currentColor`. Without parental color, the icon
|
||||||
|
* still picks up text-primary through normal CSS inheritance. */
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pixelart SVGs ship with viewBox=0 0 24 24 + fill=currentColor — they
|
/* Pixelart SVGs ship with viewBox=0 0 24 24 + fill=currentColor — they
|
||||||
|
|||||||
@ -152,8 +152,8 @@ export default {
|
|||||||
ttsStop: 'Stop Reading',
|
ttsStop: 'Stop Reading',
|
||||||
conversations: 'Conversations',
|
conversations: 'Conversations',
|
||||||
newChat: 'New Chat',
|
newChat: 'New Chat',
|
||||||
loadingAgents: 'Loading agents...',
|
loadingAgents: 'Loading employees...',
|
||||||
selectAgent: 'Please select an agent',
|
selectAgent: 'Please select an employee',
|
||||||
noConversations: 'No conversations yet',
|
noConversations: 'No conversations yet',
|
||||||
startNewChat: 'Start a new chat above',
|
startNewChat: 'Start a new chat above',
|
||||||
messages: '{count} messages',
|
messages: '{count} messages',
|
||||||
@ -165,7 +165,7 @@ export default {
|
|||||||
noActiveModel: 'No active model. Go to Model Management to select an available model before chatting.',
|
noActiveModel: 'No active model. Go to Model Management to select an available model before chatting.',
|
||||||
providerNotReady: 'The active model belongs to {name}, but this provider is not fully configured. Please complete the API Key or Base URL in Model Management.',
|
providerNotReady: 'The active model belongs to {name}, but this provider is not fully configured. Please complete the API Key or Base URL in Model Management.',
|
||||||
noAvailableModel: 'No available model.',
|
noAvailableModel: 'No available model.',
|
||||||
loadAgentsFailed: 'Failed to load agents',
|
loadAgentsFailed: 'Failed to load employees',
|
||||||
loadModelFailed: 'Failed to load model state',
|
loadModelFailed: 'Failed to load model state',
|
||||||
loadConversationsFailed: 'Failed to load conversations',
|
loadConversationsFailed: 'Failed to load conversations',
|
||||||
loadMessagesFailed: 'Failed to load messages',
|
loadMessagesFailed: 'Failed to load messages',
|
||||||
@ -295,9 +295,9 @@ export default {
|
|||||||
activity: 'Activity',
|
activity: 'Activity',
|
||||||
acpEndpoints: 'ACP Endpoints',
|
acpEndpoints: 'ACP Endpoints',
|
||||||
settingsGroup: 'Settings',
|
settingsGroup: 'Settings',
|
||||||
agents: 'Agents',
|
agents: 'Digital Employees',
|
||||||
backstage: 'Backstage',
|
backstage: 'Backstage',
|
||||||
backstageTooltip: 'See what your agents are doing right now',
|
backstageTooltip: 'See what your employees are doing right now',
|
||||||
security: 'Security',
|
security: 'Security',
|
||||||
tokenUsage: 'Token Usage',
|
tokenUsage: 'Token Usage',
|
||||||
cronJobs: 'Cron Jobs',
|
cronJobs: 'Cron Jobs',
|
||||||
@ -873,20 +873,21 @@ export default {
|
|||||||
loadFileFailed: 'Failed to load file content',
|
loadFileFailed: 'Failed to load file content',
|
||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
title: 'Agent Management',
|
kicker: 'Employee Studio',
|
||||||
desc: 'Create, edit, and manage your AI agents',
|
title: 'Digital Employees',
|
||||||
newAgent: 'New Agent',
|
desc: 'Hire, train, and manage your digital employees',
|
||||||
|
newAgent: 'Hire Employee',
|
||||||
live: {
|
live: {
|
||||||
atWork: '{n} at work — see backstage',
|
atWork: '{n} at work — see backstage',
|
||||||
needsAttention: '{n} need attention — see backstage',
|
needsAttention: '{n} need attention — see backstage',
|
||||||
},
|
},
|
||||||
templates: {
|
templates: {
|
||||||
title: 'Choose a Template',
|
title: 'Choose a Role',
|
||||||
desc: 'Start with a pre-configured agent, or create from scratch.',
|
desc: 'Hire from a pre-written job description, or start from scratch.',
|
||||||
skip: 'Start from Scratch',
|
skip: 'Start from Scratch',
|
||||||
applied: 'Agent created from template',
|
applied: 'Employee onboarded',
|
||||||
},
|
},
|
||||||
search: 'Search agents...',
|
search: 'Search employees...',
|
||||||
tabs: {
|
tabs: {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
react: 'ReAct',
|
react: 'ReAct',
|
||||||
@ -913,11 +914,11 @@ export default {
|
|||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
disabled: 'Disabled',
|
disabled: 'Disabled',
|
||||||
},
|
},
|
||||||
emptyTitle: 'No Agents',
|
emptyTitle: 'No Employees Yet',
|
||||||
emptyDesc: 'Create your first agent to get started',
|
emptyDesc: 'Hire your first employee to get started',
|
||||||
modal: {
|
modal: {
|
||||||
newTitle: 'New Agent',
|
newTitle: 'New Employee',
|
||||||
editTitle: 'Edit Agent',
|
editTitle: 'Edit Employee',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
@ -938,7 +939,7 @@ export default {
|
|||||||
defaultThinkingLevel: 'Default Thinking Level',
|
defaultThinkingLevel: 'Default Thinking Level',
|
||||||
modelName: 'Model',
|
modelName: 'Model',
|
||||||
modelGlobalDefault: 'Use global default',
|
modelGlobalDefault: 'Use global default',
|
||||||
modelHint: 'Override the global default model for this Agent. Leave blank to follow Settings → Models.',
|
modelHint: 'Override the global default model for this employee. Leave blank to follow Settings → Models.',
|
||||||
tags: 'Tags',
|
tags: 'Tags',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
},
|
},
|
||||||
@ -962,7 +963,7 @@ export default {
|
|||||||
update: 'Save',
|
update: 'Save',
|
||||||
},
|
},
|
||||||
placeholders: {
|
placeholders: {
|
||||||
name: 'Agent name',
|
name: 'Employee name',
|
||||||
icon: 'Emoji or URL',
|
icon: 'Emoji or URL',
|
||||||
description: 'Brief description',
|
description: 'Brief description',
|
||||||
systemPrompt: 'You are a helpful AI assistant...',
|
systemPrompt: 'You are a helpful AI assistant...',
|
||||||
@ -975,13 +976,13 @@ export default {
|
|||||||
messages: {
|
messages: {
|
||||||
noDescription: 'No description',
|
noDescription: 'No description',
|
||||||
noTagline: 'No role and goal yet',
|
noTagline: 'No role and goal yet',
|
||||||
deleteConfirm: 'Are you sure you want to delete this agent? This cannot be undone.',
|
deleteConfirm: 'Let this employee go? This cannot be undone.',
|
||||||
loadFailed: 'Failed to load agents',
|
loadFailed: 'Failed to load employees',
|
||||||
saveFailed: 'Failed to save agent',
|
saveFailed: 'Failed to save employee',
|
||||||
saveSuccess: 'Agent saved',
|
saveSuccess: 'Employee saved',
|
||||||
deleteFailed: 'Failed to delete agent',
|
deleteFailed: 'Failed to delete employee',
|
||||||
deleteSuccess: 'Agent deleted',
|
deleteSuccess: 'Employee let go',
|
||||||
toggleFailed: 'Failed to toggle agent status',
|
toggleFailed: 'Failed to toggle status',
|
||||||
toggleSuccess: 'Status updated',
|
toggleSuccess: 'Status updated',
|
||||||
},
|
},
|
||||||
binding: {
|
binding: {
|
||||||
|
|||||||
@ -152,8 +152,8 @@ export default {
|
|||||||
ttsStop: '停止朗读',
|
ttsStop: '停止朗读',
|
||||||
conversations: '会话列表',
|
conversations: '会话列表',
|
||||||
newChat: '新对话',
|
newChat: '新对话',
|
||||||
loadingAgents: '加载 Agent 中...',
|
loadingAgents: '加载员工中...',
|
||||||
selectAgent: '请选择 Agent',
|
selectAgent: '请选择数字员工',
|
||||||
noConversations: '暂无会话',
|
noConversations: '暂无会话',
|
||||||
startNewChat: '开始新对话吧',
|
startNewChat: '开始新对话吧',
|
||||||
messages: '{count} 条消息',
|
messages: '{count} 条消息',
|
||||||
@ -165,7 +165,7 @@ export default {
|
|||||||
noActiveModel: '当前没有激活模型。先到模型管理中选择一个可用模型,再开始对话。',
|
noActiveModel: '当前没有激活模型。先到模型管理中选择一个可用模型,再开始对话。',
|
||||||
providerNotReady: '当前激活模型属于 {name},但这个提供商还没配置完成。请到模型管理中补全 API Key 或 Base URL。',
|
providerNotReady: '当前激活模型属于 {name},但这个提供商还没配置完成。请到模型管理中补全 API Key 或 Base URL。',
|
||||||
noAvailableModel: '当前没有可用模型。',
|
noAvailableModel: '当前没有可用模型。',
|
||||||
loadAgentsFailed: '加载 Agent 列表失败',
|
loadAgentsFailed: '加载员工列表失败',
|
||||||
loadModelFailed: '加载模型状态失败',
|
loadModelFailed: '加载模型状态失败',
|
||||||
loadConversationsFailed: '加载会话列表失败',
|
loadConversationsFailed: '加载会话列表失败',
|
||||||
loadMessagesFailed: '加载消息记录失败',
|
loadMessagesFailed: '加载消息记录失败',
|
||||||
@ -295,9 +295,9 @@ export default {
|
|||||||
activity: '活动记录',
|
activity: '活动记录',
|
||||||
acpEndpoints: 'ACP 端点',
|
acpEndpoints: 'ACP 端点',
|
||||||
settingsGroup: '设置',
|
settingsGroup: '设置',
|
||||||
agents: '智能体',
|
agents: '数字员工',
|
||||||
backstage: '后台',
|
backstage: '后台',
|
||||||
backstageTooltip: '看看你的智能体此刻在做什么',
|
backstageTooltip: '看看你的数字员工此刻在做什么',
|
||||||
security: '安全',
|
security: '安全',
|
||||||
tokenUsage: 'Token 统计',
|
tokenUsage: 'Token 统计',
|
||||||
cronJobs: '定时任务',
|
cronJobs: '定时任务',
|
||||||
@ -771,20 +771,21 @@ export default {
|
|||||||
loadFileFailed: '加载文件内容失败',
|
loadFileFailed: '加载文件内容失败',
|
||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
title: '智能体管理',
|
kicker: '员工工作室',
|
||||||
desc: '创建、编辑和管理你的 AI 智能体',
|
title: '数字员工',
|
||||||
newAgent: '新建智能体',
|
desc: '招募、培训和管理你的数字员工',
|
||||||
|
newAgent: '新员工',
|
||||||
live: {
|
live: {
|
||||||
atWork: '{n} 个在干活 · 看现场',
|
atWork: '{n} 个在干活 · 看现场',
|
||||||
needsAttention: '{n} 个需要看看 · 去现场',
|
needsAttention: '{n} 个需要看看 · 去现场',
|
||||||
},
|
},
|
||||||
templates: {
|
templates: {
|
||||||
title: '选择模板',
|
title: '选择岗位',
|
||||||
desc: '选择预配置的 Agent 模板快速开始,或从空白创建。',
|
desc: '从一份岗位说明书快速招聘,或从空白开始。',
|
||||||
skip: '从空白开始',
|
skip: '从空白开始',
|
||||||
applied: '已从模板创建 Agent',
|
applied: '员工已上岗',
|
||||||
},
|
},
|
||||||
search: '搜索智能体...',
|
search: '搜索员工...',
|
||||||
tabs: {
|
tabs: {
|
||||||
all: '全部',
|
all: '全部',
|
||||||
react: 'ReAct',
|
react: 'ReAct',
|
||||||
@ -811,11 +812,11 @@ export default {
|
|||||||
enabled: '已启用',
|
enabled: '已启用',
|
||||||
disabled: '已停用',
|
disabled: '已停用',
|
||||||
},
|
},
|
||||||
emptyTitle: '暂无智能体',
|
emptyTitle: '还没有员工',
|
||||||
emptyDesc: '创建你的第一个智能体开始使用',
|
emptyDesc: '招聘第一位员工开始',
|
||||||
modal: {
|
modal: {
|
||||||
newTitle: '新建智能体',
|
newTitle: '新员工',
|
||||||
editTitle: '编辑智能体',
|
editTitle: '编辑员工',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
name: '名称',
|
name: '名称',
|
||||||
@ -836,7 +837,7 @@ export default {
|
|||||||
defaultThinkingLevel: '默认思考深度',
|
defaultThinkingLevel: '默认思考深度',
|
||||||
modelName: '模型',
|
modelName: '模型',
|
||||||
modelGlobalDefault: '使用全局默认模型',
|
modelGlobalDefault: '使用全局默认模型',
|
||||||
modelHint: '为此 Agent 单独指定模型,留空则跟随「设置 → 模型」中的全局默认。',
|
modelHint: '为该员工单独指定模型,留空则跟随「设置 → 模型」中的全局默认。',
|
||||||
tags: '标签',
|
tags: '标签',
|
||||||
enabled: '启用',
|
enabled: '启用',
|
||||||
},
|
},
|
||||||
@ -860,7 +861,7 @@ export default {
|
|||||||
update: '保存',
|
update: '保存',
|
||||||
},
|
},
|
||||||
placeholders: {
|
placeholders: {
|
||||||
name: '智能体名称',
|
name: '员工名字',
|
||||||
icon: 'Emoji 或 URL',
|
icon: 'Emoji 或 URL',
|
||||||
description: '简短描述',
|
description: '简短描述',
|
||||||
systemPrompt: '你是一个有帮助的 AI 助手...',
|
systemPrompt: '你是一个有帮助的 AI 助手...',
|
||||||
@ -873,12 +874,12 @@ export default {
|
|||||||
messages: {
|
messages: {
|
||||||
noDescription: '暂无描述',
|
noDescription: '暂无描述',
|
||||||
noTagline: '还没填岗位与目标',
|
noTagline: '还没填岗位与目标',
|
||||||
deleteConfirm: '确认删除该智能体吗?删除后不可恢复。',
|
deleteConfirm: '确认让该员工离职吗?删除后不可恢复。',
|
||||||
loadFailed: '加载智能体列表失败',
|
loadFailed: '加载员工列表失败',
|
||||||
saveFailed: '保存智能体失败',
|
saveFailed: '保存员工失败',
|
||||||
saveSuccess: '智能体已保存',
|
saveSuccess: '员工已保存',
|
||||||
deleteFailed: '删除智能体失败',
|
deleteFailed: '删除员工失败',
|
||||||
deleteSuccess: '智能体已删除',
|
deleteSuccess: '员工已离职',
|
||||||
toggleFailed: '切换状态失败',
|
toggleFailed: '切换状态失败',
|
||||||
toggleSuccess: '状态已更新',
|
toggleSuccess: '状态已更新',
|
||||||
},
|
},
|
||||||
|
|||||||
66
mateclaw-ui/src/utils/agentIconColor.ts
Normal file
66
mateclaw-ui/src/utils/agentIconColor.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Per-role accent color for digital-employee icons.
|
||||||
|
*
|
||||||
|
* The pixelarticons SVGs render with `fill="currentColor"`, so wrapping
|
||||||
|
* the icon in an element with a `color: ...` style tints the glyph
|
||||||
|
* without touching the SVG itself. We only use this in agent contexts
|
||||||
|
* (cards, picker, chat header) so skills / tools keep their default
|
||||||
|
* neutral color.
|
||||||
|
*
|
||||||
|
* The palette is tuned to MateClaw's warm/earthy brand — every entry sits
|
||||||
|
* around 45-55% lightness with mid saturation, so colors stay distinct
|
||||||
|
* but live in the same room as the rust-orange primary, instead of
|
||||||
|
* competing with it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BRAND_FALLBACK = 'var(--mc-primary)'
|
||||||
|
|
||||||
|
/** icon name (without `pi:` prefix) → CSS color */
|
||||||
|
const ICON_COLOR_MAP: Record<string, string> = {
|
||||||
|
// Engineering / inspection — rosewood, sits next to the brand rust
|
||||||
|
'bug': 'hsl(8, 62%, 50%)',
|
||||||
|
'search': 'hsl(8, 62%, 50%)',
|
||||||
|
|
||||||
|
// Research / writing — sage green, calm, "library" feel
|
||||||
|
'book-open': 'hsl(155, 32%, 42%)',
|
||||||
|
'notes': 'hsl(285, 30%, 50%)',
|
||||||
|
'article': 'hsl(155, 32%, 42%)',
|
||||||
|
|
||||||
|
// Data / analytics — dusk blue
|
||||||
|
'chart-bar-big': 'hsl(212, 45%, 48%)',
|
||||||
|
'chart': 'hsl(212, 45%, 48%)',
|
||||||
|
'analytics': 'hsl(212, 45%, 48%)',
|
||||||
|
|
||||||
|
// Customer / support — terracotta, warm and approachable
|
||||||
|
'headphone': 'hsl(20, 68%, 50%)',
|
||||||
|
'message': 'hsl(20, 68%, 50%)',
|
||||||
|
'message-text': 'hsl(20, 68%, 50%)',
|
||||||
|
|
||||||
|
// General / friendly assistants — warm amber
|
||||||
|
'robot-face-happy': 'hsl(38, 72%, 50%)',
|
||||||
|
'robot-face': 'hsl(38, 72%, 50%)',
|
||||||
|
'robot': 'hsl(38, 72%, 50%)',
|
||||||
|
|
||||||
|
// System / infrastructure — slate teal
|
||||||
|
'cpu': 'hsl(195, 28%, 42%)',
|
||||||
|
'cloud': 'hsl(195, 28%, 42%)',
|
||||||
|
|
||||||
|
// Planning / task — indigo
|
||||||
|
'clipboard-note': 'hsl(232, 38%, 52%)',
|
||||||
|
'clipboard': 'hsl(232, 38%, 52%)',
|
||||||
|
'list-box': 'hsl(232, 38%, 52%)',
|
||||||
|
'checkbox-on': 'hsl(232, 38%, 52%)',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the accent color for a stored icon string. Returns the brand
|
||||||
|
* primary as a CSS variable for emoji / URL / unknown icons so callers
|
||||||
|
* can apply the color unconditionally.
|
||||||
|
*/
|
||||||
|
export function agentIconColor(iconValue: string | null | undefined): string {
|
||||||
|
if (!iconValue) return BRAND_FALLBACK
|
||||||
|
const v = iconValue.trim()
|
||||||
|
if (!v.startsWith('pi:')) return BRAND_FALLBACK
|
||||||
|
const name = v.slice(3)
|
||||||
|
return ICON_COLOR_MAP[name] || BRAND_FALLBACK
|
||||||
|
}
|
||||||
@ -54,9 +54,17 @@ export function isStructuredPrompt(systemPrompt: string | null | undefined): boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a systemPrompt into role/goal/backstory/extra. If no section markers
|
* Parse a systemPrompt into role/goal/backstory/extra. The parse contract is
|
||||||
* are present, the entire prompt becomes `extra` so legacy agents keep their
|
* lossless: every byte of the original prompt ends up in one of the four
|
||||||
* prompt verbatim.
|
* fields, so a parse → serialize round-trip never silently drops content.
|
||||||
|
*
|
||||||
|
* - If no recognized section markers exist, the whole prompt becomes `extra`.
|
||||||
|
* - If recognized markers exist:
|
||||||
|
* - Lines before the first heading (preamble) prepend to `extra`.
|
||||||
|
* - Unknown `## Heading` blocks (e.g. `## Notes`, `## Examples`) keep
|
||||||
|
* their heading line and content and append to `extra` verbatim.
|
||||||
|
* - Multiple headings of the same kind concatenate (last writer wins on
|
||||||
|
* intent, but content is preserved).
|
||||||
*/
|
*/
|
||||||
export function parsePrompt(systemPrompt: string | null | undefined): AgentPromptProfile {
|
export function parsePrompt(systemPrompt: string | null | undefined): AgentPromptProfile {
|
||||||
const profile = emptyProfile()
|
const profile = emptyProfile()
|
||||||
@ -68,29 +76,57 @@ export function parsePrompt(systemPrompt: string | null | undefined): AgentPromp
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lines = systemPrompt.split(/\r?\n/)
|
const lines = systemPrompt.split(/\r?\n/)
|
||||||
let current: SectionKey | null = null
|
|
||||||
const buffers: Record<SectionKey, string[]> = {
|
const buffers: Record<SectionKey, string[]> = {
|
||||||
role: [],
|
role: [],
|
||||||
goal: [],
|
goal: [],
|
||||||
backstory: [],
|
backstory: [],
|
||||||
extra: [],
|
extra: [],
|
||||||
}
|
}
|
||||||
|
const preamble: string[] = []
|
||||||
|
// Unknown sections are captured as { heading, body } pairs so we can
|
||||||
|
// re-emit them verbatim (including their `## Heading` line) into `extra`.
|
||||||
|
const unknownSections: { heading: string; body: string[] }[] = []
|
||||||
|
|
||||||
|
type Bucket = { kind: 'preamble' } | { kind: 'known'; key: SectionKey } | { kind: 'unknown'; index: number }
|
||||||
|
let bucket: Bucket = { kind: 'preamble' }
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const m = line.match(SECTION_HEADING_REGEX)
|
const m = line.match(SECTION_HEADING_REGEX)
|
||||||
if (m) {
|
if (m) {
|
||||||
const key = HEADING_TO_KEY[m[1].trim().toLowerCase()]
|
const headingText = m[1].trim()
|
||||||
|
const key = HEADING_TO_KEY[headingText.toLowerCase()]
|
||||||
if (key) {
|
if (key) {
|
||||||
current = key
|
bucket = { kind: 'known', key }
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Unknown heading — start a new captured block, keep the original
|
||||||
|
// heading text so we can round-trip it back.
|
||||||
|
const idx = unknownSections.length
|
||||||
|
unknownSections.push({ heading: line, body: [] })
|
||||||
|
bucket = { kind: 'unknown', index: idx }
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if (current) buffers[current].push(line)
|
if (bucket.kind === 'preamble') preamble.push(line)
|
||||||
|
else if (bucket.kind === 'known') buffers[bucket.key].push(line)
|
||||||
|
else unknownSections[bucket.index].body.push(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of SECTION_KEYS) {
|
// Compose `extra` lossless: preamble first, then the recognized "extra"
|
||||||
profile[key] = buffers[key].join('\n').trim()
|
// section's content, then any unknown sections (keeping their headings).
|
||||||
|
const extraParts: string[] = []
|
||||||
|
const trimmedPreamble = preamble.join('\n').trim()
|
||||||
|
if (trimmedPreamble) extraParts.push(trimmedPreamble)
|
||||||
|
const trimmedExtra = buffers.extra.join('\n').trim()
|
||||||
|
if (trimmedExtra) extraParts.push(trimmedExtra)
|
||||||
|
for (const sec of unknownSections) {
|
||||||
|
const body = sec.body.join('\n').trimEnd()
|
||||||
|
extraParts.push(body ? `${sec.heading}\n${body}` : sec.heading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profile.role = buffers.role.join('\n').trim()
|
||||||
|
profile.goal = buffers.goal.join('\n').trim()
|
||||||
|
profile.backstory = buffers.backstory.join('\n').trim()
|
||||||
|
profile.extra = extraParts.join('\n\n').trim()
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="mc-page-inner agents-page">
|
<div class="mc-page-inner agents-page">
|
||||||
<div class="mc-page-header">
|
<div class="mc-page-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="mc-page-kicker">Agent Studio</div>
|
<div class="mc-page-kicker">{{ t('agents.kicker') }}</div>
|
||||||
<h1 class="mc-page-title">{{ t('agents.title') }}</h1>
|
<h1 class="mc-page-title">{{ t('agents.title') }}</h1>
|
||||||
<p class="mc-page-desc">{{ t('agents.desc') }}</p>
|
<p class="mc-page-desc">{{ t('agents.desc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -67,7 +67,11 @@
|
|||||||
the card readable at a glance.
|
the card readable at a glance.
|
||||||
-->
|
-->
|
||||||
<div class="agent-card__top">
|
<div class="agent-card__top">
|
||||||
<span class="agent-card__avatar" :class="{ 'agent-card__avatar--off': !agent.enabled }">
|
<span
|
||||||
|
class="agent-card__avatar"
|
||||||
|
:class="{ 'agent-card__avatar--off': !agent.enabled }"
|
||||||
|
:style="{ color: agentIconColor(agent.icon) }"
|
||||||
|
>
|
||||||
<SkillIcon :value="agent.icon" :size="40" :fallback="'🧑💼'" />
|
<SkillIcon :value="agent.icon" :size="40" :fallback="'🧑💼'" />
|
||||||
</span>
|
</span>
|
||||||
<div class="agent-card__identity">
|
<div class="agent-card__identity">
|
||||||
@ -144,7 +148,9 @@
|
|||||||
:class="{ applying: applyingTemplate }"
|
:class="{ applying: applyingTemplate }"
|
||||||
@click="!applyingTemplate && applyTemplate(tpl.id)"
|
@click="!applyingTemplate && applyTemplate(tpl.id)"
|
||||||
>
|
>
|
||||||
<div class="template-icon">{{ tpl.icon }}</div>
|
<div class="template-icon" :style="{ color: agentIconColor(tpl.icon) }">
|
||||||
|
<SkillIcon :value="tpl.icon" :size="28" :fallback="'🧑💼'" />
|
||||||
|
</div>
|
||||||
<div class="template-info">
|
<div class="template-info">
|
||||||
<h4 class="template-name">{{ $i18n.locale === 'zh-CN' && tpl.nameZh ? tpl.nameZh : tpl.name }}</h4>
|
<h4 class="template-name">{{ $i18n.locale === 'zh-CN' && tpl.nameZh ? tpl.nameZh : tpl.name }}</h4>
|
||||||
<p class="template-detail">{{ $i18n.locale === 'zh-CN' && tpl.descriptionZh ? tpl.descriptionZh : tpl.description }}</p>
|
<p class="template-detail">{{ $i18n.locale === 'zh-CN' && tpl.descriptionZh ? tpl.descriptionZh : tpl.description }}</p>
|
||||||
@ -432,6 +438,7 @@ import {
|
|||||||
TAGLINE_CJK_BUDGET,
|
TAGLINE_CJK_BUDGET,
|
||||||
type AgentPromptProfile,
|
type AgentPromptProfile,
|
||||||
} from '@/utils/agentPromptProfile'
|
} from '@/utils/agentPromptProfile'
|
||||||
|
import { agentIconColor } from '@/utils/agentIconColor'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<div class="agent-selector">
|
<div class="agent-selector">
|
||||||
<button class="agent-select-trigger" @click="agentDropdownOpen = !agentDropdownOpen" :title="`${$t('chat.selectAgent')} (⌘K)`">
|
<button class="agent-select-trigger" @click="agentDropdownOpen = !agentDropdownOpen" :title="`${$t('chat.selectAgent')} (⌘K)`">
|
||||||
<span class="agent-select-trigger__icon"><SkillIcon :value="currentAgent?.icon" :size="24" :fallback="'🤖'" /></span>
|
<span class="agent-select-trigger__icon" :style="{ color: agentIconColor(currentAgent?.icon) }"><SkillIcon :value="currentAgent?.icon" :size="24" :fallback="'🤖'" /></span>
|
||||||
<span v-if="!convPanelCollapsed || isMobile" class="agent-select-trigger__name">{{ currentAgent?.name || $t('chat.selectAgent') }}</span>
|
<span v-if="!convPanelCollapsed || isMobile" class="agent-select-trigger__name">{{ currentAgent?.name || $t('chat.selectAgent') }}</span>
|
||||||
<svg v-if="!convPanelCollapsed || isMobile" class="agent-select-trigger__arrow" :class="{ open: agentDropdownOpen }" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
<svg v-if="!convPanelCollapsed || isMobile" class="agent-select-trigger__arrow" :class="{ open: agentDropdownOpen }" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@ -42,10 +42,10 @@
|
|||||||
:class="{ active: String(agent.id) === String(selectedAgentId) }"
|
:class="{ active: String(agent.id) === String(selectedAgentId) }"
|
||||||
@click="selectAgent(agent)"
|
@click="selectAgent(agent)"
|
||||||
>
|
>
|
||||||
<span class="agent-dropdown-item__icon"><SkillIcon :value="agent.icon" :size="18" :fallback="'🤖'" /></span>
|
<span class="agent-dropdown-item__icon" :style="{ color: agentIconColor(agent.icon) }"><SkillIcon :value="agent.icon" :size="18" :fallback="'🤖'" /></span>
|
||||||
<div class="agent-dropdown-item__info">
|
<div class="agent-dropdown-item__info">
|
||||||
<span class="agent-dropdown-item__name">{{ agent.name }}</span>
|
<span class="agent-dropdown-item__name">{{ agent.name }}</span>
|
||||||
<span class="agent-dropdown-item__desc">{{ agent.description || agent.agentType }}</span>
|
<span class="agent-dropdown-item__desc">{{ agentTagline(agent) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="String(agent.id) === String(selectedAgentId)" class="agent-dropdown-item__check">
|
<span v-if="String(agent.id) === String(selectedAgentId)" class="agent-dropdown-item__check">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
@ -153,10 +153,21 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="chat-stage-copy" v-if="currentAgent">
|
<div class="chat-stage-copy" v-if="currentAgent">
|
||||||
<div class="chat-stage-kicker">{{ $t('nav.chat') }}</div>
|
<div class="chat-stage-kicker">{{ $t('nav.chat') }}</div>
|
||||||
<div class="agent-badge" :title="currentAgent.name">
|
<!--
|
||||||
<span class="agent-badge-icon"><SkillIcon :value="currentAgent.icon" :size="22" :fallback="'🤖'" /></span>
|
Header reads as "who is this employee" — name + tagline.
|
||||||
<span class="agent-badge-name">{{ currentAgent.name }}</span>
|
The runtime mode (ReAct / Plan-Execute) is technical jargon
|
||||||
<span class="agent-badge-type">{{ currentAgent.agentType === 'react' ? 'ReAct' : 'Plan-Execute' }}</span>
|
to end users and lives in the badge tooltip instead, so the
|
||||||
|
header doesn't get polluted.
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
class="agent-badge"
|
||||||
|
:title="`${currentAgent.name}${currentAgentRuntimeMode ? ' · ' + currentAgentRuntimeMode : ''}`"
|
||||||
|
>
|
||||||
|
<span class="agent-badge-icon" :style="{ color: agentIconColor(currentAgent.icon) }"><SkillIcon :value="currentAgent.icon" :size="22" :fallback="'🤖'" /></span>
|
||||||
|
<div class="agent-badge-text">
|
||||||
|
<span class="agent-badge-name">{{ currentAgent.name }}</span>
|
||||||
|
<span v-if="currentAgentTagline" class="agent-badge-tagline">{{ currentAgentTagline }}</span>
|
||||||
|
</div>
|
||||||
<span class="status-dot" :class="connectionStatusClass" :title="connectionStatusLabel"></span>
|
<span class="status-dot" :class="connectionStatusClass" :title="connectionStatusLabel"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -314,6 +325,8 @@ import type { Conversation, Agent, ModelConfig, ProviderInfo, ActiveModelsInfo,
|
|||||||
// 导入组件化组件
|
// 导入组件化组件
|
||||||
import MessageList from '@/components/chat/MessageList.vue'
|
import MessageList from '@/components/chat/MessageList.vue'
|
||||||
import SkillIcon from '@/components/common/SkillIcon.vue'
|
import SkillIcon from '@/components/common/SkillIcon.vue'
|
||||||
|
import { parsePrompt, deriveTagline } from '@/utils/agentPromptProfile'
|
||||||
|
import { agentIconColor } from '@/utils/agentIconColor'
|
||||||
import ChatInput from '@/components/chat/ChatInput.vue'
|
import ChatInput from '@/components/chat/ChatInput.vue'
|
||||||
import StreamLoadingBar from '@/components/chat/StreamLoadingBar.vue'
|
import StreamLoadingBar from '@/components/chat/StreamLoadingBar.vue'
|
||||||
import TalkMode from '@/components/chat/TalkMode.vue'
|
import TalkMode from '@/components/chat/TalkMode.vue'
|
||||||
@ -663,6 +676,25 @@ const connectionStatusLabel = computed(() => {
|
|||||||
// ============ 计算属性 ============
|
// ============ 计算属性 ============
|
||||||
const currentAgent = computed(() => agents.value.find(a => String(a.id) === String(selectedAgentId.value)))
|
const currentAgent = computed(() => agents.value.find(a => String(a.id) === String(selectedAgentId.value)))
|
||||||
|
|
||||||
|
/** Tagline derived from the agent's role/goal — same source of truth as the
|
||||||
|
* Agents page card, so the chat header reads the employee identically.
|
||||||
|
* Falls back to the agent's description when no triad is set. */
|
||||||
|
function agentTagline(agent: Agent): string {
|
||||||
|
const profile = parsePrompt(agent.systemPrompt)
|
||||||
|
return deriveTagline(profile, agent.description) || (agent.description || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentAgentTagline = computed(() =>
|
||||||
|
currentAgent.value ? agentTagline(currentAgent.value) : '',
|
||||||
|
)
|
||||||
|
/** Human label for the agent's runtime mode — surfaces in the badge tooltip
|
||||||
|
* only, never in the visible header. */
|
||||||
|
const currentAgentRuntimeMode = computed(() => {
|
||||||
|
const a = currentAgent.value
|
||||||
|
if (!a) return ''
|
||||||
|
return a.agentType === 'react' ? t('agents.types.react') : t('agents.types.planExecute')
|
||||||
|
})
|
||||||
|
|
||||||
// 按日期分组的会话列表
|
// 按日期分组的会话列表
|
||||||
// Per-conversation last-viewed timestamp store (localStorage-backed, MVP).
|
// Per-conversation last-viewed timestamp store (localStorage-backed, MVP).
|
||||||
// Keyed by conversationId. Updated when user opens a conversation; read by
|
// Keyed by conversationId. Updated when user opens a conversation; read by
|
||||||
@ -2334,6 +2366,13 @@ function handleCodeCopy(e: MouseEvent) {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-badge-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.agent-badge-name {
|
.agent-badge-name {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -2341,14 +2380,18 @@ function handleCodeCopy(e: MouseEvent) {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge-type {
|
/* The tagline answers "what does this employee do" — runtime mode lives in
|
||||||
|
the badge tooltip, not on screen, so we don't compete for attention. */
|
||||||
|
.agent-badge-tagline {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--mc-primary-light);
|
color: var(--mc-text-secondary);
|
||||||
background: var(--mc-bg-elevated);
|
white-space: nowrap;
|
||||||
padding: 1px 6px;
|
overflow: hidden;
|
||||||
border-radius: 10px;
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot {
|
.status-dot {
|
||||||
@ -2561,8 +2604,7 @@ function handleCodeCopy(e: MouseEvent) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge-name,
|
.agent-badge-text {
|
||||||
.agent-badge-type {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user