From 932fe54c16975db341af73b83eebbbc795bdaedc Mon Sep 17 00:00:00 2001 From: Pine Date: Fri, 4 Sep 2026 17:11:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(chat):=20=E5=8A=A9=E6=89=8B=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E6=98=BE=E7=A4=BA=E5=BD=93=E5=89=8D=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E4=BD=93=E5=90=8D=E7=A7=B0/=E5=A4=B4=E5=83=8F=EF=BC=8C?= =?UTF-8?q?=E6=97=A0=E5=88=99=E5=9B=9E=E9=80=80=E4=BA=91=E8=B6=85=E6=9C=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:聊天区助手名称/头像沿用上游硬编码 nick='QwenPaw'、 avatar='/qwenpaw.png'。前者把当前智能体(如'小园')显示成了'QwenPaw'; 后者资源未随迁移携带,dev 下实际返回 SPA index.html(头像渲染破损)。 修复: - 前端 Chat 页:welcome.nick/avatar 改为解析当前智能体——名称取 selectedAgent.name,头像取 selectedAgent.avatar;智能体无名称/头像时 回退云超服品牌 BRAND.name / BRAND.assistantAvatar(/pineagents-icon.svg)。 - 支持智能体头像字段(端到端):后端 AgentProfileConfig.avatar、 AgentSummary.avatar、agents list/create/update 透传、agent_sync IDENTITY_FIELDS 加 avatar(云超服身份同步);前端 AgentSummary/ AgentProfileConfig/CreateAgentRequest 类型补 avatar。 - branding.ts 新增 assistantAvatar 品牌头像常量(替代缺失的 /qwenpaw.png)。 验证:后端重启后 /agents 返回 avatar 字段;/pineagents-icon.svg 200 image/svg+xml(原 /qwenpaw.png 实为 SPA fallback 破损);前端 tsc 0 错; Chat/agents/constants 32 文件 322 测试全过。 --- console/src/api/types/agents.ts | 4 ++++ console/src/constants/branding.ts | 3 +++ console/src/pages/Chat/index.tsx | 10 ++++++++-- src/pineagents/app/agent_sync.py | 4 +++- src/pineagents/app/routers/agents.py | 11 +++++++++-- src/pineagents/config/config.py | 8 ++++++++ 6 files changed, 35 insertions(+), 5 deletions(-) diff --git a/console/src/api/types/agents.ts b/console/src/api/types/agents.ts index d4bdff4..36f39eb 100644 --- a/console/src/api/types/agents.ts +++ b/console/src/api/types/agents.ts @@ -17,6 +17,8 @@ export type AgentStartupStatus = export interface AgentSummary { id: string; name: string; + /** 智能体头像(URL 路径/数据 URL);空串表示无,由调用方回退云超服品牌头像。 */ + avatar?: string; description: string; workspace_dir: string; enabled: boolean; @@ -103,6 +105,7 @@ export interface MemoryGraphSnapshot { export interface AgentProfileConfig { id: string; name: string; + avatar?: string; description?: string; workspace_dir?: string; backend?: AgentBackend; @@ -145,6 +148,7 @@ export interface AgentModelSettingsPatch { export interface CreateAgentRequest { id?: string; name: string; + avatar?: string; description?: string; workspace_dir?: string; language?: string; diff --git a/console/src/constants/branding.ts b/console/src/constants/branding.ts index 8da1fcd..212bd42 100644 --- a/console/src/constants/branding.ts +++ b/console/src/constants/branding.ts @@ -5,6 +5,9 @@ export const BRAND = { name: "云超服OPC", // 产品名(对外统一品牌;上游:QwenPaw→云超服) fullName: "云南省超级个体服务平台", // 品牌全称(正式场合使用) + // 云超服品牌头像:智能体无头像时聊天等场景回退到它 + // (上游缺省是 /qwenpaw.png,本地未随迁移携带该资源) + assistantAvatar: "/pineagents-icon.svg", cli: "pineagents", // CLI 命令(上游:qwenpaw/copaw) packageName: "pineagents", // 后端包名(已彻底改名) envPrefix: "PINEAGENTS_", // 环境变量主前缀 diff --git a/console/src/pages/Chat/index.tsx b/console/src/pages/Chat/index.tsx index ee4ef09..36cb91b 100644 --- a/console/src/pages/Chat/index.tsx +++ b/console/src/pages/Chat/index.tsx @@ -38,6 +38,7 @@ import { chatApi } from "../../api/modules/chat"; import { agentApi } from "../../api/modules/agent"; import { skillApi } from "../../api/modules/skill"; import { getApiUrl } from "../../api/config"; +import { BRAND } from "../../constants/branding"; import { buildAuthHeaders } from "../../api/authHeaders"; import { providerApi } from "../../api/modules/provider"; import type { ProviderInfo, ModelInfo, SkillSpec } from "../../api/types"; @@ -1279,6 +1280,11 @@ export default function ChatPage() { }> >([]); const selectedAgentInfo = agents.find((agent) => agent.id === selectedAgent); + // 聊天助手身份:优先当前智能体的名称/头像;智能体无名称/头像时 + // 回退云超服品牌身份(BRAND.name / BRAND.assistantAvatar)。 + const chatAssistantNick = selectedAgentInfo?.name?.trim() || BRAND.name; + const chatAssistantAvatar = + selectedAgentInfo?.avatar?.trim() || BRAND.assistantAvatar; const selectedAgentBackend = selectedAgentInfo?.backend ?? "qwenpaw"; const backendCapabilities = selectedAgentInfo?.backend_capabilities; const usesQwenPawBackend = requiresQwenPawModel(selectedAgentBackend); @@ -3152,8 +3158,8 @@ export default function ChatPage() { }, welcome: { ...i18nConfig.welcome, - nick: extNick ?? "QwenPaw", - avatar: extAvatar ?? "/qwenpaw.png", + nick: extNick ?? chatAssistantNick, + avatar: extAvatar ?? chatAssistantAvatar, ...(extGreeting !== undefined ? { greeting: extGreeting } : {}), ...(extDescription !== undefined ? { description: extDescription } diff --git a/src/pineagents/app/agent_sync.py b/src/pineagents/app/agent_sync.py index bb87d07..ccfbd6e 100644 --- a/src/pineagents/app/agent_sync.py +++ b/src/pineagents/app/agent_sync.py @@ -17,7 +17,7 @@ from .server_client import forward logger = logging.getLogger(__name__) # 本地 agent.json 中需要随服务端身份同步的字段 -IDENTITY_FIELDS = ("name", "description", "language", "model_name") +IDENTITY_FIELDS = ("name", "avatar", "description", "language", "model_name") async def fetch_server_agents(auth_header: str) -> list[dict]: @@ -70,6 +70,7 @@ async def create_server_agent( description: str = "", language: str = "zh", model_name: str = "", + avatar: str = "", ) -> dict: """在服务端创建智能体身份,返回服务端生成的记录。""" return await forward( @@ -80,6 +81,7 @@ async def create_server_agent( "description": description, "language": language or "zh", "model_name": model_name, + "avatar": avatar, }, auth_header=auth_header, ) diff --git a/src/pineagents/app/routers/agents.py b/src/pineagents/app/routers/agents.py index b0b0f39..079cf73 100644 --- a/src/pineagents/app/routers/agents.py +++ b/src/pineagents/app/routers/agents.py @@ -52,6 +52,7 @@ class AgentSummary(BaseModel): id: str name: str + avatar: str = "" description: str workspace_dir: str enabled: bool @@ -95,6 +96,7 @@ class CreateAgentRequest(BaseModel): id: str | None = None name: str + avatar: str = "" description: str = "" workspace_dir: str | None = None language: str | None = None @@ -330,6 +332,8 @@ async def list_agents(request: Request = None) -> AgentListResponse: (server_info or {}).get("name") or agent_config.name ), + avatar=(server_info or {}).get("avatar") + or agent_config.avatar, description=description, workspace_dir=agent_ref.workspace_dir, enabled=enabled, @@ -358,6 +362,7 @@ async def list_agents(request: Request = None) -> AgentListResponse: AgentSummary( id=agent_id, name=agent_id.title(), + avatar="", description="", workspace_dir=agent_ref.workspace_dir, enabled=enabled, @@ -549,6 +554,7 @@ async def create_agent( name=request.name, description=request.description, language=request.language or config.agents.language or "zh", + avatar=getattr(request, "avatar", "") or "", ) new_id = server_info["id"] @@ -589,6 +595,7 @@ async def create_agent( agent_config = AgentProfileConfig( id=new_id, name=server_info.get("name") or request.name, + avatar=server_info.get("avatar") or getattr(request, "avatar", "") or "", description=server_info.get("description") or request.description, workspace_dir=str(workspace_dir), backend=request.backend, @@ -823,7 +830,7 @@ async def update_agent( identity_patch = { k: v for k, v in update_data.items() - if k in ("name", "description", "language") + if k in ("name", "avatar", "description", "language") } if identity_patch: server_info = await update_server_agent( @@ -832,7 +839,7 @@ async def update_agent( identity_patch, ) if server_info: - for key in ("name", "description", "language"): + for key in ("name", "avatar", "description", "language"): if server_info.get(key) is not None: update_data[key] = server_info[key] diff --git a/src/pineagents/config/config.py b/src/pineagents/config/config.py index 6c27119..d11cbeb 100644 --- a/src/pineagents/config/config.py +++ b/src/pineagents/config/config.py @@ -1676,6 +1676,14 @@ class AgentProfileConfig(BaseModel): id: str = Field(..., description="Unique agent ID") name: str = Field(..., description="Human-readable agent name") + avatar: str = Field( + default="", + description=( + "Agent avatar. URL path (e.g. '/logo.png') or data URL; empty " + "means the agent has no avatar and callers fall back to the " + "product (云超服) brand identity." + ), + ) description: str = Field(default="", description="Agent description") workspace_dir: str = Field( default="",