fix(workspace): 创作工作台 - 修复线上样式层级并优化 Agent、菜单与素材密度
This commit is contained in:
47
web/src/components/canvas/canvas-agent-welcome.tsx
Normal file
47
web/src/components/canvas/canvas-agent-welcome.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ArrowUpRight, Clapperboard, Layers3, Sparkles } from "lucide-react";
|
||||
|
||||
type AgentWelcomeProps = {
|
||||
brandName: string;
|
||||
nodeCount: number;
|
||||
onChooseSkill: () => void;
|
||||
onDraftPrompt: (prompt: string) => void;
|
||||
};
|
||||
|
||||
export function AgentWelcome({ brandName, nodeCount, onChooseSkill, onDraftPrompt }: AgentWelcomeProps) {
|
||||
return (
|
||||
<section className="agent-welcome" aria-label="开始 Agent 创作">
|
||||
<div className="agent-welcome-intro">
|
||||
<span className="agent-welcome-orb" aria-hidden="true" />
|
||||
<h2>在这里,和{brandName}让灵感,慢慢成形</h2>
|
||||
<p>从一个想法开始,和 Agent 一起创作。</p>
|
||||
</div>
|
||||
<div className="agent-welcome-actions">
|
||||
<button type="button" onClick={onChooseSkill}>
|
||||
<Sparkles aria-hidden="true" />
|
||||
<span>
|
||||
<strong>选择技能,开始创作</strong>
|
||||
<small>为这次创作找到合适的帮手</small>
|
||||
</span>
|
||||
<ArrowUpRight className="agent-welcome-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" onClick={() => onDraftPrompt("我想创作一段短片,请先和我一起梳理故事方向。先询问我的想法,不要直接生成。")}>
|
||||
<Clapperboard aria-hidden="true" />
|
||||
<span>
|
||||
<strong>从灵感构思故事</strong>
|
||||
<small>聊聊主题、角色,或一个难忘的画面</small>
|
||||
</span>
|
||||
<ArrowUpRight className="agent-welcome-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" disabled={nodeCount === 0} onClick={() => onDraftPrompt("请先阅读当前画布,梳理素材与节点之间的关系,给出接下来的创作建议。先不要修改节点或提交生成任务。")}>
|
||||
<Layers3 aria-hidden="true" />
|
||||
<span>
|
||||
<strong>一起梳理当前画布</strong>
|
||||
<small>{nodeCount > 0 ? `${nodeCount} 个节点,看看下一步可以做什么` : "添加节点后,一起梳理创作思路"}</small>
|
||||
</span>
|
||||
<ArrowUpRight className="agent-welcome-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="agent-welcome-footnote">先聊想法,再决定下一步</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -163,7 +163,7 @@ export function AgentChatMessage({
|
||||
return (
|
||||
<div className={`flex items-start gap-3 ${isUser ? "justify-end" : "justify-start"}`}>
|
||||
{!isUser ? <AgentTimelineMarker theme={theme} tone="agent" /> : null}
|
||||
<div className={`min-w-0 text-sm leading-6 ${isUser ? "max-w-[82%] rounded-2xl rounded-br-md px-3.5 py-2.5 text-right" : "max-w-[calc(100%-36px)] flex-1 text-left"}`} style={{ color, ...(isUser ? { background: theme.node.agentUserMessage } : {}) }}>
|
||||
<div className={`agent-message-body min-w-0 text-sm leading-6 ${isUser ? "agent-message-user max-w-[82%] px-4 py-3 text-right" : "max-w-[calc(100%-36px)] flex-1 text-left"}`} style={{ color }}>
|
||||
{item.interjection ? (
|
||||
<span
|
||||
className="mb-1 inline-flex items-center rounded-full px-1.5 py-[1px] text-[var(--fs-label)] leading-4"
|
||||
@@ -370,7 +370,7 @@ export function AgentPlanBar({ items, theme, minimized, onToggle }: {
|
||||
const doneCount = items.filter((entry) => entry.status === "done").length;
|
||||
const allDone = doneCount === items.length;
|
||||
return (
|
||||
<div className="agent-plan-bar mx-3 mb-2 overflow-hidden rounded-xl" style={{ background: theme.node.fill, border: `1px solid ${theme.node.stroke}`, color: theme.node.text }}>
|
||||
<div className="agent-plan-bar mx-3 mb-2 overflow-hidden rounded-xl" style={{ color: theme.node.text }}>
|
||||
<button type="button" className="flex w-full items-center gap-2 px-3 py-2 text-left focus-visible:outline focus-visible:outline-2" aria-expanded={!minimized} onClick={onToggle}>
|
||||
<ListChecks className="size-3.5 shrink-0" style={{ color: allDone ? "#429477" : theme.node.muted }} />
|
||||
<span className="text-xs font-semibold">本轮待办</span>
|
||||
@@ -405,7 +405,7 @@ export function AgentQuestionBar({ question, theme, onAnswer, disabled = false }
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="agent-question-bar mx-3 mb-2 overflow-hidden rounded-xl" style={{ background: theme.node.fill, border: `1px solid ${theme.accent.primary}`, color: theme.node.text }}>
|
||||
<div className="agent-question-bar mx-3 mb-2 overflow-hidden rounded-xl" style={{ color: theme.node.text }}>
|
||||
<div className="flex items-start gap-2 px-3 pt-2.5">
|
||||
<HelpCircle className="mt-[1px] size-3.5 shrink-0" style={{ color: theme.accent.primary }} />
|
||||
<span className="min-w-0 flex-1 text-xs font-semibold leading-5">{question.question}</span>
|
||||
@@ -417,8 +417,8 @@ export function AgentQuestionBar({ question, theme, onAnswer, disabled = false }
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
title={option.detail || option.label}
|
||||
className="max-w-full rounded-md border px-3 py-1.5 text-left text-xs transition focus-visible:outline focus-visible:outline-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
style={{ borderColor: theme.node.stroke, background: theme.toolbar.itemHover }}
|
||||
className="max-w-full rounded-md border-0 px-3 py-1.5 text-left text-xs transition focus-visible:outline focus-visible:outline-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
style={{ background: theme.toolbar.itemHover }}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={(event) => {
|
||||
@@ -600,13 +600,11 @@ export function AgentChatComposer({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-w-0 shrink-0 px-3 pb-3 pt-2" onWheelCapture={(event) => event.stopPropagation()}>
|
||||
<div className="agent-composer-wrap min-w-0 shrink-0" onWheelCapture={(event) => event.stopPropagation()}>
|
||||
<div
|
||||
className="group/composer relative rounded-2xl px-3 pb-2.5 pt-3 transition-[background-color,box-shadow] duration-200"
|
||||
className="agent-composer-surface group/composer relative transition-[background-color,box-shadow] duration-200"
|
||||
style={{
|
||||
background: theme.node.fill,
|
||||
color: theme.accent.primary,
|
||||
boxShadow: `0 16px 40px ${theme.spatial.shadow}, inset 0 1px 0 rgba(255,255,255,0.045)`,
|
||||
}}
|
||||
>
|
||||
{sending && !reducedMotion ? <WorkingGlow active color={theme.accent.primary} radius={22} /> : null}
|
||||
@@ -757,8 +755,8 @@ export function AgentChatComposer({
|
||||
whileTap={!reducedMotion && !stopping ? { scale: 0.9, y: 1 } : undefined}
|
||||
animate={stopping && !reducedMotion ? { scale: [1, 0.94, 1] } : { scale: 1 }}
|
||||
transition={{ type: "spring", stiffness: 420, damping: 24 }}
|
||||
className="grid size-9 shrink-0 place-items-center rounded-full p-0 outline-none transition-[background-color,box-shadow,color,transform] duration-200 focus-visible:ring-2 focus-visible:ring-current/35 disabled:cursor-not-allowed"
|
||||
style={{ background: theme.accent.danger, color: theme.accent.onPrimary, boxShadow: `0 8px 20px ${theme.accent.danger}45` }}
|
||||
className="grid size-7 shrink-0 place-items-center rounded-full p-0 outline-none transition-[background-color,box-shadow,color,transform] duration-200 focus-visible:ring-2 focus-visible:ring-current/35 disabled:cursor-not-allowed"
|
||||
style={{ background: theme.accent.danger, color: theme.accent.onPrimary }}
|
||||
>
|
||||
{stopping ? <LoaderCircle className="size-4 animate-spin" /> : <Square className="size-3.5" fill="currentColor" />}
|
||||
</motion.button> : null}
|
||||
@@ -772,11 +770,10 @@ export function AgentChatComposer({
|
||||
whileTap={canSubmit && !reducedMotion ? { scale: 0.9, y: 1 } : undefined}
|
||||
animate={stopping && !reducedMotion ? { scale: [1, 0.94, 1] } : { scale: 1, rotate: 0 }}
|
||||
transition={sending && !reducedMotion ? { duration: 0.42, ease: "easeOut" } : { type: "spring", stiffness: 420, damping: 24 }}
|
||||
className="grid size-9 shrink-0 place-items-center rounded-full p-0 outline-none transition-[background-color,box-shadow,color,transform] duration-200 focus-visible:ring-2 focus-visible:ring-current/35 disabled:cursor-not-allowed"
|
||||
className="agent-composer-send grid size-7 shrink-0 place-items-center rounded-full p-0 outline-none transition-[background-color,box-shadow,color,transform] duration-200 focus-visible:ring-2 focus-visible:ring-current/35 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: canSubmit || sending ? theme.accent.primary : theme.spatial.surface,
|
||||
color: canSubmit || sending ? theme.accent.onPrimary : theme.node.muted,
|
||||
boxShadow: canSubmit || sending ? `0 8px 20px ${theme.accent.primary}45` : "none",
|
||||
}}
|
||||
>
|
||||
<motion.span
|
||||
@@ -786,7 +783,7 @@ export function AgentChatComposer({
|
||||
transition={{ duration: reducedMotion ? 0 : 0.18, ease: "easeOut" }}
|
||||
className="grid place-items-center"
|
||||
>
|
||||
{sending ? <LoaderCircle className="size-4 animate-spin" /> : <ArrowUp className="size-4" />}
|
||||
{sending ? <LoaderCircle className="size-3.5 animate-spin" /> : <ArrowUp className="size-3.5" />}
|
||||
</motion.span>
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type Dispatch, type SetStateAction } from "react";
|
||||
import { Button, Dropdown, Input } from "antd";
|
||||
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
||||
import { ArrowLeft, Bot, Check, ChevronRight, CircleDot, Clock3, Download, History, LoaderCircle, MessageSquarePlus, Settings2, ShieldCheck, Trash2, Sparkles, X } from "lucide-react";
|
||||
import { ArrowLeft, Check, ChevronRight, CircleDot, Clock3, Download, History, LoaderCircle, MessageSquarePlus, MoveDiagonal2, Settings2, ShieldCheck, Trash2, Sparkles, X } from "lucide-react";
|
||||
import { saveAs } from "file-saver";
|
||||
import { buildAgentDebugExport } from "@/lib/canvas/agent-debug-export";
|
||||
import { agentPlanVisible, latestAgentPlanItems, pendingAgentQuestion } from "@/lib/canvas/cloud-agent-plan";
|
||||
@@ -24,6 +24,7 @@ import { addSkill, listAddedSkills, listSkills, type Skill, type SkillCategory }
|
||||
import { clearCloudAgentPendingSubmission, cloudAgentConversationTitle, loadCloudAgentConversations, loadCloudAgentPendingSubmission, saveCloudAgentConversations, saveCloudAgentPendingSubmission, type CloudAgentConversation, type CloudAgentPendingSubmission } from "@/services/cloud-agent-conversations";
|
||||
import { logicalModelIDForConfig, modelOptionName, resolveModelRequestConfig, selectableModelsByCapability, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
|
||||
import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store";
|
||||
import { useAppearanceStore } from "@/stores/use-appearance-store";
|
||||
import { applyAgentCanvasPatches, refreshCanvasAfterAgent, saveRemoteUserDataNow } from "@/services/user-data-sync";
|
||||
import { createAgentCanvasSync } from "@/services/agent-canvas-sync";
|
||||
import { buildSkillMentionReferences, resolveSkillMentions } from "@/services/skill-runtime";
|
||||
@@ -31,6 +32,7 @@ import { AgentChatComposer, AgentChatMessage, AgentPlanBar, AgentQuestionBar, Ag
|
||||
import { CanvasAgentSkillLibraryModal } from "./canvas-agent-skill-library-modal";
|
||||
import { CanvasCloudAgentSettings, agentPermissionLabel, agentPermissionMenuItems, agentPermissionVisual, type AgentContextKey } from "./canvas-cloud-agent-settings";
|
||||
import { useAgentPanelLayout } from "./use-agent-panel-layout";
|
||||
import { AgentWelcome } from "./canvas-agent-welcome";
|
||||
import "./canvas-cloud-agent.css";
|
||||
|
||||
type CloudAgentPanelProps = { canvasId: string; domainProjectId?: string; nodeCount: number; references: CanvasResourceReference[]; open: boolean; prefillPrompt?: string; onOpen: () => void; onCollapse: () => void; onFocusNode?: (nodeId: string) => void };
|
||||
@@ -598,8 +600,8 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||||
exit={reducedMotion ? { opacity: 0 } : { opacity: 0, y: 12, scale: 0.985 }}
|
||||
transition={{ duration: reducedMotion ? 0 : 0.26, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="canvas-agent-panel fixed z-[var(--z-modal-overlay)] flex min-w-0 flex-col overflow-hidden rounded-2xl border max-sm:rounded-b-none"
|
||||
style={{ ...panelLayout.style, background: theme.node.panel, borderColor: theme.toolbar.border, color: theme.node.text, boxShadow: `0 28px 90px ${theme.spatial.shadow}` }}
|
||||
className="canvas-agent-panel fixed z-[var(--z-modal-overlay)] flex min-w-0 flex-col overflow-hidden"
|
||||
style={{ ...panelLayout.style, "--agent-surface-base": theme.node.panel, "--agent-ink": theme.node.text, "--agent-accent": theme.accent.primary, "--agent-shadow-color": theme.spatial.shadow } as CSSProperties & Record<`--${string}`, string>}
|
||||
aria-label="Agent 工作台"
|
||||
data-canvas-no-zoom
|
||||
data-canvas-wheel-scroll
|
||||
@@ -613,10 +615,11 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
aria-label="调整 Agent 面板大小"
|
||||
title="拖动调整宽高,也可用方向键调整"
|
||||
data-agent-resize="northwest"
|
||||
className="absolute left-0 top-0 z-10 hidden size-5 cursor-nw-resize touch-none opacity-50 transition-opacity hover:opacity-100 focus-visible:outline focus-visible:outline-2 sm:block"
|
||||
className="agent-panel-resize-corner absolute left-2 top-2 z-10 hidden size-5 cursor-nw-resize touch-none place-items-center rounded-md opacity-60 transition-opacity hover:opacity-100 focus-visible:outline focus-visible:outline-2 sm:grid"
|
||||
onKeyDown={panelLayout.onResizeKeyDown}
|
||||
style={{ color: theme.node.muted }}
|
||||
>
|
||||
<span className="absolute left-1.5 top-1.5 size-2 border-l-2 border-t-2 rounded-tl" style={{ borderColor: theme.node.muted }} />
|
||||
<MoveDiagonal2 className="size-3" aria-hidden="true" />
|
||||
</button>
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{view === "settings" ? (
|
||||
@@ -676,6 +679,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
<motion.div key="chat" className="flex min-h-0 flex-1 flex-col" initial={{ opacity: 0, x: -12 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -12 }} transition={{ duration: reducedMotion ? 0 : 0.18 }}>
|
||||
<AgentHeader
|
||||
theme={theme}
|
||||
hasMessages={messages.length > 0}
|
||||
statusLabel={statusLabel}
|
||||
statusColor={statusColor}
|
||||
nodeCount={nodeCount}
|
||||
@@ -715,6 +719,8 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
|
||||
approval={approval}
|
||||
nodeCount={nodeCount}
|
||||
approvalSubmitting={approvalSubmitting || connectionStatus !== "connected"}
|
||||
onChooseSkill={() => setSkillsOpen(true)}
|
||||
onDraftPrompt={(draft) => setPrompt((current) => current.trim() ? `${current}\n\n${draft}` : draft)}
|
||||
onApprovalReasonChange={(reason) => setApproval((current) => (current ? { ...current, reason } : current))}
|
||||
onApprove={(settings) => void submitApproval("approve", settings)}
|
||||
onReject={() => void submitApproval("reject")}
|
||||
@@ -812,23 +818,20 @@ function AgentLauncher({ theme, statusColor, approvalPending, reducedMotion, onO
|
||||
);
|
||||
}
|
||||
|
||||
function AgentHeader({ theme, statusLabel, statusColor, nodeCount, onNew, onHistory, onSettings, onCollapse, onExport, exporting }: { theme: CanvasTheme; statusLabel: string; statusColor: string; nodeCount: number; onNew: () => void; onHistory: () => void; onSettings: () => void; onCollapse: () => void; onExport: () => void; exporting: boolean }) {
|
||||
function AgentHeader({ theme, hasMessages, statusLabel, statusColor, nodeCount, onNew, onHistory, onSettings, onCollapse, onExport, exporting }: { theme: CanvasTheme; hasMessages: boolean; statusLabel: string; statusColor: string; nodeCount: number; onNew: () => void; onHistory: () => void; onSettings: () => void; onCollapse: () => void; onExport: () => void; exporting: boolean }) {
|
||||
return (
|
||||
<header data-agent-drag-handle className="flex h-[68px] shrink-0 items-center gap-3 px-4" style={{ boxShadow: `inset 0 -1px 0 ${theme.toolbar.border}` }}>
|
||||
<span className="grid size-9 shrink-0 place-items-center rounded-xl" style={{ color: theme.node.text, background: theme.node.fill }}>
|
||||
<Bot className="size-4" />
|
||||
</span>
|
||||
<header data-agent-drag-handle className="agent-panel-header flex shrink-0 items-center gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold">Agent</span>
|
||||
<span className="flex items-center gap-1 text-[11px]" style={{ color: statusColor }}>
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span className="agent-panel-title">{hasMessages ? "画布助手" : "新对话"}</span>
|
||||
<span className="agent-panel-status flex items-center gap-1" style={{ color: statusColor }}>
|
||||
<CircleDot className="size-3" />
|
||||
{statusLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-0.5 text-[11px] opacity-40">当前画布 · {nodeCount} 个节点</div>
|
||||
<div className="agent-panel-context">Agent · 当前画布 {nodeCount} 个节点</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<div className="agent-header-actions flex items-center gap-0.5" style={{ color: theme.node.muted }}>
|
||||
<Button type="text" shape="circle" icon={<Download className="size-4" />} loading={exporting} onClick={onExport} aria-label="导出 Agent 调试记录" title="导出对话、工具参数、审批和错误(分享前请检查隐私)" />
|
||||
<Button type="text" shape="circle" icon={<MessageSquarePlus className="size-4" />} onClick={onNew} aria-label="新建对话" title="新建对话" />
|
||||
<Button type="text" shape="circle" icon={<History className="size-4" />} onClick={onHistory} aria-label="历史对话" title="历史对话" />
|
||||
@@ -842,7 +845,7 @@ function AgentHeader({ theme, statusLabel, statusColor, nodeCount, onNew, onHist
|
||||
function AgentHistory({ conversations, activeConversationId, theme, onBack, onNew, onOpen, onDelete }: { conversations: CloudAgentConversation[]; activeConversationId: string; theme: CanvasTheme; onBack: () => void; onNew: () => void; onOpen: (conversation: CloudAgentConversation) => void; onDelete: (id: string) => void }) {
|
||||
return (
|
||||
<div className="canvas-agent-history-root flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<header data-agent-drag-handle className="flex h-[68px] shrink-0 items-center gap-2 px-3" style={{ boxShadow: `inset 0 -1px 0 ${theme.toolbar.border}` }}>
|
||||
<header data-agent-drag-handle className="agent-panel-header flex shrink-0 items-center gap-2">
|
||||
<Button type="text" shape="circle" icon={<ArrowLeft className="size-4" />} onClick={onBack} aria-label="返回对话" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-semibold">历史对话</div>
|
||||
@@ -891,6 +894,8 @@ function AgentConversation({
|
||||
approval,
|
||||
approvalSubmitting,
|
||||
nodeCount,
|
||||
onChooseSkill,
|
||||
onDraftPrompt,
|
||||
onFocusNode,
|
||||
onApprovalReasonChange,
|
||||
onApprove,
|
||||
@@ -903,11 +908,14 @@ function AgentConversation({
|
||||
approval: ApprovalState | null;
|
||||
approvalSubmitting: boolean;
|
||||
nodeCount: number;
|
||||
onChooseSkill: () => void;
|
||||
onDraftPrompt: (prompt: string) => void;
|
||||
onFocusNode?: (nodeId: string) => void;
|
||||
onApprovalReasonChange: (reason: string) => void;
|
||||
onApprove: (settings?: AgentMediaSettings) => void;
|
||||
onReject: () => void;
|
||||
}) {
|
||||
const brandName = useAppearanceStore((state) => state.appearance.brandName);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const followRef = useRef(true);
|
||||
@@ -934,12 +942,12 @@ function AgentConversation({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={scrollRef} data-agent-conversation className="thin-scrollbar min-h-0 flex-1 overflow-y-auto px-5 py-5" onScroll={(event) => {
|
||||
<div ref={scrollRef} data-agent-conversation className="agent-conversation thin-scrollbar min-h-0 flex-1 overflow-y-auto" onScroll={(event) => {
|
||||
const element = event.currentTarget;
|
||||
followRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 48;
|
||||
}}>
|
||||
{!messages.length ? <AgentEmptyState theme={theme} nodeCount={nodeCount} /> : null}
|
||||
<div ref={contentRef} className="space-y-2.5">
|
||||
{!messages.length ? <AgentWelcome brandName={brandName} nodeCount={nodeCount} onChooseSkill={onChooseSkill} onDraftPrompt={onDraftPrompt} /> : null}
|
||||
<div ref={contentRef} className="agent-conversation-messages">
|
||||
{messages.map((item) => (
|
||||
<AgentChatMessage key={item.id} item={item} theme={theme} references={references} onFocusNode={onFocusNode} isStreaming={busy && !approval && item.streaming === true && item === messages.at(-1)} />
|
||||
))}
|
||||
@@ -952,18 +960,6 @@ function AgentConversation({
|
||||
);
|
||||
}
|
||||
|
||||
function AgentEmptyState({ theme, nodeCount }: { theme: CanvasTheme; nodeCount: number }) {
|
||||
return (
|
||||
<div className="flex h-full min-h-72 flex-col items-center justify-center px-8 pb-8 text-center">
|
||||
<div className="grid size-12 place-items-center rounded-2xl" style={{ background: theme.node.fill, color: theme.node.muted }}>
|
||||
<Bot className="size-6" />
|
||||
</div>
|
||||
<h2 className="mt-5 text-lg font-semibold">现在想做什么?</h2>
|
||||
<p className="mt-2 max-w-64 text-xs leading-5" style={{ color: theme.node.muted }}>当前画布有 {nodeCount} 个节点。Agent 可以按权限读取画布、调用已启用 Skill、整理文本节点并提交受预算约束的生成任务。</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ComposerControls({
|
||||
reasoningMode,
|
||||
reasoningSupported,
|
||||
@@ -992,6 +988,7 @@ function ComposerControls({
|
||||
selectedSkillCount: number;
|
||||
}) {
|
||||
const permissionVisual = agentPermissionVisual(permissionMode);
|
||||
const PermissionIcon = permissionVisual.icon;
|
||||
return (
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-0.5">
|
||||
<ModelPicker
|
||||
@@ -1000,7 +997,7 @@ function ComposerControls({
|
||||
capability="text"
|
||||
onChange={onModelChange}
|
||||
variant="creation"
|
||||
className="!h-8 !min-w-0 !w-36 !max-w-full !border-0 !bg-transparent !px-1.5 !shadow-none"
|
||||
className="!h-8 !min-w-0 !w-52 !max-w-full !border-0 !bg-transparent !px-1.5 !shadow-none"
|
||||
popoverClassName="agent-model-picker-popover"
|
||||
showSelectedPrice={false}
|
||||
showOptionPrices
|
||||
@@ -1014,12 +1011,12 @@ function ComposerControls({
|
||||
<Dropdown trigger={["click"]} placement="topLeft" menu={{ items: agentPermissionMenuItems(permissionMode, onPermissionChange) }}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-8 shrink-0 items-center gap-1 rounded-md px-2 text-[11px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/25"
|
||||
className="grid size-8 shrink-0 place-items-center rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current/25"
|
||||
style={{ color: theme.node.muted, background: "transparent" }}
|
||||
aria-label="选择 Agent 执行权限"
|
||||
aria-label={`执行权限:${agentPermissionLabel(permissionMode)},点击切换`}
|
||||
title={`执行权限:${agentPermissionLabel(permissionMode)},点击切换`}
|
||||
>
|
||||
<ShieldCheck className="size-3.5" style={{ color: permissionVisual.color }} />
|
||||
<span className="max-w-20 truncate">{agentPermissionLabel(permissionMode)}</span>
|
||||
<PermissionIcon className="size-3.5" style={{ color: permissionVisual.color }} aria-hidden="true" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
<button
|
||||
@@ -1033,7 +1030,7 @@ function ComposerControls({
|
||||
onClick={() => onSkillsOpenChange(true)}
|
||||
>
|
||||
<Sparkles className="size-3.5" />
|
||||
<span className="max-w-28 truncate">Skills技能包({selectedSkillCount})</span>
|
||||
<span className="max-w-28 truncate">Skills({selectedSkillCount})</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -76,7 +76,7 @@ export function CanvasCloudAgentSettings(props: AgentSettingsProps) {
|
||||
|
||||
return (
|
||||
<div className="canvas-agent-settings flex min-h-0 min-w-0 flex-1 flex-col" style={{ background: theme.node.panel }}>
|
||||
<header data-agent-drag-handle className="flex h-[68px] shrink-0 items-center gap-2 px-3" style={{ boxShadow: `inset 0 -1px 0 ${theme.toolbar.border}` }}>
|
||||
<header data-agent-drag-handle className="agent-panel-header flex shrink-0 items-center gap-2">
|
||||
<Button type="text" shape="circle" icon={<ArrowLeft className="size-4" />} onClick={section === "home" ? props.onBack : goHome} aria-label={section === "home" ? "返回对话" : "返回设置"} />
|
||||
<div className="min-w-0 flex-1"><div className="text-sm font-semibold">{title}</div><div className="mt-0.5 text-[11px] opacity-40">{section === "home" ? "只影响下一次新运行" : sectionSubtitle(section)}</div></div>
|
||||
{section !== "home" ? <span className="rounded-full px-2 py-1 text-[10px] opacity-50" style={{ background: theme.node.fill }}>{section === "skills" ? `${props.selectedSkillIds.length} 已启用` : "当前 Agent"}</span> : null}
|
||||
@@ -317,5 +317,5 @@ function sectionTitle(section: SettingsSection) { return section === "profile" ?
|
||||
function sectionSubtitle(section: SettingsSection) { return section === "profile" ? "用户、项目和画布的长期行为偏好" : section === "memories" ? "只属于你,批准后才会注入会话" : section === "skills" ? "搜索、安装并选择本轮技能" : section === "mcp" ? "云端工具与连接状态" : section === "context" ? "控制 Agent 能读取的范围" : "控制本轮积分与生成消耗"; }
|
||||
|
||||
export function agentPermissionLabel(mode: AgentPermissionMode) { return permissionOptions.find((option) => option.value === mode)?.label || "请求审批"; }
|
||||
export function agentPermissionVisual(mode: AgentPermissionMode) { const option = permissionOptions.find((item) => item.value === mode) || permissionOptions[0]; return { color: option.color, soft: `${option.color}1f` }; }
|
||||
export function agentPermissionVisual(mode: AgentPermissionMode) { const option = permissionOptions.find((item) => item.value === mode) || permissionOptions[0]; return { color: option.color, soft: `${option.color}1f`, icon: option.icon }; }
|
||||
export function agentPermissionMenuItems(mode: AgentPermissionMode, onChange: (mode: AgentPermissionMode) => void) { return permissionOptions.map((option) => ({ key: option.value, label: <span>{option.label}</span>, icon: mode === option.value ? <Check className="size-3.5" style={{ color: option.color }} /> : <option.icon className="size-3.5" style={{ color: option.color }} />, onClick: () => onChange(option.value) })); }
|
||||
|
||||
@@ -1,6 +1,242 @@
|
||||
.canvas-agent-panel {
|
||||
/* Canvas theme -> material tokens. Only the floating shell blurs the canvas. */
|
||||
--agent-material-solid: color-mix(in srgb, var(--agent-ink) 3%, var(--agent-surface-base));
|
||||
--agent-material-glass: color-mix(in srgb, var(--agent-material-solid) 68%, transparent);
|
||||
--agent-material-sheen: color-mix(in srgb, var(--color-neutral-0) 8%, transparent);
|
||||
--agent-surface-soft: color-mix(in srgb, var(--agent-ink) 4%, transparent);
|
||||
--agent-surface-hover: color-mix(in srgb, var(--agent-ink) 7%, transparent);
|
||||
--agent-surface-raised: color-mix(in srgb, var(--agent-ink) 6%, var(--agent-material-solid));
|
||||
--agent-caption: color-mix(in srgb, var(--agent-ink) 62%, var(--agent-surface-base));
|
||||
--agent-radius-shell: calc(var(--radius) * 3.5);
|
||||
--agent-radius-surface: calc(var(--radius) * 2.5);
|
||||
--agent-material-shadow: 0 24px 80px var(--agent-shadow-color);
|
||||
--agent-focus-ring: color-mix(in srgb, var(--agent-accent) 65%, var(--agent-ink));
|
||||
container-type: inline-size;
|
||||
overscroll-behavior: contain;
|
||||
isolation: isolate;
|
||||
border: 0;
|
||||
border-radius: var(--agent-radius-shell);
|
||||
background: var(--agent-material-solid);
|
||||
color: var(--agent-ink);
|
||||
box-shadow: var(--agent-material-shadow);
|
||||
}
|
||||
|
||||
@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
||||
.canvas-agent-panel {
|
||||
background: linear-gradient(145deg, var(--agent-material-sheen), transparent 55%), var(--agent-material-glass);
|
||||
-webkit-backdrop-filter: blur(32px) saturate(145%);
|
||||
backdrop-filter: blur(32px) saturate(145%);
|
||||
}
|
||||
}
|
||||
|
||||
.agent-panel-header {
|
||||
min-height: 80px;
|
||||
padding: var(--space-4) var(--space-5) var(--space-3) var(--space-6);
|
||||
}
|
||||
|
||||
.agent-panel-title {
|
||||
font-size: var(--fs-heading);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.agent-panel-context {
|
||||
margin-top: var(--space-1);
|
||||
color: var(--agent-caption);
|
||||
font-size: var(--fs-label);
|
||||
}
|
||||
|
||||
.agent-panel-status {
|
||||
font-size: var(--fs-label);
|
||||
}
|
||||
|
||||
.agent-header-actions .ant-btn {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.agent-conversation {
|
||||
padding: var(--space-4) var(--space-6) var(--space-6);
|
||||
scroll-padding-block: var(--space-6);
|
||||
}
|
||||
|
||||
.agent-conversation-messages > * + * {
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.agent-message-user {
|
||||
border-radius: var(--agent-radius-surface) var(--agent-radius-surface) var(--space-2) var(--agent-radius-surface);
|
||||
background: var(--agent-surface-hover, color-mix(in srgb, var(--foreground) 7%, transparent));
|
||||
}
|
||||
|
||||
.agent-welcome {
|
||||
display: flex;
|
||||
min-height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-block: var(--space-6);
|
||||
}
|
||||
|
||||
.agent-welcome-intro {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.agent-welcome-orb {
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
margin-bottom: var(--space-5);
|
||||
border-radius: 50%;
|
||||
background:
|
||||
radial-gradient(circle at 32% 24%, var(--color-neutral-0), transparent 36%),
|
||||
radial-gradient(circle at 70% 80%, var(--palette-node-audio), transparent 64%),
|
||||
radial-gradient(circle at 35% 65%, color-mix(in srgb, var(--agent-accent) 65%, var(--color-neutral-0)), var(--agent-accent));
|
||||
box-shadow: inset -4px -6px 14px color-mix(in srgb, var(--agent-accent) 28%, transparent), 0 8px 32px color-mix(in srgb, var(--agent-accent) 14%, transparent);
|
||||
}
|
||||
|
||||
.agent-welcome-intro h2 {
|
||||
margin: 0;
|
||||
color: var(--agent-ink);
|
||||
font-size: var(--fs-title);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.5;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.agent-welcome-intro p {
|
||||
margin: var(--space-2) 0 0;
|
||||
color: var(--agent-caption);
|
||||
font-size: var(--fs-caption);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.agent-welcome-actions {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
gap: var(--space-1);
|
||||
margin-top: var(--space-8);
|
||||
}
|
||||
|
||||
.agent-welcome-actions button {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-3) var(--space-4);
|
||||
border: 0;
|
||||
border-radius: var(--space-4);
|
||||
background: transparent;
|
||||
color: var(--agent-caption);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background-color 160ms ease, color 160ms ease;
|
||||
}
|
||||
|
||||
.agent-welcome-actions button:hover:not(:disabled) {
|
||||
background: var(--agent-surface-soft);
|
||||
color: var(--agent-ink);
|
||||
}
|
||||
|
||||
.agent-welcome-actions button:focus-visible {
|
||||
outline: 2px solid var(--agent-focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.agent-welcome-actions button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.agent-welcome-actions svg {
|
||||
width: var(--space-5);
|
||||
height: var(--space-5);
|
||||
flex: 0 0 auto;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.agent-welcome-actions button > span {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.agent-welcome-actions strong,
|
||||
.agent-welcome-actions small {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.agent-welcome-actions strong {
|
||||
color: var(--agent-ink);
|
||||
font-size: var(--fs-body);
|
||||
}
|
||||
|
||||
.agent-welcome-actions small {
|
||||
margin-top: var(--space-half);
|
||||
font-size: var(--fs-label);
|
||||
}
|
||||
|
||||
.agent-welcome-actions .agent-welcome-arrow {
|
||||
width: var(--space-4);
|
||||
height: var(--space-4);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.agent-welcome-footnote {
|
||||
margin: var(--space-6) 0 0;
|
||||
color: var(--agent-caption);
|
||||
font-size: var(--fs-label);
|
||||
}
|
||||
|
||||
.agent-composer-wrap {
|
||||
padding: var(--space-2) var(--space-4) var(--space-4);
|
||||
}
|
||||
|
||||
.agent-composer-surface {
|
||||
padding: var(--space-4) var(--space-4) var(--space-3);
|
||||
border-radius: var(--agent-radius-surface, var(--radius-xl));
|
||||
background: var(--agent-surface-raised, var(--background));
|
||||
}
|
||||
|
||||
.agent-composer-surface:focus-within {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--agent-accent, var(--foreground)) 35%, transparent);
|
||||
}
|
||||
|
||||
.agent-plan-bar {
|
||||
border: 0;
|
||||
background: var(--agent-surface-soft, color-mix(in srgb, var(--foreground) 4%, transparent));
|
||||
}
|
||||
|
||||
.agent-question-bar {
|
||||
border: 0;
|
||||
background: var(--agent-surface-raised, var(--background));
|
||||
}
|
||||
|
||||
@container (max-width: 400px) {
|
||||
.agent-panel-header {
|
||||
gap: var(--space-2);
|
||||
padding-inline: var(--space-4);
|
||||
}
|
||||
.agent-panel-status { white-space: nowrap; }
|
||||
.agent-conversation { padding-inline: var(--space-4); }
|
||||
.agent-welcome-actions button { padding-inline: var(--space-2); }
|
||||
}
|
||||
|
||||
@media (max-width: 639px) {
|
||||
.canvas-agent-panel { border-end-start-radius: 0; border-end-end-radius: 0; }
|
||||
.agent-composer-wrap { padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
.canvas-agent-panel { background: var(--agent-material-solid); -webkit-backdrop-filter: none; backdrop-filter: none; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-welcome-actions button { transition: none; }
|
||||
}
|
||||
|
||||
/* Agent 浮窗高于普通画布层,模型菜单必须叠在对话窗之上;菜单本身与全站二级选择器同一套。 */
|
||||
@@ -172,16 +408,16 @@
|
||||
|
||||
.agent-tool-action-link {
|
||||
min-width: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
||||
padding: 4px 7px;
|
||||
background: var(--agent-surface-soft, color-mix(in srgb, var(--foreground) 3%, transparent));
|
||||
padding: var(--space-2) var(--space-3);
|
||||
color: color-mix(in srgb, currentColor 88%, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 17px;
|
||||
overflow-wrap: anywhere;
|
||||
text-align: left;
|
||||
transition: border-color 140ms ease, background-color 140ms ease, color 140ms ease;
|
||||
transition: background-color 140ms ease, color 140ms ease;
|
||||
}
|
||||
|
||||
.agent-tool-action-link:not(:disabled) {
|
||||
@@ -189,7 +425,6 @@
|
||||
}
|
||||
|
||||
.agent-tool-action-link:not(:disabled):hover {
|
||||
border-color: color-mix(in srgb, var(--foreground) 26%, transparent);
|
||||
background: color-mix(in srgb, var(--foreground) 7%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
@@ -412,7 +647,7 @@
|
||||
}
|
||||
|
||||
.agent-reasoning-card[open] {
|
||||
background: color-mix(in srgb, var(--agent-reasoning-accent) 3.5%, var(--background));
|
||||
background: var(--agent-surface-soft, color-mix(in srgb, var(--agent-reasoning-accent) 3.5%, var(--background)));
|
||||
}
|
||||
|
||||
.agent-reasoning-summary {
|
||||
@@ -590,11 +825,10 @@
|
||||
.canvas-agent-approval-card {
|
||||
--agent-approval-blue: #2563eb;
|
||||
--agent-approval-blue-hover: #1d4ed8;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 13%, transparent);
|
||||
border-radius: 16px;
|
||||
background: color-mix(in srgb, var(--foreground) 4%, var(--background));
|
||||
padding: 16px;
|
||||
box-shadow: 0 10px 28px color-mix(in srgb, var(--foreground) 5%, transparent);
|
||||
border: 0;
|
||||
border-radius: var(--agent-radius-surface, var(--radius-xl));
|
||||
background: var(--agent-surface-raised, color-mix(in srgb, var(--foreground) 4%, var(--background)));
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.canvas-agent-approval-header {
|
||||
@@ -654,7 +888,7 @@
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 9%, transparent);
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
||||
padding: 9px 10px;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, ConfigProvider } from "antd";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useState, type ReactNode } from "react";
|
||||
|
||||
/** 内容库的浏览控件,不复用后台表格的筛选外框。 */
|
||||
export function CollectionToolbar({ children, trailing, active, onReset, label = "搜索与筛选" }: {
|
||||
@@ -10,8 +10,23 @@ export function CollectionToolbar({ children, trailing, active, onReset, label =
|
||||
onReset?: () => void;
|
||||
label?: string;
|
||||
}) {
|
||||
return <section className="collection-toolbar" aria-label={label}>
|
||||
<div className="collection-toolbar-controls">{children}</div>
|
||||
const [inputModality, setInputModality] = useState<"keyboard" | "pointer">("keyboard");
|
||||
|
||||
return <section
|
||||
className="collection-toolbar"
|
||||
aria-label={label}
|
||||
data-input-modality={inputModality}
|
||||
onPointerDownCapture={() => setInputModality("pointer")}
|
||||
onKeyDownCapture={(event) => {
|
||||
if (!event.altKey && !event.ctrlKey && !event.metaKey) setInputModality("keyboard");
|
||||
}}
|
||||
onBlurCapture={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget)) setInputModality("keyboard");
|
||||
}}
|
||||
>
|
||||
<ConfigProvider select={{ variant: "filled", classNames: { popup: { root: "workspace-quiet-popup" } } }}>
|
||||
<div className="collection-toolbar-controls">{children}</div>
|
||||
</ConfigProvider>
|
||||
<div className="collection-toolbar-actions">
|
||||
{active && onReset ? <Button type="text" icon={<RotateCcw />} onClick={onReset}>重置</Button> : null}
|
||||
{trailing}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.workspace-account-popover .ant-popover-container { padding: 6px; border: 0 !important; border-radius: 20px; background: var(--user-surface-raised) !important; box-shadow: none !important; }
|
||||
.workspace-account-popover .ant-popover-container { padding: var(--space-2); }
|
||||
.workspace-account-popover .workspace-topbar-account-menu { width: auto; min-width: 0; padding: 0; }
|
||||
.workspace-account-card { width: min(304px, calc(100vw - 48px)); color: var(--user-ink); padding: 12px; }
|
||||
.workspace-account-card-identity { display: flex; align-items: center; gap: 12px; padding: 4px 0 16px; }
|
||||
@@ -8,7 +8,7 @@
|
||||
.workspace-account-card-identity strong { font-size: 15px; line-height: 24px; }
|
||||
.workspace-account-card-identity span { color: var(--user-ink-muted); font-size: 12px; }
|
||||
.workspace-account-card-identity em { font-style: normal; font-size: 10px; padding: 4px 8px; border-radius: 999px; background: var(--user-surface-muted); color: var(--user-ink-muted); }
|
||||
.workspace-account-card-wallet { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; padding: 16px; margin-bottom: 12px; border-radius: 16px; background: linear-gradient(120deg, var(--user-accent-soft), color-mix(in srgb, var(--product-accent-blue) 10%, var(--user-surface))); }
|
||||
.workspace-account-card-wallet { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; padding: 16px; margin-bottom: 12px; border-radius: var(--user-radius-card); background: var(--user-surface-muted); }
|
||||
.workspace-account-card-balance > span { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--user-ink-muted); }
|
||||
.workspace-account-card-balance strong { display: block; margin-top: 4px; font-size: 26px; font-weight: 650; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
|
||||
.workspace-account-card-wallet > button { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--user-accent); white-space: nowrap; padding: 6px 0; }
|
||||
|
||||
@@ -27,6 +27,7 @@ export function WorkspaceAccountMenu() {
|
||||
<><Popover
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
arrow={false}
|
||||
rootClassName="workspace-account-popover"
|
||||
open={menuOpen}
|
||||
onOpenChange={setMenuOpen}
|
||||
|
||||
@@ -81,7 +81,7 @@ function WorkspaceSidebarProfile({ collapsed, user }: { collapsed: boolean; user
|
||||
<WorkspaceSidebarCheckin collapsed={collapsed} />
|
||||
<WorkspaceSidebarStorageMeter collapsed={collapsed} />
|
||||
<div className={cn("app-workspace-sidebar-profile-row", collapsed && "is-collapsed")}>
|
||||
<Popover open={menuOpen} onOpenChange={setMenuOpen} trigger="click" placement="topLeft" rootClassName="workspace-account-popover" content={content}>
|
||||
<Popover open={menuOpen} onOpenChange={setMenuOpen} trigger="click" placement="topLeft" arrow={false} rootClassName="workspace-account-popover" content={content}>
|
||||
<button type="button" className={cn("app-workspace-sidebar-profile", collapsed && "is-collapsed")} aria-label="打开账户菜单" title={profileName}>
|
||||
<span className="app-workspace-sidebar-profile-avatar">{avatar}</span>
|
||||
{!collapsed ? <span className="app-workspace-sidebar-profile-copy"><strong>{profileName}</strong><span>创作工作台</span></span> : null}
|
||||
|
||||
Reference in New Issue
Block a user