fix(chat): 助手身份显示当前智能体名称/头像,无则回退云超服
问题:聊天区助手名称/头像沿用上游硬编码 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 测试全过。
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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_", // 环境变量主前缀
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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="",
|
||||
|
||||
Reference in New Issue
Block a user