feat(workbench): refine operational interface
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
|
||||
import {
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
HelpCircle,
|
||||
ListChecks,
|
||||
ListTree,
|
||||
LockKeyhole,
|
||||
Loader2,
|
||||
ShieldCheck,
|
||||
XCircle,
|
||||
type LucideIcon
|
||||
@@ -106,8 +107,7 @@ export function AgentMessageDetails({
|
||||
onRejectQuestion?: (request: AgentQuestionRequest) => Promise<void> | void;
|
||||
}) {
|
||||
const hasDetails = Boolean(
|
||||
message.progress?.length ||
|
||||
message.todos?.todos.length ||
|
||||
message.todos?.todos.length ||
|
||||
message.permissions?.length ||
|
||||
message.questions?.length
|
||||
);
|
||||
@@ -119,11 +119,6 @@ export function AgentMessageDetails({
|
||||
return (
|
||||
<motion.div className="space-y-2" layout variants={agentPanelListVariants} animate="animate">
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{message.progress?.length ? (
|
||||
<motion.div key="progress" layout initial="initial" animate="animate" exit="exit" variants={agentPanelItemVariants}>
|
||||
<ProgressList progress={message.progress} />
|
||||
</motion.div>
|
||||
) : null}
|
||||
{message.todos ? (
|
||||
<motion.div key="todos" layout initial="initial" animate="animate" exit="exit" variants={agentPanelItemVariants}>
|
||||
<TodoCard todoUpdate={message.todos} />
|
||||
@@ -148,6 +143,20 @@ export function AgentMessageDetails({
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentMessageProgress({
|
||||
progress,
|
||||
running
|
||||
}: {
|
||||
progress?: AgentChatProgress[];
|
||||
running: boolean;
|
||||
}) {
|
||||
if (!progress?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ProgressList progress={progress} running={running} />;
|
||||
}
|
||||
|
||||
function AgentNestedBlock({
|
||||
icon: Icon,
|
||||
iconClassName,
|
||||
@@ -185,44 +194,76 @@ function AnimatedDetailItem({ children }: { children: ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ProgressList({ progress }: { progress: AgentChatProgress[] }) {
|
||||
function ProgressList({ progress, running }: { progress: AgentChatProgress[]; running: boolean }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const recentProgress = progress.slice(-3);
|
||||
const visibleProgress = expanded ? progress : running ? recentProgress : [];
|
||||
|
||||
return (
|
||||
<AgentNestedBlock icon={Loader2} iconClassName="text-blue-600" title="执行进度">
|
||||
<motion.ol className="space-y-1.5" variants={agentPanelListVariants} animate="animate">
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{progress.slice(-6).map((item) => (
|
||||
<motion.li
|
||||
key={item.id}
|
||||
layout
|
||||
className="grid grid-cols-[16px_1fr_auto] items-start gap-2 text-xs"
|
||||
initial="initial"
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
variants={agentPanelItemVariants}
|
||||
transition={agentLayoutTransition}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"mt-0.5 h-3.5 w-3.5 rounded-full border",
|
||||
progressStatusMeta[item.status].dotClassName
|
||||
)}
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-semibold text-slate-800" title={item.title}>
|
||||
{item.title}
|
||||
<div className="agent-panel-nested rounded-xl p-2.5">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2 text-left text-xs font-semibold text-slate-600"
|
||||
aria-expanded={expanded}
|
||||
onClick={() => setExpanded((current) => !current)}
|
||||
>
|
||||
<ListTree size={16} className="shrink-0 text-blue-700" aria-hidden="true" />
|
||||
<span>执行进度</span>
|
||||
<span className="flex-1" />
|
||||
<span className="shrink-0 font-normal text-slate-400">
|
||||
{expanded
|
||||
? `全部 ${progress.length} 条`
|
||||
: running
|
||||
? `最近 ${recentProgress.length} 条`
|
||||
: `共 ${progress.length} 条`}
|
||||
</span>
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className={cn("shrink-0 text-slate-400 transition-transform", expanded && "rotate-180")}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{visibleProgress.length ? (
|
||||
<motion.ol
|
||||
className="mt-2 space-y-1.5 overflow-hidden"
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: "auto", opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={agentLayoutTransition}
|
||||
>
|
||||
{visibleProgress.map((item) => (
|
||||
<li key={item.id} className="grid grid-cols-[16px_1fr_auto] items-start gap-2 text-xs">
|
||||
<span
|
||||
className={cn(
|
||||
"mt-0.5 h-3.5 w-3.5 rounded-full border",
|
||||
progressStatusMeta[item.status].dotClassName
|
||||
)}
|
||||
/>
|
||||
<span className="min-w-0">
|
||||
<span className="block truncate font-semibold text-slate-800" title={item.title}>
|
||||
{item.title}
|
||||
</span>
|
||||
{item.detail ? (
|
||||
<span className="block line-clamp-2 whitespace-pre-wrap leading-5 text-slate-500">
|
||||
{item.detail}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
{item.detail ? (
|
||||
<span className="block line-clamp-2 whitespace-pre-wrap leading-5 text-slate-500">{item.detail}</span>
|
||||
) : null}
|
||||
</span>
|
||||
<span className={cn("rounded-full px-2 py-0.5 font-semibold", progressStatusMeta[item.status].className)}>
|
||||
{progressStatusMeta[item.status].label}
|
||||
</span>
|
||||
</motion.li>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
</motion.ol>
|
||||
</AgentNestedBlock>
|
||||
<span
|
||||
className={cn(
|
||||
"rounded-full px-2 py-0.5 font-semibold",
|
||||
progressStatusMeta[item.status].className
|
||||
)}
|
||||
>
|
||||
{progressStatusMeta[item.status].label}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</motion.ol>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -310,7 +351,7 @@ function PermissionRequestCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-white/90 px-2.5 py-2 text-xs">
|
||||
<div className="rounded-lg bg-[var(--glass-menu)] px-2.5 py-2 text-xs">
|
||||
<div className="grid grid-cols-[1fr_auto] gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="truncate font-semibold text-slate-800" title={permission.target ?? permission.permission}>
|
||||
@@ -438,7 +479,7 @@ function QuestionRequestCard({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-white/90 px-2.5 py-2 text-xs">
|
||||
<div className="rounded-lg bg-[var(--glass-menu)] px-2.5 py-2 text-xs">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<span className="min-w-0 truncate font-semibold text-slate-800">
|
||||
{request.questions[0]?.header || "问题"}
|
||||
@@ -556,7 +597,7 @@ function QuestionInput({
|
||||
return (
|
||||
<label
|
||||
key={option.label}
|
||||
className="flex cursor-pointer items-start gap-2 rounded-md border border-slate-200 bg-white/90 px-2 py-1.5 text-slate-600"
|
||||
className="flex cursor-pointer items-start gap-2 rounded-md border border-slate-200 bg-[var(--surface-solid)] px-2 py-1.5 text-slate-600"
|
||||
>
|
||||
<input
|
||||
type={question.multiple ? "checkbox" : "radio"}
|
||||
@@ -579,7 +620,7 @@ function QuestionInput({
|
||||
) : null}
|
||||
{showCustomInput ? (
|
||||
<textarea
|
||||
className="min-h-16 w-full resize-none rounded-md border border-slate-200 bg-white/90 px-2 py-1.5 text-xs leading-5 text-slate-700 outline-none transition focus:border-blue-300 focus:ring-2 focus:ring-blue-100 disabled:opacity-60"
|
||||
className="min-h-16 w-full resize-none rounded-md border border-slate-200 bg-[var(--surface-solid)] px-2 py-1.5 text-xs leading-5 text-slate-700 outline-none transition focus:border-blue-300 focus:ring-2 focus:ring-blue-100 disabled:opacity-60"
|
||||
placeholder="输入回答"
|
||||
value={value.custom}
|
||||
disabled={disabled}
|
||||
|
||||
Reference in New Issue
Block a user