Skip to content

Commit 446959c

Browse files
authored
feat(studio): unify resource detail layouts (#1037)
1 parent ecfd5f8 commit 446959c

95 files changed

Lines changed: 1458 additions & 1448 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/src/cronjobs/CronJobs.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -433,18 +433,14 @@ function Drawer({
433433

434434
function JobList({
435435
jobs,
436-
busyAction,
437436
canCreate,
438437
onCreate,
439438
onSelect,
440-
onRun,
441439
}: {
442440
jobs: CronJob[];
443-
busyAction: string;
444441
canCreate: boolean;
445442
onCreate: () => void;
446443
onSelect: (job: CronJob) => void;
447-
onRun: (job: CronJob) => void;
448444
}) {
449445
return (
450446
<ResourceGrid>
@@ -457,8 +453,6 @@ function JobList({
457453
创建定时任务
458454
</ResourceCreateCard>
459455
{jobs.map((job) => {
460-
const running = cronJobIsRunning(job);
461-
const busy = busyAction.includes(job.jobId);
462456
const schedule = describeCronJobSchedule(job.schedule);
463457
return (
464458
<LibraryResourceCard
@@ -468,16 +462,8 @@ function JobList({
468462
status={<Badge color={job.enabled ? "success" : "secondary"} variant="soft" size="sm" pill>{job.enabled ? "已启用" : "已暂停"}</Badge>}
469463
description={job.prompt}
470464
metadata={[
471-
{ label: "Runtime", value: job.runtimeName || job.agentName, title: `${job.runtimeName} / ${job.agentName}` },
472465
{ label: "执行计划", value: schedule, title: schedule },
473-
{ label: "下次执行", value: job.enabled ? formatCronJobDate(job.nextRunAt) : "已暂停" },
474466
]}
475-
action={{
476-
label: "立即执行",
477-
disabled: busy || running || !job.enabled,
478-
title: running ? "已有执行正在进行" : !job.enabled ? "请先启用任务" : "立即执行",
479-
onClick: () => onRun(job),
480-
}}
481467
detailAction={{ label: "查看详情", onClick: () => onSelect(job) }}
482468
/>
483469
);
@@ -751,7 +737,7 @@ export function CronJobs({ cloudProvider }: CronJobsProps) {
751737
</ResourceToolbar>
752738
{notice ? <div className="cronjobs-banner" role="status"><Alert color="info" variant="soft" description={notice} /></div> : null}
753739
<ResourceResults aria-label="定时任务列表">
754-
{loading && jobs.length === 0 ? <ResourceLoadingState /> : error ? <EmptyMessage className="cronjobs-state" fill="none"><EmptyMessage.Icon color="danger"><Clock /></EmptyMessage.Icon><EmptyMessage.Title color="danger">无法加载定时任务</EmptyMessage.Title><EmptyMessage.Description>{error}</EmptyMessage.Description><EmptyMessage.ActionRow><Button type="button" color="secondary" variant="outline" size="lg" pill={false} onClick={() => void load()}><ArrowRotateCw />重试</Button></EmptyMessage.ActionRow></EmptyMessage> : <JobList jobs={visibleJobs} busyAction={busyAction} canCreate={!loading && runtimes.length > 0} onCreate={() => setDrawerJob(null)} onSelect={(job) => setSelectedId(job.jobId)} onRun={runNow} />}
740+
{loading && jobs.length === 0 ? <ResourceLoadingState /> : error ? <EmptyMessage className="cronjobs-state" fill="none"><EmptyMessage.Icon color="danger"><Clock /></EmptyMessage.Icon><EmptyMessage.Title color="danger">无法加载定时任务</EmptyMessage.Title><EmptyMessage.Description>{error}</EmptyMessage.Description><EmptyMessage.ActionRow><Button type="button" color="secondary" variant="outline" size="lg" pill={false} onClick={() => void load()}><ArrowRotateCw />重试</Button></EmptyMessage.ActionRow></EmptyMessage> : <JobList jobs={visibleJobs} canCreate={!loading && runtimes.length > 0} onCreate={() => setDrawerJob(null)} onSelect={(job) => setSelectedId(job.jobId)} />}
755741
</ResourceResults>
756742
{drawerJob !== undefined ? <Drawer job={drawerJob} runtimes={runtimes} cloudProvider={cloudProvider} busy={busyAction.endsWith(":save")} onClose={() => setDrawerJob(undefined)} onSubmit={submitDrawer} /> : null}
757743
</ResourcePageShell>

frontend/src/ui/AgentWorkspace.css

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -682,59 +682,13 @@
682682
gap: 8px;
683683
}
684684

685-
.aw-head-delete {
686-
min-height: 34px;
687-
display: inline-flex;
688-
align-items: center;
689-
justify-content: center;
690-
gap: 6px;
691-
padding: 0 12px;
692-
border: 1px solid hsl(var(--destructive) / 0.24);
685+
.aw-agent-meta {
686+
flex: 0 0 auto;
687+
padding: 2px 7px;
693688
border-radius: 999px;
694-
background: hsl(var(--destructive) / 0.07);
695-
color: hsl(var(--destructive));
696-
cursor: pointer;
697-
font: inherit;
698-
font-size: 12px;
699-
font-weight: 620;
700-
}
701-
702-
.aw-head-delete:hover:not(:disabled) {
703-
background: hsl(var(--destructive) / 0.12);
704-
}
705-
706-
.aw-head-delete:disabled {
707-
cursor: default;
708-
opacity: 0.46;
709-
}
710-
711-
.aw-head-delete svg {
712-
width: 14px;
713-
height: 14px;
714-
}
715-
716-
.aw-head-delete--draft {
717-
border-color: hsl(var(--border));
718-
background: transparent;
719-
color: hsl(var(--foreground));
720-
}
721-
722-
.aw-head-delete--draft:hover:not(:disabled) {
723-
background: hsl(var(--secondary) / 0.54);
724-
}
725-
726-
.aw-head-delete.studio-update-action {
727-
border: 1px solid hsl(var(--destructive) / 0.34);
728-
background: rgba(255, 255, 255, 0.76);
729-
color: hsl(var(--destructive));
730-
-webkit-backdrop-filter: blur(7px);
731-
backdrop-filter: blur(7px);
732-
}
733-
734-
.aw-head-delete.studio-update-action:hover:not(:disabled) {
735-
border: 1px solid hsl(var(--destructive));
736-
background: hsl(var(--destructive));
737-
color: #fff;
689+
background: hsl(var(--secondary));
690+
color: hsl(var(--muted-foreground));
691+
font-size: 10.5px;
738692
}
739693

740694
.aw-agent-title-row {
@@ -889,6 +843,18 @@
889843
padding: 0 24px 80px;
890844
}
891845

846+
.aw-agent-detail__body .aw-content {
847+
min-width: 0;
848+
overflow: visible;
849+
margin-top: 0;
850+
padding: 0;
851+
}
852+
853+
.aw-agent-detail__body .aw-detail-deployment {
854+
padding-right: 0;
855+
padding-left: 0;
856+
}
857+
892858
.aw-basic-stack {
893859
display: flex;
894860
flex-direction: column;

frontend/src/ui/AgentWorkspace.tsx

Lines changed: 52 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Search,
2121
Trash2,
2222
} from "lucide-react";
23+
import { Button } from "@openai/apps-sdk-ui/components/Button";
2324
import {
2425
deleteAgentFeedbackCases,
2526
createGithubDeliveryRollbackPr,
@@ -74,7 +75,7 @@ import { BUILTIN_TOOLS } from "../create/veadkCatalog";
7475
import type { DeploymentTaskUpdate } from "./ProjectPreview";
7576
import { DeploymentErrorMessage } from "./DeploymentErrorMessage";
7677
import { Markdown } from "./Markdown";
77-
import { PageBackButton } from "./PageBackButton";
78+
import { ResourceDetailLayout } from "./ResourceCollection";
7879
import { StudioConfirmDialog } from "./StudioConfirmDialog";
7980
import { TextShimmer } from "./text-shimmer/TextShimmer";
8081
import "./AgentWorkspace.css";
@@ -2779,7 +2780,7 @@ export function AgentWorkspace({
27792780
<p>未选择智能体</p>
27802781
</main>
27812782
) : (
2782-
<main className={`aw-main${deploymentInProgress ? " is-deploying" : ""}`}>
2783+
<main className={`aw-main${deploymentInProgress ? " is-deploying" : ""}${detailOnly ? " resource-page" : ""}`}>
27832784
{selectedAgent && !selectedAgentInfo && loadingAgentInfo && (
27842785
<div className="aw-detail-loading" role="status" aria-live="polite">
27852786
<div className="aw-detail-loading-card">
@@ -2802,32 +2803,34 @@ export function AgentWorkspace({
28022803
</div>
28032804
</div>
28042805
)}
2805-
<div className="aw-agent-head">
2806-
<div className="aw-agent-heading">
2807-
{detailOnly && onBack ? (
2808-
<PageBackButton label="返回智能体列表" onClick={onBack} />
2809-
) : null}
2810-
<div className="aw-agent-heading-copy">
2811-
<div className="aw-agent-title-row">
2812-
<h2>{selectedName}</h2>
2813-
{displayCurrentVersion != null && (
2814-
<span>v{displayCurrentVersion}</span>
2815-
)}
2816-
{selectedDraft && <span>草稿</span>}
2817-
{selectedAgentUpdateDraft && <span>待更新</span>}
2818-
{!selectedAgent && !selectedDraft && selectedPendingTask && (
2819-
<span>{selectedPendingTask.label}</span>
2820-
)}
2821-
</div>
2822-
<p>{draft.description || (loadingAgentInfo || (detailOnly && !detailAgentInfoResolved) ? "正在读取智能体信息…" : "暂无描述")}</p>
2823-
</div>
2824-
</div>
2825-
{(selectedDraft || selectedAgentUpdateDraft || selectedAgent?.canDelete) && (
2826-
<div className="aw-head-actions">
2806+
<ResourceDetailLayout
2807+
className="aw-agent-detail"
2808+
title={selectedName}
2809+
description={draft.description || (loadingAgentInfo || (detailOnly && !detailAgentInfoResolved) ? "正在读取智能体信息…" : "暂无描述")}
2810+
identitySeed={selectedName}
2811+
backLabel="返回智能体列表"
2812+
onBack={detailOnly ? onBack : undefined}
2813+
meta={(
2814+
<>
2815+
{displayCurrentVersion != null && <span className="aw-agent-meta">v{displayCurrentVersion}</span>}
2816+
{selectedDraft && <span className="aw-agent-meta">草稿</span>}
2817+
{selectedAgentUpdateDraft && <span className="aw-agent-meta">待更新</span>}
2818+
{!selectedAgent && !selectedDraft && selectedPendingTask && (
2819+
<span className="aw-agent-meta">{selectedPendingTask.label}</span>
2820+
)}
2821+
</>
2822+
)}
2823+
actionsClassName="aw-head-actions"
2824+
bodyClassName="aw-agent-detail__body"
2825+
actions={(selectedDraft || selectedAgentUpdateDraft || selectedAgent?.canDelete) ? (
2826+
<>
28272827
{(selectedDraft || selectedAgentUpdateDraft) && (
2828-
<button
2828+
<Button
28292829
type="button"
2830-
className="aw-head-delete aw-head-delete--draft"
2830+
color="danger"
2831+
variant="soft"
2832+
size="lg"
2833+
pill={false}
28312834
onClick={() => {
28322835
const draftToDelete = selectedDraft ?? selectedAgentUpdateDraft;
28332836
if (draftToDelete) deleteSingleDraft(draftToDelete);
@@ -2838,24 +2841,32 @@ export function AgentWorkspace({
28382841
>
28392842
<Trash2 aria-hidden />
28402843
<span>删除草稿</span>
2841-
</button>
2844+
</Button>
28422845
)}
28432846
{selectedAgent?.canDelete && (
2844-
<button
2847+
<Button
28452848
type="button"
2846-
className="aw-head-delete"
2849+
color="danger"
2850+
variant="soft"
2851+
size="lg"
2852+
pill={false}
28472853
onClick={() => void deleteSingleAgent(selectedAgent)}
28482854
disabled={deletingAgents}
28492855
aria-label="删除 Agent"
28502856
title="删除 Agent"
28512857
>
28522858
<Trash2 aria-hidden />
28532859
<span>{deletingAgents ? "删除中…" : "删除 Agent"}</span>
2854-
</button>
2860+
</Button>
28552861
)}
2856-
</div>
2857-
)}
2858-
</div>
2862+
</>
2863+
) : undefined}
2864+
sections={visibleAgentSections.map((item) => ({
2865+
key: item.id,
2866+
label: item.label,
2867+
disabled: deploymentInProgress,
2868+
content: item.id === section ? (
2869+
<>
28592870
{deploymentTask && shouldShowDeploymentTask && (
28602871
<div
28612872
className={`aw-detail-deployment${deploymentInProgress ? " is-running" : ""}`}
@@ -2868,50 +2879,7 @@ export function AgentWorkspace({
28682879
/>
28692880
</div>
28702881
)}
2871-
<nav
2872-
className="aw-agent-tabs"
2873-
aria-label="智能体详情"
2874-
role="tablist"
2875-
>
2876-
{visibleAgentSections.map((item) => (
2877-
<button
2878-
type="button"
2879-
key={item.id}
2880-
id={`agent-${item.id}-tab`}
2881-
className={section === item.id ? "is-active" : ""}
2882-
role="tab"
2883-
aria-selected={section === item.id}
2884-
aria-controls={`agent-${item.id}-panel`}
2885-
tabIndex={section === item.id ? 0 : -1}
2886-
onClick={() => setSection(item.id)}
2887-
onKeyDown={(event) => {
2888-
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
2889-
event.preventDefault();
2890-
const currentIndex = visibleAgentSections.findIndex(
2891-
(sectionItem) => sectionItem.id === item.id,
2892-
);
2893-
const nextIndex = event.key === "Home"
2894-
? 0
2895-
: event.key === "End"
2896-
? visibleAgentSections.length - 1
2897-
: (currentIndex + (event.key === "ArrowRight" ? 1 : -1) + visibleAgentSections.length)
2898-
% visibleAgentSections.length;
2899-
const nextSection = visibleAgentSections[nextIndex];
2900-
setSection(nextSection.id);
2901-
document.getElementById(`agent-${nextSection.id}-tab`)?.focus();
2902-
}}
2903-
>
2904-
{item.label}
2905-
</button>
2906-
))}
2907-
</nav>
2908-
2909-
<div
2910-
className="aw-content"
2911-
id={`agent-${section}-panel`}
2912-
role="tabpanel"
2913-
aria-labelledby={`agent-${section}-tab`}
2914-
>
2882+
<div className="aw-content">
29152883
{section === "basic" && (
29162884
<div className="aw-basic-stack">
29172885
{(detailAgentInfoError || runtimeDetailError) && (
@@ -3746,6 +3714,13 @@ export function AgentWorkspace({
37463714
</span>
37473715
</div>
37483716
)}
3717+
</>
3718+
) : null,
3719+
}))}
3720+
activeSectionKey={section}
3721+
navigationLabel="智能体详情"
3722+
onSectionChange={setSection}
3723+
/>
37493724
</main>
37503725
)}
37513726
</div>

0 commit comments

Comments
 (0)