mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-02 02:57:51 +00:00
refactor(ui): implement agent avatar resolution and logo fallback in agent rendering
This commit is contained in:
@@ -138,6 +138,30 @@ export function normalizeAgentLabel(agent: {
|
|||||||
return agent.name?.trim() || agent.identity?.name?.trim() || agent.id;
|
return agent.name?.trim() || agent.identity?.name?.trim() || agent.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AVATAR_URL_RE = /^(https?:\/\/|data:image\/|\/)/i;
|
||||||
|
|
||||||
|
export function resolveAgentAvatarUrl(
|
||||||
|
agent: { identity?: { avatar?: string; avatarUrl?: string } },
|
||||||
|
agentIdentity?: AgentIdentityResult | null,
|
||||||
|
): string | null {
|
||||||
|
const url =
|
||||||
|
agentIdentity?.avatar?.trim() ??
|
||||||
|
agent.identity?.avatarUrl?.trim() ??
|
||||||
|
agent.identity?.avatar?.trim();
|
||||||
|
if (!url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (AVATAR_URL_RE.test(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function agentLogoUrl(basePath: string): string {
|
||||||
|
const base = basePath?.trim() ? basePath.replace(/\/$/, "") : "";
|
||||||
|
return base ? `${base}/favicon.svg` : "/favicon.svg";
|
||||||
|
}
|
||||||
|
|
||||||
function isLikelyEmoji(value: string) {
|
function isLikelyEmoji(value: string) {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
@@ -229,7 +253,7 @@ export type AgentContext = {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
model: string;
|
model: string;
|
||||||
identityName: string;
|
identityName: string;
|
||||||
identityEmoji: string;
|
identityAvatar: string;
|
||||||
skillsLabel: string;
|
skillsLabel: string;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
};
|
};
|
||||||
@@ -255,14 +279,14 @@ export function buildAgentContext(
|
|||||||
agent.name?.trim() ||
|
agent.name?.trim() ||
|
||||||
config.entry?.name ||
|
config.entry?.name ||
|
||||||
agent.id;
|
agent.id;
|
||||||
const identityEmoji = resolveAgentEmoji(agent, agentIdentity) || "-";
|
const identityAvatar = resolveAgentAvatarUrl(agent, agentIdentity) ? "custom" : "—";
|
||||||
const skillFilter = Array.isArray(config.entry?.skills) ? config.entry?.skills : null;
|
const skillFilter = Array.isArray(config.entry?.skills) ? config.entry?.skills : null;
|
||||||
const skillCount = skillFilter?.length ?? null;
|
const skillCount = skillFilter?.length ?? null;
|
||||||
return {
|
return {
|
||||||
workspace,
|
workspace,
|
||||||
model: modelLabel,
|
model: modelLabel,
|
||||||
identityName,
|
identityName,
|
||||||
identityEmoji,
|
identityAvatar,
|
||||||
skillsLabel: skillFilter ? `${skillCount} selected` : "all skills",
|
skillsLabel: skillFilter ? `${skillCount} selected` : "all skills",
|
||||||
isDefault: Boolean(defaultId && agent.id === defaultId),
|
isDefault: Boolean(defaultId && agent.id === defaultId),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ import { renderAgentTools, renderAgentSkills } from "./agents-panels-tools-skill
|
|||||||
import {
|
import {
|
||||||
agentAvatarHue,
|
agentAvatarHue,
|
||||||
agentBadgeText,
|
agentBadgeText,
|
||||||
|
agentLogoUrl,
|
||||||
buildAgentContext,
|
buildAgentContext,
|
||||||
normalizeAgentLabel,
|
normalizeAgentLabel,
|
||||||
resolveAgentEmoji,
|
resolveAgentAvatarUrl,
|
||||||
} from "./agents-utils.ts";
|
} from "./agents-utils.ts";
|
||||||
|
|
||||||
export type AgentsPanel = "overview" | "files" | "tools" | "skills" | "channels" | "cron";
|
export type AgentsPanel = "overview" | "files" | "tools" | "skills" | "channels" | "cron";
|
||||||
@@ -65,6 +66,7 @@ export type AgentSkillsState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AgentsProps = {
|
export type AgentsProps = {
|
||||||
|
basePath: string;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
agentsList: AgentsListResult | null;
|
agentsList: AgentsListResult | null;
|
||||||
@@ -174,8 +176,12 @@ export function renderAgents(props: AgentsProps) {
|
|||||||
`
|
`
|
||||||
: filteredAgents.map((agent) => {
|
: filteredAgents.map((agent) => {
|
||||||
const badge = agentBadgeText(agent.id, defaultId);
|
const badge = agentBadgeText(agent.id, defaultId);
|
||||||
const emoji = resolveAgentEmoji(agent, props.agentIdentityById[agent.id] ?? null);
|
const avatarUrl = resolveAgentAvatarUrl(
|
||||||
|
agent,
|
||||||
|
props.agentIdentityById[agent.id] ?? null,
|
||||||
|
);
|
||||||
const hue = agentAvatarHue(agent.id);
|
const hue = agentAvatarHue(agent.id);
|
||||||
|
const logoUrl = agentLogoUrl(props.basePath);
|
||||||
return html`
|
return html`
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -183,7 +189,11 @@ export function renderAgents(props: AgentsProps) {
|
|||||||
@click=${() => props.onSelectAgent(agent.id)}
|
@click=${() => props.onSelectAgent(agent.id)}
|
||||||
>
|
>
|
||||||
<div class="agent-avatar" style="--agent-hue: ${hue}">
|
<div class="agent-avatar" style="--agent-hue: ${hue}">
|
||||||
${emoji || normalizeAgentLabel(agent).slice(0, 1)}
|
${
|
||||||
|
avatarUrl
|
||||||
|
? html`<img src=${avatarUrl} alt="" class="agent-avatar__img" />`
|
||||||
|
: html`<img src=${logoUrl} alt="" class="agent-avatar__img agent-avatar__logo" />`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="agent-info">
|
<div class="agent-info">
|
||||||
<div class="agent-title">${normalizeAgentLabel(agent)}</div>
|
<div class="agent-title">${normalizeAgentLabel(agent)}</div>
|
||||||
@@ -211,6 +221,7 @@ export function renderAgents(props: AgentsProps) {
|
|||||||
defaultId,
|
defaultId,
|
||||||
props.agentIdentityById[selectedAgent.id] ?? null,
|
props.agentIdentityById[selectedAgent.id] ?? null,
|
||||||
props.onSetDefault,
|
props.onSetDefault,
|
||||||
|
props.basePath,
|
||||||
)}
|
)}
|
||||||
${renderAgentTabs(props.activePanel, (panel) => props.onSelectPanel(panel), tabCounts)}
|
${renderAgentTabs(props.activePanel, (panel) => props.onSelectPanel(panel), tabCounts)}
|
||||||
${
|
${
|
||||||
@@ -341,11 +352,12 @@ function renderAgentHeader(
|
|||||||
defaultId: string | null,
|
defaultId: string | null,
|
||||||
agentIdentity: AgentIdentityResult | null,
|
agentIdentity: AgentIdentityResult | null,
|
||||||
onSetDefault: (agentId: string) => void,
|
onSetDefault: (agentId: string) => void,
|
||||||
|
basePath: string,
|
||||||
) {
|
) {
|
||||||
const badge = agentBadgeText(agent.id, defaultId);
|
const badge = agentBadgeText(agent.id, defaultId);
|
||||||
const displayName = normalizeAgentLabel(agent);
|
const displayName = normalizeAgentLabel(agent);
|
||||||
const subtitle = agent.identity?.theme?.trim() || "Agent workspace and routing.";
|
const subtitle = agent.identity?.theme?.trim() || "Agent workspace and routing.";
|
||||||
const emoji = resolveAgentEmoji(agent, agentIdentity);
|
const avatarUrl = resolveAgentAvatarUrl(agent, agentIdentity);
|
||||||
const hue = agentAvatarHue(agent.id);
|
const hue = agentAvatarHue(agent.id);
|
||||||
const isDefault = Boolean(defaultId && agent.id === defaultId);
|
const isDefault = Boolean(defaultId && agent.id === defaultId);
|
||||||
|
|
||||||
@@ -354,11 +366,16 @@ function renderAgentHeader(
|
|||||||
actionsMenuOpen = false;
|
actionsMenuOpen = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const logoUrl = agentLogoUrl(basePath);
|
||||||
return html`
|
return html`
|
||||||
<section class="card agent-header">
|
<section class="card agent-header">
|
||||||
<div class="agent-header-main">
|
<div class="agent-header-main">
|
||||||
<div class="agent-avatar agent-avatar--lg" style="--agent-hue: ${hue}">
|
<div class="agent-avatar agent-avatar--lg" style="--agent-hue: ${hue}">
|
||||||
${emoji || displayName.slice(0, 1)}
|
${
|
||||||
|
avatarUrl
|
||||||
|
? html`<img src=${avatarUrl} alt="" class="agent-avatar__img" />`
|
||||||
|
: html`<img src=${logoUrl} alt="" class="agent-avatar__img agent-avatar__logo" />`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="card-title">${displayName}</div>
|
<div class="card-title">${displayName}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user