Commit d92166aa authored by edy's avatar edy

feat(ui): port agent activation badge from Windows client for AI thinking visualization

- Add AgentActivationBadge component with animated role cards showing
  active agents (诊断师/策略师/内容师/技术顾问) during streaming
- Add RoleTraceIcon for role icons in trace display lines
- Add skill-to-role mapping (SKILL_ROLE_MAP) in messageTraceDisplay
- Add resolveRoleFromSkill with token-boundary matching to prevent
  false positives
- Add getActiveRoleFromTraces, getActiveRoleIdFromMessages utilities
- Wire activeRoleId in App.tsx (resolves existing TODO)
- Add agent card CSS with enter/exit animations, accent pulse, and
  working dot animations
- Add reduced-motion and responsive (720px) CSS overrides
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 468ea511
Pipeline #18505 failed
...@@ -46,6 +46,7 @@ import { useComposerResize } from "./features/chat/useComposerResize"; ...@@ -46,6 +46,7 @@ import { useComposerResize } from "./features/chat/useComposerResize";
import { useHomeIntentSuggestion } from "./features/chat/useHomeIntentSuggestion"; import { useHomeIntentSuggestion } from "./features/chat/useHomeIntentSuggestion";
import { useMessageListAutoScroll } from "./features/chat/useMessageListAutoScroll"; import { useMessageListAutoScroll } from "./features/chat/useMessageListAutoScroll";
import { useMessageTraces } from "./features/chat/useMessageTraces"; import { useMessageTraces } from "./features/chat/useMessageTraces";
import { getActiveRoleIdFromMessages } from "./features/chat/messageTraceDisplay";
import { usePromptSubmission } from "./features/chat/usePromptSubmission"; import { usePromptSubmission } from "./features/chat/usePromptSubmission";
import { useSessionMessageStore } from "./features/chat/useSessionMessageStore"; import { useSessionMessageStore } from "./features/chat/useSessionMessageStore";
import { KnowledgeView } from "./features/knowledge/KnowledgeView"; import { KnowledgeView } from "./features/knowledge/KnowledgeView";
...@@ -807,6 +808,10 @@ export default function App() { ...@@ -807,6 +808,10 @@ export default function App() {
ui ui
}); });
const hasVisibleConversation = messages.length > 0 || sendPhase !== "idle"; const hasVisibleConversation = messages.length > 0 || sendPhase !== "idle";
const activeRoleId = useMemo(
() => getActiveRoleIdFromMessages(messages, messageTraces),
[messages, messageTraces]
);
const showStartupOverlay = startupStateActive && !hasVisibleConversation; const showStartupOverlay = startupStateActive && !hasVisibleConversation;
const hasVisibleSession = Boolean(visibleSessionId && scopedSessions.some((session) => session.id === visibleSessionId)); const hasVisibleSession = Boolean(visibleSessionId && scopedSessions.some((session) => session.id === visibleSessionId));
const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving; const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving;
...@@ -1491,7 +1496,7 @@ export default function App() { ...@@ -1491,7 +1496,7 @@ export default function App() {
homeShowcaseEmployees: homeChatCopy.employees homeShowcaseEmployees: homeChatCopy.employees
}, },
isStreaming: sendPhase === "streaming" || sendPhase === "finalizing", isStreaming: sendPhase === "streaming" || sendPhase === "finalizing",
// TODO: wire activeRoleId from the current active role in the ongoing AI collaboration flow activeRoleId,
messages: { messages: {
messageListRef, messageListRef,
messages, messages,
...@@ -1502,6 +1507,7 @@ export default function App() { ...@@ -1502,6 +1507,7 @@ export default function App() {
sending, sending,
messageLabels: { messageLabels: {
thinking: ui.thinking, thinking: ui.thinking,
traceTitle: ui.traceTitle,
hideTrace: ui.hideTrace, hideTrace: ui.hideTrace,
traceCollapsed: ui.traceCollapsed traceCollapsed: ui.traceCollapsed
}, },
......
...@@ -62,6 +62,7 @@ export const ui = { ...@@ -62,6 +62,7 @@ export const ui = {
projectSessionsLabel: "\u4f1a\u8bdd", projectSessionsLabel: "\u4f1a\u8bdd",
newSession: "\u65b0\u5efa\u4f1a\u8bdd", newSession: "\u65b0\u5efa\u4f1a\u8bdd",
closeSession: "\u5173\u95ed\u4f1a\u8bdd", closeSession: "\u5173\u95ed\u4f1a\u8bdd",
traceTitle: "\u601d\u8003\u8fc7\u7a0b",
hideTrace: "\u6536\u8d77", hideTrace: "\u6536\u8d77",
traceEmpty: "\u8fd8\u6ca1\u6709\u53ef\u663e\u793a\u7684\u8fdb\u5ea6\u3002", traceEmpty: "\u8fd8\u6ca1\u6709\u53ef\u663e\u793a\u7684\u8fdb\u5ea6\u3002",
traceCollapsed: "\u5c55\u5f00", traceCollapsed: "\u5c55\u5f00",
......
import { renderRoleIcon } from "./roleIconPaths"
const ROLE_COLORS: Record<string, string> = {
"诊断师": "#3b82f6",
"策略师": "#f59e0b",
"内容师": "#10b981",
"技术顾问": "#8b5cf6",
}
export interface AgentActivationBadgeProps {
role: {
name: string
icon: string
}
statusLabel: string
statusDetail?: string
visible: boolean
}
function getRoleColor(roleName: string): string {
return ROLE_COLORS[roleName] ?? "#6b7280"
}
export function AgentActivationBadge({ role, statusLabel, statusDetail, visible }: AgentActivationBadgeProps) {
const accentColor = getRoleColor(role.name)
return (
<div
className={"agent-activation-card" + (visible ? " agent-card-visible" : " agent-card-hidden")}
style={{ "--agent-accent-color": accentColor } as React.CSSProperties}
aria-label={`${role.name}正在工作:${statusLabel}`}
>
<div className="agent-card-accent" aria-hidden="true" />
<div className="agent-card-body">
<div className="agent-card-icon-wrap" style={{ color: accentColor }}>
{renderRoleIcon(role.icon, { width: 22, height: 22, strokeWidth: 1.6 })}
</div>
<div className="agent-card-info">
<div className="agent-card-header">
<span className="agent-card-role-name">{role.name}</span>
<span className="agent-card-working-badge" aria-hidden="true">
<span className="agent-card-working-dot" />
<span className="agent-card-working-text">工作中</span>
</span>
</div>
<p className="agent-card-status">{statusLabel}</p>
{statusDetail ? <p className="agent-card-detail">{statusDetail}</p> : null}
</div>
</div>
</div>
)
}
...@@ -87,6 +87,7 @@ interface ConversationWorkspaceMessagesProps { ...@@ -87,6 +87,7 @@ interface ConversationWorkspaceMessagesProps {
sending: boolean sending: boolean
messageLabels: { messageLabels: {
thinking: string thinking: string
traceTitle: string
hideTrace: string hideTrace: string
traceCollapsed: string traceCollapsed: string
} }
......
import type { ChatAttachment, ChatMessage } from "@qjclaw/shared-types" import type { ChatAttachment, ChatMessage } from "@qjclaw/shared-types"
import { useEffect, useMemo, useState, type ReactNode, type RefObject, type UIEvent } from "react" import { useEffect, useMemo, useState, type ReactNode, type RefObject, type UIEvent } from "react"
import { desktopApi } from "../../lib/desktop-api" import { desktopApi } from "../../lib/desktop-api"
import { getTraceDisplayLines, getTraceLineClassName, getTraceStripTitle } from "./messageTraceDisplay" import { getLatestTraceLabel, getTraceDisplayLines, getTraceLineClassName, getActiveRoleFromTraces, getTraceStripTitle } from "./messageTraceDisplay"
import type { MessageTraceState } from "./useMessageTraces" import type { MessageTraceState } from "./useMessageTraces"
import { AgentActivationBadge } from "./AgentActivationBadge"
import { RoleTraceIcon } from "./RoleTraceIcon"
type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge" type ViewMode = "chat" | "experts" | "tasks" | "automation" | "plugins" | "settings" | "knowledge"
type MessageReaction = "up" | "down" type MessageReaction = "up" | "down"
...@@ -22,6 +24,7 @@ interface VideoStatusCardContent { ...@@ -22,6 +24,7 @@ interface VideoStatusCardContent {
interface MessageListLabels { interface MessageListLabels {
thinking: string thinking: string
traceTitle: string
hideTrace: string hideTrace: string
traceCollapsed: string traceCollapsed: string
} }
...@@ -221,8 +224,14 @@ export function MessageList({ ...@@ -221,8 +224,14 @@ export function MessageList({
const isTraceExpanded = Boolean(messageTrace?.expanded) const isTraceExpanded = Boolean(messageTrace?.expanded)
const isReasoningActive = message.streamState === "streaming" || message.streamState === "error" const isReasoningActive = message.streamState === "streaming" || message.streamState === "error"
const showReasoningStrip = message.role === "assistant" && !videoStatusCard && isReasoningActive && (hasTrace || message.streamState === "error" || isWaitingForContent) const showReasoningStrip = message.role === "assistant" && !videoStatusCard && isReasoningActive && (hasTrace || message.streamState === "error" || isWaitingForContent)
const activeRole = message.role === "assistant" && hasTrace
? getActiveRoleFromTraces(messageTrace!.items)
: undefined
const showAgentBadge = Boolean(activeRole) && message.streamState === "streaming"
const traceTitle = message.role === "assistant" && message.streamState === "streaming"
? (activeRole ? labels.traceTitle : (message.statusLabel ?? getLatestTraceLabel(messageTrace?.items ?? []) ?? labels.traceTitle))
: getTraceStripTitle(messageTrace?.items ?? [], labels.thinking, message.statusLabel, message.statusDetail)
const traceDisplayLines = hasTrace ? getTraceDisplayLines(messageTrace?.items ?? []) : [] const traceDisplayLines = hasTrace ? getTraceDisplayLines(messageTrace?.items ?? []) : []
const traceTitle = getTraceStripTitle(messageTrace?.items ?? [], labels.thinking, message.statusLabel, message.statusDetail)
const canCopyMessage = Boolean(message.content.trim()) const canCopyMessage = Boolean(message.content.trim())
const hasVisibleAttachments = message.role === "user" && Array.isArray(message.attachments) && message.attachments.some((attachment) => attachment.localPath && attachment.name) const hasVisibleAttachments = message.role === "user" && Array.isArray(message.attachments) && message.attachments.some((attachment) => attachment.localPath && attachment.name)
const canDeleteMessage = message.streamState !== "streaming" && (canCopyMessage || hasVisibleAttachments) const canDeleteMessage = message.streamState !== "streaming" && (canCopyMessage || hasVisibleAttachments)
...@@ -292,6 +301,14 @@ export function MessageList({ ...@@ -292,6 +301,14 @@ export function MessageList({
<article key={message.id} className={"message-card group " + message.role + (message.streamState ? " " + message.streamState : "")}> <article key={message.id} className={"message-card group " + message.role + (message.streamState ? " " + message.streamState : "")}>
<div className={"message-card-body message-card-body-" + message.role}> <div className={"message-card-body message-card-body-" + message.role}>
<div className={"message-bubble" + (message.role === "assistant" ? " message-bubble-assistant" : " message-bubble-user")}> <div className={"message-bubble" + (message.role === "assistant" ? " message-bubble-assistant" : " message-bubble-user")}>
{showAgentBadge && activeRole ? (
<AgentActivationBadge
role={{ name: activeRole.name, icon: activeRole.icon }}
statusLabel={activeRole.label}
statusDetail={activeRole.detail}
visible={message.streamState === "streaming"}
/>
) : null}
{showReasoningStrip ? ( {showReasoningStrip ? (
<div className={"message-trace" + (message.streamState === "streaming" ? " streaming" : "")} aria-live={message.streamState === "streaming" ? "polite" : undefined}> <div className={"message-trace" + (message.streamState === "streaming" ? " streaming" : "")} aria-live={message.streamState === "streaming" ? "polite" : undefined}>
<button <button
...@@ -318,7 +335,10 @@ export function MessageList({ ...@@ -318,7 +335,10 @@ export function MessageList({
<span className="message-trace-marker" aria-hidden="true" /> <span className="message-trace-marker" aria-hidden="true" />
<span className="message-trace-main"> <span className="message-trace-main">
<span className="message-trace-row"> <span className="message-trace-row">
<span className="message-trace-text">{line.title}</span> <span className="message-trace-text">
{line.icon ? <RoleTraceIcon icon={line.icon} /> : null}
{line.title}
</span>
<span className="message-trace-time">{line.time}</span> <span className="message-trace-time">{line.time}</span>
</span> </span>
{line.detail ? <span className="message-trace-detail">{line.detail}</span> : null} {line.detail ? <span className="message-trace-detail">{line.detail}</span> : null}
......
import { renderRoleIcon } from "./roleIconPaths"
interface RoleTraceIconProps {
icon: string
}
export function RoleTraceIcon({ icon }: RoleTraceIconProps) {
return (
<span className="message-trace-role-icon" aria-hidden="true">
{renderRoleIcon(icon, { width: 14, height: 14, strokeWidth: 1.8 })}
</span>
)
}
...@@ -2,15 +2,93 @@ import type { ConversationTraceItem, TraceTone } from "./useMessageTraces" ...@@ -2,15 +2,93 @@ import type { ConversationTraceItem, TraceTone } from "./useMessageTraces"
export type TraceStepKind = "setup" | "routing" | "tool" | "writing" | "complete" | "error" export type TraceStepKind = "setup" | "routing" | "tool" | "writing" | "complete" | "error"
// Mirrors project.json roles.skillIds mapping — keep in sync when roles change.
interface SkillRoleEntry {
name: string
icon: string
roleId: string
}
const SKILL_ROLE_MAP: Record<string, SkillRoleEntry> = {
"xhs-strategist": { name: "策划专家", icon: "lightbulb", roleId: "strategist" },
"xhs-copywriter": { name: "文案专家", icon: "pen", roleId: "copywriter" },
"xhs-create-note": { name: "文案专家", icon: "pen", roleId: "copywriter" },
"xhs-pipeline": { name: "文案专家", icon: "pen", roleId: "copywriter" },
"xhs-publish-note": { name: "文案专家", icon: "pen", roleId: "copywriter" },
"xhs-kol": { name: "达人投放", icon: "users", roleId: "kol" },
"xhs-community": { name: "社区运营", icon: "chat", roleId: "community" },
"xhs-analytics": { name: "数据分析", icon: "chart", roleId: "analytics" },
"xhs-research": { name: "策划专家", icon: "lightbulb", roleId: "strategist" },
"geo-diagnostician": { name: "诊断师", icon: "search", roleId: "diagnostician" },
"geo-strategist": { name: "策略师", icon: "lightbulb", roleId: "strategist" },
"geo-content-creator": { name: "内容师", icon: "pen", roleId: "content-creator" },
"geo-tech-advisor": { name: "技术顾问", icon: "settings", roleId: "tech-advisor" },
}
export function resolveRoleFromSkill(value: string): SkillRoleEntry | undefined {
const lower = value.toLowerCase()
for (const [skillId, role] of Object.entries(SKILL_ROLE_MAP)) {
const sid = skillId.toLowerCase()
const idx = lower.indexOf(sid)
if (idx === -1) continue
// Match skillId as a complete token — bounded by string edges, whitespace, or hyphens
const beforeOk = idx === 0 || /[\s-]/.test(lower[idx - 1])
const afterOk = idx + sid.length === lower.length || /[\s-]/.test(lower[idx + sid.length])
if (beforeOk && afterOk) return role
}
return undefined
}
export interface ActiveRoleInfo {
name: string
icon: string
roleId: string
label: string
detail?: string
}
export function getActiveRoleFromTraces(
items: ConversationTraceItem[]
): ActiveRoleInfo | undefined {
for (let i = items.length - 1; i >= 0; i--) {
const role = resolveRoleFromSkill(items[i].stage)
if (role) {
return { ...role, label: items[i].label, detail: items[i].detail }
}
}
return undefined
}
export function getActiveRoleIdFromMessages(
messages: Array<{ role: string; id: string }>,
traces: Record<string, { items: ConversationTraceItem[] }>
): string | undefined {
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i]
if (message.role !== "assistant") continue
const trace = traces[message.id]
if (!trace?.items?.length) continue
const active = getActiveRoleFromTraces(trace.items)
if (active) return active.roleId
}
return undefined
}
export interface TraceDisplayLine { export interface TraceDisplayLine {
key: string key: string
time: string time: string
title: string title: string
icon?: string
detail?: string detail?: string
tone: TraceTone tone: TraceTone
kind: TraceStepKind kind: TraceStepKind
} }
export function getLatestTraceLabel(items: ConversationTraceItem[]): string | undefined {
const latest = items.at(-1)?.label?.replace(/\s+/g, " ").trim()
return latest || undefined
}
const TRACE_TITLE_MAX_LENGTH = 34 const TRACE_TITLE_MAX_LENGTH = 34
export function formatTraceTime(value: string): string { export function formatTraceTime(value: string): string {
...@@ -69,6 +147,10 @@ function classifyTraceItem(item: ConversationTraceItem): TraceStepKind { ...@@ -69,6 +147,10 @@ function classifyTraceItem(item: ConversationTraceItem): TraceStepKind {
if (stage.includes("prepare") || stage.includes("request") || stage.includes("await")) { if (stage.includes("prepare") || stage.includes("request") || stage.includes("await")) {
return "setup" return "setup"
} }
// Recognize skill stages (e.g. "geo-diagnostician", "xhs-strategist") as tool kind
if (resolveRoleFromSkill(stage)) {
return "tool"
}
if (stage.includes("workspace") || stage.includes("runtime") || stage.includes("tool")) { if (stage.includes("workspace") || stage.includes("runtime") || stage.includes("tool")) {
return "tool" return "tool"
} }
...@@ -84,26 +166,30 @@ function classifyTraceItem(item: ConversationTraceItem): TraceStepKind { ...@@ -84,26 +166,30 @@ function classifyTraceItem(item: ConversationTraceItem): TraceStepKind {
return "setup" return "setup"
} }
function getTraceStepTitle(item: ConversationTraceItem, kind: TraceStepKind): string { function getTraceStepDisplay(item: ConversationTraceItem, kind: TraceStepKind): { title: string; icon?: string } {
if (kind === "error") { if (kind === "error") {
return "遇到错误" return { title: "遇到错误" }
} }
if (kind === "complete") { if (kind === "complete") {
return "回答完成" return { title: "回答完成" }
} }
if (kind === "routing") { if (kind === "routing") {
return "选择合适的处理方式" return { title: "选择合适的处理方式" }
} }
if (kind === "tool") { if (kind === "tool") {
return "准备执行环境" const role = resolveRoleFromSkill(`${item.stage} ${item.label}`)
if (role) {
return { title: `${role.name}正在工作`, icon: role.icon }
}
return { title: "准备执行环境" }
} }
if (kind === "writing") { if (kind === "writing") {
return "整理回答内容" return { title: "整理回答内容" }
} }
if (item.stage === "request") { if (item.stage === "request") {
return "理解问题" return { title: "理解问题" }
} }
return "准备上下文" return { title: "准备上下文" }
} }
function getTraceStepDetail(item: ConversationTraceItem, kind: TraceStepKind): string | undefined { function getTraceStepDetail(item: ConversationTraceItem, kind: TraceStepKind): string | undefined {
...@@ -115,7 +201,7 @@ function getTraceStepDetail(item: ConversationTraceItem, kind: TraceStepKind): s ...@@ -115,7 +201,7 @@ function getTraceStepDetail(item: ConversationTraceItem, kind: TraceStepKind): s
if (detail) { if (detail) {
return detail return detail
} }
if (!label || label === getTraceStepTitle(item, kind)) { if (!label || label === getTraceStepDisplay(item, kind).title) {
return undefined return undefined
} }
return label return label
...@@ -125,23 +211,36 @@ export function getTraceLineClassName(tone: TraceTone, kind?: TraceStepKind): st ...@@ -125,23 +211,36 @@ export function getTraceLineClassName(tone: TraceTone, kind?: TraceStepKind): st
return ["message-trace-line", tone, kind ? `trace-kind-${kind}` : ""].filter(Boolean).join(" ") return ["message-trace-line", tone, kind ? `trace-kind-${kind}` : ""].filter(Boolean).join(" ")
} }
export function getTraceLineLabels(item: ConversationTraceItem): {
time: string
label: string
detail?: string
} {
return {
time: formatTraceTime(item.createdAt),
label: item.label,
detail: item.detail
}
}
export function getTraceDisplayLines(items: ConversationTraceItem[]): TraceDisplayLine[] { export function getTraceDisplayLines(items: ConversationTraceItem[]): TraceDisplayLine[] {
const lines: TraceDisplayLine[] = [] const lines: TraceDisplayLine[] = []
for (const item of items) { for (const item of items) {
const kind = classifyTraceItem(item) const kind = classifyTraceItem(item)
const title = getTraceStepTitle(item, kind) const display = getTraceStepDisplay(item, kind)
const detail = getTraceStepDetail(item, kind) const detail = getTraceStepDetail(item, kind)
const previous = lines.at(-1) const previous = lines.at(-1)
if (previous && previous.title === title && previous.detail === detail && previous.tone === item.tone && previous.kind === kind) { if (previous && previous.title === display.title && previous.detail === detail && previous.tone === item.tone && previous.kind === kind) {
continue continue
} }
lines.push({ lines.push({
key: item.id, key: item.id,
time: formatTraceTime(item.createdAt), time: formatTraceTime(item.createdAt),
title, title: display.title,
icon: display.icon,
detail, detail,
tone: item.tone, tone: item.tone,
kind kind
......
...@@ -465,6 +465,139 @@ ...@@ -465,6 +465,139 @@
line-height: 1.4; line-height: 1.4;
} }
/* ── Agent Activation Card ─────────────────────────────────────── */
.agent-activation-card {
width: min(100%, 880px);
display: grid;
grid-template-columns: 4px 1fr;
gap: 0;
background: linear-gradient(135deg, rgba(241, 245, 249, 0.85) 0%, rgba(248, 250, 252, 0.9) 100%);
border: 1px solid rgba(203, 213, 225, 0.55);
border-radius: 12px;
overflow: hidden;
margin-bottom: 10px;
animation: agent-card-enter 0.35s ease-out both;
}
.agent-activation-card.agent-card-hidden {
animation: agent-card-exit 0.25s ease-in both;
pointer-events: none;
}
.agent-card-accent {
width: 4px;
height: 100%;
background: var(--agent-accent-color, #6b7280);
border-radius: 4px 0 0 4px;
animation: agent-accent-pulse 2.4s ease-in-out infinite;
}
.agent-card-body {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 14px;
}
.agent-card-icon-wrap {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 10px;
background: color-mix(in srgb, var(--agent-accent-color, #6b7280) 12%, transparent);
color: var(--agent-accent-color, #6b7280);
}
.agent-card-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.agent-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.agent-card-role-name {
font-size: 14px;
font-weight: 650;
color: #1e293b;
line-height: 1.3;
}
.agent-card-working-badge {
display: flex;
align-items: center;
gap: 5px;
flex-shrink: 0;
}
.agent-card-working-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--agent-accent-color, #6b7280);
animation: agent-working-dot 1.6s ease-in-out infinite;
}
.agent-card-working-text {
font-size: 11px;
font-weight: 500;
color: #64748b;
letter-spacing: 0.01em;
}
.agent-card-status {
margin: 0;
font-size: 13px;
font-weight: 400;
color: #475569;
line-height: 1.45;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-card-detail {
margin: 0;
font-size: 12px;
font-weight: 400;
color: #94a3b8;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@keyframes agent-card-enter {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes agent-card-exit {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-6px); }
}
@keyframes agent-accent-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.45; }
}
@keyframes agent-working-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.25; transform: scale(0.65); }
}
.message-trace-content { .message-trace-content {
position: relative; position: relative;
display: grid; display: grid;
...@@ -553,6 +686,18 @@ ...@@ -553,6 +686,18 @@
font-weight: 700; font-weight: 700;
} }
.message-trace-role-icon {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 3px;
color: #e11d48;
vertical-align: text-bottom;
position: relative;
top: 1px;
}
.message-trace-detail { .message-trace-detail {
display: -webkit-box; display: -webkit-box;
max-width: 100%; max-width: 100%;
...@@ -1303,6 +1448,18 @@ select:focus-visible { ...@@ -1303,6 +1448,18 @@ select:focus-visible {
.reasoning-strip.streaming::after { .reasoning-strip.streaming::after {
opacity: 0; opacity: 0;
} }
.agent-activation-card {
animation: none;
}
.agent-card-accent {
animation: none;
}
.agent-card-working-dot {
animation: none;
}
} }
@media (max-width: 1100px) { @media (max-width: 1100px) {
...@@ -1344,6 +1501,30 @@ select:focus-visible { ...@@ -1344,6 +1501,30 @@ select:focus-visible {
.nav-item { justify-content: center; } .nav-item { justify-content: center; }
.hero-line { font-size: 18px; } .hero-line { font-size: 18px; }
.chat-panel { padding: 16px; } .chat-panel { padding: 16px; }
.agent-activation-card {
width: 100%;
border-radius: 10px;
}
.agent-card-body {
padding: 10px 12px;
gap: 10px;
}
.agent-card-icon-wrap {
width: 32px;
height: 32px;
border-radius: 8px;
}
.agent-card-role-name {
font-size: 13px;
}
.agent-card-status {
font-size: 12px;
}
} }
@media (max-width: 860px) { @media (max-width: 860px) {
......
...@@ -1545,6 +1545,11 @@ ...@@ -1545,6 +1545,11 @@
box-shadow: var(--revamp-shadow-soft); box-shadow: var(--revamp-shadow-soft);
} }
.conversation-shell .message-card.assistant .agent-activation-card {
border: 1px solid rgba(203, 213, 225, 0.58);
background: linear-gradient(135deg, rgba(248, 250, 252, 0.92) 0%, rgba(255, 255, 255, 0.94) 100%);
}
.conversation-shell .message-card.assistant .message-trace.streaming { .conversation-shell .message-card.assistant .message-trace.streaming {
margin-bottom: 12px; margin-bottom: 12px;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment