feat(web): improve media playground controls

This commit is contained in:
2026-05-11 00:40:02 +08:00
parent ada765d90e
commit 9f7c9f6581
4 changed files with 1952 additions and 27 deletions
+609
View File
@@ -322,6 +322,17 @@
padding: 0;
}
.playgroundHero[data-media-board="true"] {
width: 100%;
height: 100%;
min-height: 0;
align-content: stretch;
align-items: stretch;
justify-items: stretch;
overflow: hidden;
padding: 0;
}
.playgroundModeSwitch {
display: inline-flex;
align-self: center;
@@ -868,6 +879,530 @@
color: var(--primary);
}
.mediaSettingsTrigger {
display: inline-flex;
min-height: 34px;
max-width: 260px;
align-items: center;
gap: 7px;
padding: 0 10px;
border: 1px solid var(--input);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-normal);
font-size: var(--font-size-sm);
white-space: nowrap;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
}
.mediaSettingsTrigger span {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.mediaSettingsPanel {
display: grid;
width: min(560px, calc(100vw - 24px));
gap: 12px;
padding: 12px;
border-radius: var(--radius-lg);
}
.mediaSettingsSection {
display: grid;
gap: 8px;
}
.mediaSettingsLabel {
color: var(--muted-foreground);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
}
.mediaAspectGrid {
display: grid;
grid-template-columns: repeat(auto-fill, 44px);
gap: 4px;
justify-content: start;
}
.mediaAspectGrid button {
display: inline-flex;
min-width: 0;
min-height: 46px;
align-items: center;
flex-direction: column;
justify-content: center;
gap: 2px;
padding: 4px 2px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-normal);
font-size: var(--font-size-xs);
}
.mediaAspectGrid button[data-active="true"] {
border-color: var(--primary);
background: color-mix(in srgb, var(--primary) 8%, var(--surface));
color: var(--primary);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent);
}
.mediaAspectIcon {
position: relative;
display: grid;
width: 13px;
height: 13px;
place-items: center;
color: var(--text-normal);
}
.mediaAspectIcon::before {
content: "";
display: block;
border: 1.5px solid currentColor;
border-radius: 3px;
}
.mediaAspectIcon[data-visual="smart"]::before {
width: 12px;
height: 12px;
border-radius: 4px;
}
.mediaAspectIcon[data-visual="smart"] svg {
position: absolute;
width: 8px;
height: 8px;
}
.mediaAspectIcon[data-visual="wide"]::before {
width: 13px;
height: 4px;
}
.mediaAspectIcon[data-visual="landscape"]::before {
width: 12px;
height: 7px;
}
.mediaAspectIcon[data-visual="square"]::before {
width: 10px;
height: 10px;
}
.mediaAspectIcon[data-visual="portrait"]::before {
width: 7px;
height: 12px;
}
.mediaAspectIcon[data-visual="tall"]::before {
width: 5px;
height: 13px;
}
.mediaAspectGrid strong {
overflow: hidden;
max-width: 100%;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
line-height: 1;
text-overflow: ellipsis;
white-space: nowrap;
}
.mediaResolutionSegment,
.mediaOutputModeSegment {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
gap: 6px;
}
.mediaResolutionSegment button,
.mediaOutputModeSegment button {
display: inline-flex;
min-height: 34px;
align-items: center;
justify-content: center;
gap: 6px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-normal);
font-size: var(--font-size-sm);
white-space: nowrap;
}
.mediaResolutionSegment button[data-active="true"],
.mediaOutputModeSegment button[data-active="true"] {
border-color: var(--primary);
background: color-mix(in srgb, var(--primary) 8%, var(--surface));
color: var(--primary);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--primary) 20%, transparent);
}
.mediaResolutionSegment button:disabled,
.mediaOutputModeSegment button:disabled,
.mediaCountGrid button:disabled {
cursor: not-allowed;
opacity: 0.56;
}
.mediaResolutionSegment svg,
.mediaSettingsHint svg {
color: #06a6bd;
}
.mediaSizeRow {
display: grid;
grid-template-columns: minmax(0, 1fr) 24px minmax(0, 1fr) auto;
align-items: center;
gap: 8px;
}
.mediaSizeRow label {
display: grid;
grid-template-columns: 24px minmax(0, 1fr);
align-items: center;
gap: 6px;
min-height: 36px;
padding: 0 8px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-muted);
}
.mediaSizeRow label span,
.mediaSizeRow strong {
color: #607080;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-semibold);
}
.mediaSizeRow .shInput {
border: 0;
background: transparent;
box-shadow: none;
color: var(--text-strong);
font-size: var(--font-size-sm);
text-align: right;
}
.mediaSizeRow > svg {
justify-self: center;
color: #607080;
}
.mediaCountGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
gap: 6px;
}
.mediaCountGrid button {
min-height: 32px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-normal);
font-size: var(--font-size-sm);
}
.mediaCountGrid button[data-active="true"] {
border-color: var(--primary);
background: var(--primary);
color: var(--primary-foreground);
}
.mediaCustomCount {
display: grid;
grid-template-columns: auto 110px;
align-items: center;
justify-content: start;
gap: 10px;
color: var(--text-soft);
font-size: var(--font-size-sm);
}
.mediaUnsupportedNote {
margin: 0;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface-muted);
color: var(--text-soft);
font-size: var(--font-size-sm);
}
.mediaSettingsHint {
display: inline-flex;
width: fit-content;
align-items: center;
gap: 6px;
color: var(--text-soft);
font-size: var(--font-size-sm);
}
.mediaTaskPage {
display: grid;
grid-template-rows: minmax(0, 1fr) auto;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
}
.mediaTaskTimeline {
display: grid;
align-content: start;
gap: 22px;
justify-items: center;
min-height: 0;
overflow-y: auto;
padding: 38px 40px 22px;
}
.mediaTaskTimeline h1 {
width: min(1240px, 100%);
font-size: 30px;
}
.mediaTaskTimeline > .playgroundError {
width: min(1240px, 100%);
}
.mediaTaskItem {
display: grid;
gap: 14px;
width: min(1240px, 100%);
}
.mediaTaskHeader {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 14px;
}
.mediaTaskHeader > div {
display: grid;
gap: 4px;
min-width: 0;
}
.mediaTaskHeader p {
display: flex;
min-width: 0;
align-items: baseline;
gap: 9px;
flex-wrap: wrap;
}
.mediaTaskHeader p span {
color: var(--text-strong);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-medium);
}
.mediaTaskHeader small,
.mediaTaskHeader time {
color: #7a8794;
font-size: var(--font-size-sm);
}
.mediaTaskStatus {
flex: 0 0 auto;
min-height: 28px;
padding: 5px 9px;
border-radius: 999px;
background: var(--surface-muted);
color: var(--text-soft);
font-size: var(--font-size-xs);
}
.mediaTaskStatus[data-status="succeeded"] {
background: #eefdf4;
color: #15803d;
}
.mediaTaskStatus[data-status="failed"],
.mediaTaskStatus[data-status="cancelled"] {
background: #fef2f2;
color: #b91c1c;
}
.mediaPreviewStage {
display: grid;
position: relative;
width: 100%;
justify-items: center;
}
.mediaPreviewStage[data-count="1"] {
min-height: min(62vh, 600px);
max-height: 600px;
align-items: center;
overflow: hidden;
border: 1px solid rgba(15, 23, 42, 0.05);
border-radius: 18px;
background:
linear-gradient(135deg, rgba(248, 250, 252, 0.96), rgba(234, 238, 243, 0.74)),
repeating-linear-gradient(90deg, rgba(15, 23, 42, 0.035) 0 1px, transparent 1px 28px),
repeating-linear-gradient(0deg, rgba(15, 23, 42, 0.03) 0 1px, transparent 1px 28px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);
}
.mediaPreviewStage[data-count="1"]::after {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.18) 44%, rgba(15, 23, 42, 0.035));
content: "";
pointer-events: none;
}
.mediaPreviewBackdrop {
position: absolute;
inset: -28px;
width: calc(100% + 56px);
height: calc(100% + 56px);
opacity: 0.2;
filter: blur(34px) saturate(1.08);
object-fit: cover;
pointer-events: none;
transform: scale(1.04);
}
.mediaGrid {
--media-grid-columns: 1;
display: grid;
position: relative;
z-index: 1;
grid-template-columns: repeat(var(--media-grid-columns), minmax(0, 1fr));
gap: 2px;
width: 100%;
max-width: var(--media-grid-max-width, 100%);
max-height: 600px;
justify-self: center;
overflow-y: auto;
overscroll-behavior: contain;
}
.mediaTile {
position: relative;
display: grid;
min-width: 0;
min-height: 210px;
max-height: 600px;
aspect-ratio: var(--media-result-aspect, 1 / 1);
place-items: center;
overflow: hidden;
border-radius: 2px;
background: #eef0f2;
}
.mediaTile[data-count="1"] {
min-height: min(62vh, 600px);
box-shadow: 0 18px 44px rgba(15, 23, 42, 0.14);
}
.mediaTile[data-count="2"] {
min-height: min(48vh, 520px);
}
.mediaTile img,
.mediaTile video {
width: 100%;
height: 100%;
object-fit: cover;
}
.mediaLoading,
.mediaEmptyTile {
position: absolute;
inset: 0;
display: grid;
place-items: center;
gap: 10px;
overflow: hidden;
background: linear-gradient(110deg, #edf0f2 0%, #f7f8f9 42%, #e6eaee 78%);
background-size: 220% 100%;
color: #6b7280;
font-size: var(--font-size-sm);
animation: mediaLoadingShimmer 1.35s ease-in-out infinite;
}
.mediaLoading svg {
animation: mediaLoadingSpin 0.9s linear infinite;
}
.mediaEmptyTile {
animation: none;
background: #eef0f2;
}
.mediaTaskError {
width: fit-content;
max-width: 100%;
padding: 8px 10px;
border: 1px solid #fecaca;
border-radius: var(--radius-md);
background: #fef2f2;
color: #991b1b;
font-size: var(--font-size-sm);
}
.mediaTaskActions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.mediaTaskActions > span {
display: inline-flex;
align-items: center;
gap: 5px;
margin-left: auto;
color: var(--text-soft);
font-size: var(--font-size-sm);
}
.mediaTaskActions > span svg {
width: 14px;
height: 14px;
color: #607080;
}
.mediaComposerDock {
padding: 16px 40px 28px;
background: linear-gradient(180deg, rgba(250, 250, 250, 0), var(--surface-subtle) 26%);
}
.mediaComposerDock .playgroundComposer {
width: min(1240px, 100%);
min-height: 190px;
margin: 0 auto;
border-radius: 26px;
}
@keyframes mediaLoadingSpin {
to {
transform: rotate(360deg);
}
}
@keyframes mediaLoadingShimmer {
0% {
background-position: 140% 0;
}
100% {
background-position: -80% 0;
}
}
@media (max-width: 1180px) {
.publicWorksMasonry {
column-count: 3;
@@ -954,6 +1489,61 @@
flex-direction: column;
}
.mediaSettingsTrigger,
.composerFooter .playgroundModelSelect,
.composerFooter .playgroundApiKeySelect,
.playgroundModelSelect,
.playgroundApiKeySelect {
width: 100%;
max-width: 100%;
}
.mediaSettingsPanel {
max-height: calc(100vh - 32px);
overflow-y: auto;
padding: 12px;
border-radius: var(--radius-lg);
}
.mediaAspectGrid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.mediaResolutionSegment button {
min-height: 34px;
font-size: var(--font-size-sm);
}
.mediaSizeRow {
grid-template-columns: 1fr;
}
.mediaSizeRow > svg,
.mediaSizeRow > strong {
display: none;
}
.mediaTaskTimeline {
padding: 30px 18px 18px;
}
.mediaGrid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.mediaGrid[data-count="1"] {
grid-template-columns: 1fr;
}
.mediaTile[data-count="1"],
.mediaTile[data-count="2"] {
min-height: auto;
}
.mediaComposerDock {
padding: 12px 18px 18px;
}
.composerQuickPrompts {
flex-wrap: wrap;
}
@@ -964,6 +1554,25 @@
}
@media (max-width: 560px) {
.mediaAspectGrid,
.mediaCountGrid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.mediaGrid {
grid-template-columns: 1fr;
}
.mediaTaskHeader {
align-items: flex-start;
flex-direction: column;
}
.mediaTaskActions > span {
width: 100%;
margin-left: 0;
}
.publicWorksMasonry {
column-count: 1;
}