Merge pull request 'feat: 多页面UI打磨 — 设置面板、状态反馈与样式升级' (#35) from feat/ui-polish-and-skills into master
Web Quality / verify (push) Has been cancelled
Web Quality / verify (push) Has been cancelled
Reviewed-on: #35
This commit was merged in pull request #35.
This commit is contained in:
@@ -8,6 +8,7 @@ interface BeforeAfterCompareProps {
|
||||
sourceAlt?: string;
|
||||
resultAlt?: string;
|
||||
className?: string;
|
||||
aspectRatio?: string;
|
||||
onSourceLoad?: (width: number, height: number) => void;
|
||||
}
|
||||
|
||||
@@ -26,6 +27,7 @@ export default function BeforeAfterCompare({
|
||||
sourceAlt = "原图",
|
||||
resultAlt = "结果",
|
||||
className = "",
|
||||
aspectRatio,
|
||||
onSourceLoad,
|
||||
}: BeforeAfterCompareProps) {
|
||||
const stageRef = useRef<HTMLDivElement>(null);
|
||||
@@ -43,7 +45,10 @@ export default function BeforeAfterCompare({
|
||||
<div
|
||||
ref={stageRef}
|
||||
className={`before-after-compare ${className}`}
|
||||
style={{ "--compare-position": `${position}%` } as CSSProperties}
|
||||
style={{
|
||||
"--compare-position": `${position}%`,
|
||||
...(aspectRatio ? { "--compare-aspect-ratio": aspectRatio } : {}),
|
||||
} as CSSProperties}
|
||||
aria-label="前后对比"
|
||||
>
|
||||
<div className="before-after-compare__layer before-after-compare__layer--source">
|
||||
|
||||
@@ -281,6 +281,94 @@ function CharacterMixPage({
|
||||
}
|
||||
};
|
||||
|
||||
const clearCharacterAsset = () => {
|
||||
if (characterPreview) URL.revokeObjectURL(characterPreview);
|
||||
setCharacterFile("");
|
||||
setCharacterPreview("");
|
||||
setCharacterDataUrl("");
|
||||
setFaceHint(null);
|
||||
if (characterInputRef.current) characterInputRef.current.value = "";
|
||||
setNotice("已移除人物图");
|
||||
};
|
||||
|
||||
const clearReferenceVideo = () => {
|
||||
if (videoPreview) URL.revokeObjectURL(videoPreview);
|
||||
setVideoFile("");
|
||||
setVideoPreview("");
|
||||
setVideoDataUrl("");
|
||||
if (videoInputRef.current) videoInputRef.current.value = "";
|
||||
setNotice("已移除参考视频");
|
||||
};
|
||||
|
||||
const characterMixSettingsPanel = (
|
||||
<div className="studio-panel__section character-mix-settings-panel">
|
||||
<div className="studio-panel__section-head">
|
||||
<span className="studio-panel__section-title">迁移设置</span>
|
||||
</div>
|
||||
<div className="studio-panel__section-body">
|
||||
<div className="character-mix-prompt-field">
|
||||
<div className="studio-label">驱动提示词</div>
|
||||
<textarea
|
||||
value={promptInput}
|
||||
onChange={(e) => setPromptInput(e.target.value)}
|
||||
placeholder="保持角色原有服装,动作流畅自然"
|
||||
rows={3}
|
||||
maxLength={1000}
|
||||
/>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">图像检测</span>
|
||||
<span className="studio-toggle-row__desc">检测人物朝向</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${checkImage ? " is-on" : ""}`} onClick={() => setCheckImage(!checkImage)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
{checkImage && characterPreview && faceHint && (
|
||||
<div className={`character-mix-face-hint character-mix-face-hint--${faceHint}`}>
|
||||
{faceHint === "analyzing" ? (
|
||||
<>
|
||||
<InfoCircleOutlined />
|
||||
<span>正在分析人物图像...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircleOutlined />
|
||||
<span>图像已就绪,将自动检测人物面部朝向</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">水印输出</span>
|
||||
<span className="studio-toggle-row__desc">含水印版本</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${watermark ? " is-on" : ""}`} onClick={() => setWatermark(!watermark)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="studio-generate-btn" onClick={() => void handleCreateTask()} disabled={isCreating || !characterDataUrl || !videoDataUrl}>
|
||||
{isCreating ? <LoadingOutlined /> : <PlayCircleOutlined />}
|
||||
{isCreating ? "生成中..." : "开始迁移"}
|
||||
</button>
|
||||
{resultUrl && (
|
||||
<div className="studio-result-actions">
|
||||
<button type="button" onClick={() => void handleDownloadResult()} disabled={isDownloadingResult}>
|
||||
<DownloadOutlined />
|
||||
{isDownloadingResult ? "保存中" : "保存本地"}
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleAddResultToAssets()} disabled={isSavingResultAsset}>
|
||||
<InboxOutlined />
|
||||
{isSavingResultAsset ? "加入中" : "加入资产库"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="image-workbench-page character-mix-page" aria-label="角色迁移">
|
||||
<header className="image-workbench-topbar">
|
||||
@@ -339,8 +427,10 @@ function CharacterMixPage({
|
||||
|
||||
<StudioToolLayout
|
||||
noTop
|
||||
noRight
|
||||
leftPanel={
|
||||
<div
|
||||
className="character-mix-source-panel"
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
@@ -389,6 +479,20 @@ function CharacterMixPage({
|
||||
<strong>{characterFile || "上传人物图"}</strong>
|
||||
<small>单人正面或半身更稳定</small>
|
||||
</span>
|
||||
{characterPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
className="studio-upload-slot__remove"
|
||||
aria-label="移除人物图"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearCharacterAsset();
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -427,9 +531,24 @@ function CharacterMixPage({
|
||||
<strong>{videoFile || "上传参考视频"}</strong>
|
||||
<small>MP4 / MOV / AVI</small>
|
||||
</span>
|
||||
{videoPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
className="studio-upload-slot__remove"
|
||||
aria-label="移除参考视频"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearReferenceVideo();
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{characterMixSettingsPanel}
|
||||
</div>
|
||||
}
|
||||
canvas={
|
||||
@@ -480,80 +599,6 @@ function CharacterMixPage({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
rightPanel={
|
||||
<div className="studio-panel__section">
|
||||
<div className="studio-panel__section-head">
|
||||
<span className="studio-panel__section-title">迁移设置</span>
|
||||
</div>
|
||||
<div className="studio-panel__section-body">
|
||||
<div style={{ marginBottom: 10 }}>
|
||||
<div className="studio-label" style={{ fontSize: 11, color: "var(--fg-muted, #999)", marginBottom: 4 }}>驱动提示词</div>
|
||||
<textarea
|
||||
value={promptInput}
|
||||
onChange={(e) => setPromptInput(e.target.value)}
|
||||
placeholder="保持角色原有服装,动作流畅自然"
|
||||
rows={3}
|
||||
maxLength={1000}
|
||||
style={{
|
||||
width: "100%", resize: "vertical", background: "var(--bg-elevated, #1a1a1a)",
|
||||
border: "1px solid var(--border-subtle, #333)", borderRadius: 6,
|
||||
padding: "6px 8px", fontSize: 12, color: "var(--fg-body, #eee)",
|
||||
fontFamily: "inherit", outline: "none", boxSizing: "border-box",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">图像检测</span>
|
||||
<span className="studio-toggle-row__desc">检测人物朝向</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${checkImage ? " is-on" : ""}`} onClick={() => setCheckImage(!checkImage)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
{checkImage && characterPreview && faceHint && (
|
||||
<div className={`character-mix-face-hint character-mix-face-hint--${faceHint}`}>
|
||||
{faceHint === "analyzing" ? (
|
||||
<>
|
||||
<InfoCircleOutlined />
|
||||
<span>正在分析人物图像...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircleOutlined />
|
||||
<span>图像已就绪,将自动检测人物面部朝向</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">水印输出</span>
|
||||
<span className="studio-toggle-row__desc">含水印版本</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${watermark ? " is-on" : ""}`} onClick={() => setWatermark(!watermark)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="studio-generate-btn" onClick={() => void handleCreateTask()} disabled={isCreating || !characterDataUrl || !videoDataUrl}>
|
||||
{isCreating ? <LoadingOutlined /> : <PlayCircleOutlined />}
|
||||
{isCreating ? "生成中..." : "开始迁移"}
|
||||
</button>
|
||||
{resultUrl && (
|
||||
<div className="studio-result-actions">
|
||||
<button type="button" onClick={() => void handleDownloadResult()} disabled={isDownloadingResult}>
|
||||
<DownloadOutlined />
|
||||
{isDownloadingResult ? "保存中" : "保存本地"}
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleAddResultToAssets()} disabled={isSavingResultAsset}>
|
||||
<InboxOutlined />
|
||||
{isSavingResultAsset ? "加入中" : "加入资产库"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
statusBar={
|
||||
<>
|
||||
<span className="studio-status-bar__badge studio-status-bar__badge--idle">就绪</span>
|
||||
|
||||
@@ -206,6 +206,24 @@ function DigitalHumanPage({
|
||||
}
|
||||
};
|
||||
|
||||
const clearImageAsset = () => {
|
||||
if (imagePreview) URL.revokeObjectURL(imagePreview);
|
||||
setImageName("");
|
||||
setImageFile(null);
|
||||
setImagePreview("");
|
||||
if (imageInputRef.current) imageInputRef.current.value = "";
|
||||
setNotice("已移除参考人像");
|
||||
};
|
||||
|
||||
const clearAudioAsset = () => {
|
||||
if (audioPreview) URL.revokeObjectURL(audioPreview);
|
||||
setAudioName("");
|
||||
setAudioFile(null);
|
||||
setAudioPreview("");
|
||||
if (audioInputRef.current) audioInputRef.current.value = "";
|
||||
setNotice("已移除音频源");
|
||||
};
|
||||
|
||||
const handleDownloadResult = async () => {
|
||||
if (!resultVideoUrl || isDownloadingResult) return;
|
||||
setIsDownloadingResult(true);
|
||||
@@ -418,6 +436,76 @@ function DigitalHumanPage({
|
||||
}
|
||||
};
|
||||
|
||||
const digitalHumanSettingsPanel = (
|
||||
<div className="studio-panel__section digital-human-settings-panel">
|
||||
<div className="studio-panel__section-head">
|
||||
<span className="studio-panel__section-title">参数</span>
|
||||
</div>
|
||||
<div className="studio-panel__section-body">
|
||||
<div className="digital-human-prompt-field">
|
||||
<div className="studio-label">提示词</div>
|
||||
<textarea
|
||||
value={promptInput}
|
||||
onChange={(e) => setPromptInput(e.target.value)}
|
||||
placeholder="例如:自然微笑,边说边轻微点头"
|
||||
rows={3}
|
||||
maxLength={2500}
|
||||
/>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">去水印</span>
|
||||
<span className="studio-toggle-row__desc">生成无水印预览</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${!watermark ? " is-on" : ""}`} onClick={() => setWatermark(!watermark)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">保留原声</span>
|
||||
<span className="studio-toggle-row__desc">音频作为驱动源</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${keepOriginalAudio ? " is-on" : ""}`} onClick={() => setKeepOriginalAudio(!keepOriginalAudio)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="studio-generate-btn" onClick={() => void handleCreateTask()} disabled={isCreating || !imageFile || !audioFile}>
|
||||
<PlayCircleOutlined />
|
||||
{isCreating ? "生成中..." : "开始生成"}
|
||||
</button>
|
||||
{isCreating && (
|
||||
<button type="button" className="studio-generate-btn digital-human-cancel-btn" onClick={handleCancel} aria-label="取消生成任务">
|
||||
<CloseCircleOutlined />
|
||||
取消生成
|
||||
</button>
|
||||
)}
|
||||
{resultVideoUrl && (
|
||||
<button type="button" className="studio-generate-btn" onClick={() => {
|
||||
setResultVideoUrl("");
|
||||
setActiveTaskId("");
|
||||
setTaskProgress(0);
|
||||
setNotice("已清空工作区");
|
||||
}}>
|
||||
清空
|
||||
</button>
|
||||
)}
|
||||
{resultVideoUrl && (
|
||||
<div className="studio-result-actions">
|
||||
<button type="button" onClick={() => void handleDownloadResult()} disabled={isDownloadingResult}>
|
||||
<DownloadOutlined />
|
||||
{isDownloadingResult ? "保存中" : "保存本地"}
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleAddResultToAssets()} disabled={isSavingResultAsset}>
|
||||
<InboxOutlined />
|
||||
{isSavingResultAsset ? "加入中" : "加入资产库"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="image-workbench-page digital-human-page" aria-label="数字人">
|
||||
<header className="image-workbench-topbar">
|
||||
@@ -476,8 +564,10 @@ function DigitalHumanPage({
|
||||
|
||||
<StudioToolLayout
|
||||
noTop
|
||||
noRight
|
||||
leftPanel={
|
||||
<div
|
||||
className="digital-human-source-panel"
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
@@ -523,6 +613,20 @@ function DigitalHumanPage({
|
||||
<strong>{imageName || "上传参考图"}</strong>
|
||||
<small>PNG / JPG / WEBP</small>
|
||||
</span>
|
||||
{imagePreview ? (
|
||||
<button
|
||||
type="button"
|
||||
className="studio-upload-slot__remove"
|
||||
aria-label="移除参考人像"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearImageAsset();
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -558,10 +662,26 @@ function DigitalHumanPage({
|
||||
<strong>{audioName || "上传音频"}</strong>
|
||||
<small>MP3 / WAV / M4A,建议 5 分钟内</small>
|
||||
</span>
|
||||
{audioPreview ? (
|
||||
<button
|
||||
type="button"
|
||||
className="studio-upload-slot__remove"
|
||||
aria-label="移除音频源"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clearAudioAsset();
|
||||
}}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</label>
|
||||
{audioPreview ? <audio src={audioPreview} controls className="studio-audio-preview" /> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{digitalHumanSettingsPanel}
|
||||
</div>
|
||||
}
|
||||
canvas={
|
||||
@@ -596,83 +716,6 @@ function DigitalHumanPage({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
rightPanel={
|
||||
<>
|
||||
<div className="studio-panel__section">
|
||||
<div className="studio-panel__section-head">
|
||||
<span className="studio-panel__section-title">参数</span>
|
||||
</div>
|
||||
<div className="studio-panel__section-body">
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<div className="studio-label" style={{ fontSize: 11, color: "var(--fg-muted, #999)", marginBottom: 4 }}>提示词</div>
|
||||
<textarea
|
||||
value={promptInput}
|
||||
onChange={(e) => setPromptInput(e.target.value)}
|
||||
placeholder="例如:自然微笑,边说边轻微点头"
|
||||
rows={3}
|
||||
maxLength={2500}
|
||||
style={{
|
||||
width: "100%", resize: "vertical", background: "var(--bg-elevated, #1a1a1a)",
|
||||
border: "1px solid var(--border-subtle, #333)", borderRadius: 6,
|
||||
padding: "6px 8px", fontSize: 12, color: "var(--fg-body, #eee)",
|
||||
fontFamily: "inherit", outline: "none", boxSizing: "border-box",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">去水印</span>
|
||||
<span className="studio-toggle-row__desc">生成无水印预览</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${!watermark ? " is-on" : ""}`} onClick={() => setWatermark(!watermark)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="studio-toggle-row">
|
||||
<div className="studio-toggle-row__copy">
|
||||
<span className="studio-toggle-row__title">保留原声</span>
|
||||
<span className="studio-toggle-row__desc">音频作为驱动源</span>
|
||||
</div>
|
||||
<button type="button" className={`studio-toggle${keepOriginalAudio ? " is-on" : ""}`} onClick={() => setKeepOriginalAudio(!keepOriginalAudio)}>
|
||||
<span className="studio-toggle__thumb" />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="studio-generate-btn" onClick={() => void handleCreateTask()} disabled={isCreating || !imageFile || !audioFile}>
|
||||
<PlayCircleOutlined />
|
||||
{isCreating ? "生成中..." : "开始生成"}
|
||||
</button>
|
||||
{isCreating && (
|
||||
<button type="button" className="studio-generate-btn digital-human-cancel-btn" onClick={handleCancel} aria-label="取消生成任务">
|
||||
<CloseCircleOutlined />
|
||||
取消生成
|
||||
</button>
|
||||
)}
|
||||
{resultVideoUrl && (
|
||||
<button type="button" className="studio-generate-btn" onClick={() => {
|
||||
setResultVideoUrl("");
|
||||
setActiveTaskId("");
|
||||
setTaskProgress(0);
|
||||
setNotice("已清空工作区");
|
||||
}}>
|
||||
清空
|
||||
</button>
|
||||
)}
|
||||
{resultVideoUrl && (
|
||||
<div className="studio-result-actions">
|
||||
<button type="button" onClick={() => void handleDownloadResult()} disabled={isDownloadingResult}>
|
||||
<DownloadOutlined />
|
||||
{isDownloadingResult ? "保存中" : "保存本地"}
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleAddResultToAssets()} disabled={isSavingResultAsset}>
|
||||
<InboxOutlined />
|
||||
{isSavingResultAsset ? "加入中" : "加入资产库"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
statusBar={
|
||||
<>
|
||||
<span className="studio-status-bar__badge studio-status-bar__badge--running">预览</span>
|
||||
|
||||
@@ -13,13 +13,11 @@ import "../../styles/pages/home.css";
|
||||
import WelcomeSplash from "./WelcomeSplash";
|
||||
import ToolboxSection from "./ToolboxSection";
|
||||
import ScriptReviewShowcase from "./ScriptReviewShowcase";
|
||||
import ModelGenerationShowcase from "./ModelGenerationShowcase";
|
||||
|
||||
const [heroImage1, heroImage2, heroImage3] = ossAssets.home.heroSlides;
|
||||
const {
|
||||
ecommerce: featureEcommerceImage,
|
||||
script: featureScriptImage,
|
||||
token: featureTokenImage,
|
||||
} = ossAssets.home.features;
|
||||
|
||||
interface HomePageProps {
|
||||
@@ -42,16 +40,6 @@ const HOME_CAROUSEL_IMAGES = [
|
||||
];
|
||||
|
||||
const HOME_FEATURES = [
|
||||
{
|
||||
key: "model",
|
||||
eyebrow: "AI Generation",
|
||||
title: "模型生成",
|
||||
description: "通过AI模型生成文本、图片、视频,三种模式覆盖全内容类型,Agent对话式交互智能产出。",
|
||||
imageUrl: featureTokenImage,
|
||||
actionLabel: "开始生成",
|
||||
icon: <ThunderboltOutlined />,
|
||||
stats: ["文本生成", "图片生成", "视频生成"],
|
||||
},
|
||||
{
|
||||
key: "ecommerce",
|
||||
eyebrow: "AI Commerce",
|
||||
@@ -646,7 +634,7 @@ function HomePage({ onOpenGenerate, onStartOnboarding, onOpenCanvas, onOpenEcomm
|
||||
<main className="omni-home__feature-pages" aria-label="OmniAI 功能介绍">
|
||||
{HOME_FEATURES.map((feature, index) => (
|
||||
<section key={feature.key} className={`omni-home__feature-page is-${feature.key}${index % 2 ? " is-alt" : ""}`}>
|
||||
{feature.key !== "script" && feature.key !== "model" && feature.key !== "ecommerce" ? (
|
||||
{feature.key !== "script" && feature.key !== "ecommerce" ? (
|
||||
<div className="omni-home__feature-copy">
|
||||
<span>
|
||||
{feature.icon}
|
||||
@@ -660,18 +648,16 @@ function HomePage({ onOpenGenerate, onStartOnboarding, onOpenCanvas, onOpenEcomm
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="omni-home__feature-visual" aria-hidden={feature.key !== "script" && feature.key !== "model" && feature.key !== "ecommerce"}>
|
||||
<div className="omni-home__feature-visual" aria-hidden={feature.key !== "script" && feature.key !== "ecommerce"}>
|
||||
{feature.key === "script" ? (
|
||||
<ScriptReviewShowcase />
|
||||
) : feature.key === "model" ? (
|
||||
<ModelGenerationShowcase />
|
||||
) : feature.key === "ecommerce" ? (
|
||||
<EcommerceFeatureShowcase />
|
||||
) : (
|
||||
<img src={feature.imageUrl} alt="" />
|
||||
)}
|
||||
</div>
|
||||
{feature.key !== "script" && feature.key !== "model" && feature.key !== "ecommerce" ? (
|
||||
{feature.key !== "script" && feature.key !== "ecommerce" ? (
|
||||
<div className="omni-home__feature-stats" aria-hidden="true">
|
||||
{feature.stats.map((item) => (
|
||||
<span key={item}>{item}</span>
|
||||
|
||||
@@ -609,6 +609,20 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleRemoveWorkbenchResult = (index: number) => {
|
||||
setResultImages((current) => {
|
||||
const next = current.filter((_, imageIndex) => imageIndex !== index);
|
||||
if (next.length) {
|
||||
saveToolTaskState("imagewb", { taskId: taskIdRef.current || "", resultUrl: next[0], status: "完成", progress: 100 });
|
||||
setStatus(`已移除生成图,剩余 ${next.length} 张`);
|
||||
} else {
|
||||
clearToolTaskState("imagewb");
|
||||
setStatus("已移除生成图");
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (!referenceImages.length && !prompt.trim()) {
|
||||
setStatus("请先上传参考图或输入提示词");
|
||||
@@ -797,7 +811,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
aria-label="删除局部重绘素材"
|
||||
onClick={handleRemoveInpaintImage}
|
||||
>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -830,7 +844,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section className="image-workbench-control-card">
|
||||
<section className="image-workbench-control-card image-workbench-inpaint-params-card">
|
||||
<h3>参数</h3>
|
||||
<span className="image-workbench-field-label">输出分辨率</span>
|
||||
<div className="image-workbench-segmented">
|
||||
@@ -842,7 +856,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="image-workbench-control-card">
|
||||
<section className="image-workbench-control-card image-workbench-inpaint-prompt-card">
|
||||
<h3>提示词</h3>
|
||||
<textarea
|
||||
className="image-workbench-prompt"
|
||||
@@ -967,7 +981,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
) : activeTool === "camera" ? (
|
||||
<main className="image-workbench-layout image-workbench-layout--camera">
|
||||
<aside className="image-workbench-panel image-workbench-panel--left">
|
||||
<section className="image-workbench-control-card">
|
||||
<section className="image-workbench-control-card image-workbench-camera-material">
|
||||
<div className="image-workbench-section-title">
|
||||
<h3>素材</h3>
|
||||
<span>{cameraImage ? "已导入" : "待上传"}</span>
|
||||
@@ -997,7 +1011,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
aria-label="删除镜头参考图"
|
||||
onClick={handleRemoveCameraImage}
|
||||
>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1248,7 +1262,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
aria-label={`删除参考图 ${index + 1}`}
|
||||
onClick={() => handleRemoveReferenceImage(index)}
|
||||
>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -1282,7 +1296,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
aria-label="删除参考图"
|
||||
onClick={() => handleRemoveReferenceImage(0)}
|
||||
>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -1300,7 +1314,7 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="image-workbench-control-card">
|
||||
<section className="image-workbench-control-card image-workbench-output-card">
|
||||
<h3>输出</h3>
|
||||
<span className="image-workbench-field-label">尺寸</span>
|
||||
<div className="image-workbench-segmented">
|
||||
@@ -1365,6 +1379,14 @@ function ImageWorkbenchPage({ initialTool = "workbench", onOpenMore, onSelectVie
|
||||
<div className="image-workbench-result-grid">
|
||||
{resultImages.map((url, i) => (
|
||||
<div key={url} className="image-workbench-result-card">
|
||||
<button
|
||||
type="button"
|
||||
className="image-workbench-result-remove"
|
||||
aria-label={`移除生成结果 ${i + 1}`}
|
||||
onClick={() => handleRemoveWorkbenchResult(i)}
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
<a href={url} target="_blank" rel="noopener noreferrer" className="image-workbench-result-item">
|
||||
<img src={url} alt={`生成结果 ${i + 1}`} />
|
||||
</a>
|
||||
|
||||
@@ -40,12 +40,14 @@ interface MoreTool {
|
||||
}
|
||||
|
||||
const toolPreviewImages: Record<string, string> = {
|
||||
workbench: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/toolbox/image-workbench-20260609132455.png",
|
||||
inpaint: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E5%B1%80%E9%83%A8%E9%87%8D%E7%BB%98.PNG",
|
||||
camera: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E9%95%9C%E5%A4%B4%E5%AE%9E%E9%AA%8C%E5%AE%A4.PNG",
|
||||
upscale: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E5%88%86%E8%BE%A8%E7%8E%87%E6%8F%90%E5%8D%87.PNG",
|
||||
watermarkRemoval: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E5%8E%BB%E6%B0%B4%E5%8D%B0.PNG",
|
||||
dialogGenerator: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E4%BA%A4%E4%BA%92%E5%BC%8F%E5%AF%B9%E8%AF%9D%E6%A1%86%E7%94%9F%E6%88%90%E5%99%A8.PNG",
|
||||
subtitleRemoval: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E5%AD%97%E5%B9%95%E5%8E%BB%E9%99%A4.PNG",
|
||||
digitalHuman: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/toolbox/digital-human-20260609132455.png",
|
||||
characterMix: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E8%A7%92%E8%89%B2%E8%BF%81%E7%A7%BB.PNG",
|
||||
avatarConsole: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/toolbox/images/%E6%95%B0%E5%AD%97%E4%BA%BA%E6%8E%A7%E5%88%B6%E5%8F%B0.PNG",
|
||||
};
|
||||
|
||||
@@ -439,7 +439,7 @@ function ResolutionUpscalePage({
|
||||
</button>
|
||||
{sourcePreview ? (
|
||||
<button type="button" className="image-workbench-upload-remove" aria-label="删除素材" onClick={clearSource}>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -459,13 +459,20 @@ function ResolutionUpscalePage({
|
||||
<section className="image-workbench-control-card">
|
||||
<h3>参数</h3>
|
||||
{mode === "image" ? (
|
||||
<label className="image-workbench-select">
|
||||
<span>放大倍数</span>
|
||||
<select value={imageScale} onChange={(event) => setImageScale(event.target.value as ImageScale)}>
|
||||
<option value="2x">2x</option>
|
||||
<option value="4x">4x</option>
|
||||
</select>
|
||||
</label>
|
||||
<div className="resolution-upscale-scale-options" role="radiogroup" aria-label="放大倍数">
|
||||
{(["2x", "4x"] as ImageScale[]).map((scale) => (
|
||||
<button
|
||||
key={scale}
|
||||
type="button"
|
||||
className={`resolution-upscale-scale-option${imageScale === scale ? " is-active" : ""}`}
|
||||
aria-pressed={imageScale === scale}
|
||||
onClick={() => setImageScale(scale)}
|
||||
>
|
||||
<strong>{scale}</strong>
|
||||
<span>{scale === "2x" ? "日常清晰增强" : "高倍细节修复"}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="resolution-upscale-style-chips">
|
||||
@@ -548,6 +555,7 @@ function ResolutionUpscalePage({
|
||||
resultLabel={resultPreview ? resultSizeText : "等待结果"}
|
||||
sourceAlt="原图预览"
|
||||
resultAlt="超分结果预览"
|
||||
aspectRatio={sourceDimensions ? `${sourceDimensions.width} / ${sourceDimensions.height}` : undefined}
|
||||
onSourceLoad={(width, height) => setSourceDimensions({ width, height })}
|
||||
/>
|
||||
{resultPreview && (
|
||||
|
||||
@@ -360,7 +360,7 @@ function SubtitleRemovalPage({
|
||||
</button>
|
||||
{sourcePreview ? (
|
||||
<button type="button" className="image-workbench-upload-remove" aria-label="删除素材" onClick={clearSource}>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -339,7 +339,7 @@ function WatermarkRemovalPage({
|
||||
</button>
|
||||
{sourcePreview ? (
|
||||
<button type="button" className="image-workbench-upload-remove" aria-label="删除素材" onClick={clearSource}>
|
||||
×
|
||||
<DeleteOutlined />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -21,8 +21,22 @@ interface ConversationSidebarProps {
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateStr: string): string {
|
||||
const relativeMatch = dateStr.trim().match(/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks|mo|month|months|y|yr|year|years)\s+ago$/i);
|
||||
if (relativeMatch) {
|
||||
const value = Number(relativeMatch[1]);
|
||||
const unit = relativeMatch[2].toLowerCase();
|
||||
if (unit.startsWith("s")) return "刚刚";
|
||||
if (unit === "m" || unit.startsWith("min")) return `${value} 分钟前`;
|
||||
if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return `${value} 小时前`;
|
||||
if (unit === "d" || unit.startsWith("day")) return `${value} 天前`;
|
||||
if (unit === "w" || unit.startsWith("week")) return `${value} 周前`;
|
||||
if (unit === "mo" || unit.startsWith("month")) return `${value} 个月前`;
|
||||
if (unit === "y" || unit.startsWith("yr") || unit.startsWith("year")) return `${value} 年前`;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const then = new Date(dateStr).getTime();
|
||||
if (!Number.isFinite(then)) return dateStr;
|
||||
const diff = now - then;
|
||||
if (diff < 60_000) return "刚刚";
|
||||
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`;
|
||||
|
||||
@@ -25,13 +25,26 @@ interface ProjectSidebarProps {
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateStr: string): string {
|
||||
const relativeMatch = dateStr.trim().match(/^(\d+)\s*(s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks|mo|month|months|y|yr|year|years)\s+ago$/i);
|
||||
if (relativeMatch) {
|
||||
const value = Number(relativeMatch[1]);
|
||||
const unit = relativeMatch[2].toLowerCase();
|
||||
if (unit.startsWith("s")) return "刚刚";
|
||||
if (unit === "m" || unit.startsWith("min")) return `${value} 分钟前`;
|
||||
if (unit === "h" || unit.startsWith("hr") || unit.startsWith("hour")) return `${value} 小时前`;
|
||||
if (unit === "d" || unit.startsWith("day")) return `${value} 天前`;
|
||||
if (unit === "w" || unit.startsWith("week")) return `${value} 周前`;
|
||||
if (unit === "mo" || unit.startsWith("month")) return `${value} 个月前`;
|
||||
if (unit === "y" || unit.startsWith("yr") || unit.startsWith("year")) return `${value} 年前`;
|
||||
}
|
||||
|
||||
const then = new Date(dateStr).getTime();
|
||||
if (!Number.isFinite(then)) return "";
|
||||
if (!Number.isFinite(then)) return dateStr;
|
||||
const diff = Date.now() - then;
|
||||
if (diff < 60_000) return "just now";
|
||||
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} min ago`;
|
||||
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} h ago`;
|
||||
if (diff < 604_800_000) return `${Math.floor(diff / 86_400_000)} d ago`;
|
||||
if (diff < 60_000) return "刚刚";
|
||||
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`;
|
||||
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`;
|
||||
if (diff < 604_800_000) return `${Math.floor(diff / 86_400_000)} 天前`;
|
||||
return new Date(dateStr).toLocaleDateString("zh-CN");
|
||||
}
|
||||
|
||||
|
||||
@@ -286,6 +286,7 @@ function WorkbenchPage({
|
||||
const [messagePreviewAttachment, setMessagePreviewAttachment] = useState<ChatAttachment | null>(null);
|
||||
const [selectedPromptCase, setSelectedPromptCase] = useState<PromptCaseViewModel | null>(null);
|
||||
const [serverPromptCases, setServerPromptCases] = useState<PromptCaseViewModel[]>([]);
|
||||
const [promptCaseStatus, setPromptCaseStatus] = useState<"loading" | "ready" | "error">("loading");
|
||||
const [promptCaseMeasuredRatios, setPromptCaseMeasuredRatios] = useState<Record<string, number>>({});
|
||||
const [mentionPanelPlacement, setMentionPanelPlacement] = useState<"above" | "below">("above");
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
@@ -757,6 +758,7 @@ function WorkbenchPage({
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setPromptCaseStatus("loading");
|
||||
communityClient
|
||||
.listApprovedCases({ limit: 100, tag: "生成页面社区", sort: "latest" })
|
||||
.then((items) => {
|
||||
@@ -766,10 +768,12 @@ function WorkbenchPage({
|
||||
.map(communityCaseToPromptCase)
|
||||
.filter((item): item is PromptCaseViewModel => Boolean(item)),
|
||||
);
|
||||
setPromptCaseStatus("ready");
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setServerPromptCases([]);
|
||||
setPromptCaseStatus("error");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3410,30 +3414,48 @@ function WorkbenchPage({
|
||||
<div className="wb-showcase__header">
|
||||
<h2>图片提示词案例</h2>
|
||||
</div>
|
||||
<div className="wb-prompt-cases__grid">
|
||||
{promptCaseDisplayItems.map((item, index) => {
|
||||
const measuredRatio = promptCaseMeasuredRatios[item.id];
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={getPromptCaseCardClassName(item, index, measuredRatio)}
|
||||
onClick={() => setSelectedPromptCase(item)}
|
||||
>
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
loading="lazy"
|
||||
onLoad={(event) => handlePromptCaseImageLoad(item.id, event)}
|
||||
/>
|
||||
<div>
|
||||
<strong>{item.title}</strong>
|
||||
<em>{item.author}</em>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{promptCaseStatus === "loading" ? (
|
||||
<div className="wb-prompt-cases__grid wb-prompt-cases__grid--skeleton" aria-label="图片提示词案例加载中">
|
||||
{Array.from({ length: 8 }, (_, index) => (
|
||||
<span key={index} className={`wb-prompt-case-skeleton wb-prompt-case-skeleton--${index % 4}`} />
|
||||
))}
|
||||
</div>
|
||||
) : promptCaseStatus === "error" ? (
|
||||
<div className="wb-prompt-cases__state">
|
||||
<strong>案例暂时没有加载成功</strong>
|
||||
<span>你仍然可以直接输入提示词开始生成。</span>
|
||||
</div>
|
||||
) : promptCaseDisplayItems.length === 0 ? (
|
||||
<div className="wb-prompt-cases__state">
|
||||
<strong>暂无可展示案例</strong>
|
||||
<span>稍后会在这里展示社区精选提示词。</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="wb-prompt-cases__grid">
|
||||
{promptCaseDisplayItems.map((item, index) => {
|
||||
const measuredRatio = promptCaseMeasuredRatios[item.id];
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={getPromptCaseCardClassName(item, index, measuredRatio)}
|
||||
onClick={() => setSelectedPromptCase(item)}
|
||||
>
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
loading="lazy"
|
||||
onLoad={(event) => handlePromptCaseImageLoad(item.id, event)}
|
||||
/>
|
||||
<div>
|
||||
<strong>{item.title}</strong>
|
||||
<em>{item.author}</em>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,50 +2,74 @@
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.subtitle-removal-preset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
min-height: 92px;
|
||||
padding: 10px 8px;
|
||||
border: 1.5px solid var(--border-weak, #e5e5e5);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-surface, #fff);
|
||||
border: 1px solid color-mix(in srgb, var(--border-subtle, #333) 82%, white 10%);
|
||||
border-radius: var(--radius-xs, 8px);
|
||||
background:
|
||||
linear-gradient(180deg, rgb(255 255 255 / 0.018), transparent),
|
||||
var(--bg-inset, #111);
|
||||
color: var(--fg-muted, #aaa);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, transform 0.16s ease;
|
||||
}
|
||||
|
||||
.subtitle-removal-preset:hover {
|
||||
border-color: var(--accent, #0d9488);
|
||||
background: var(--bg-elevated, #f8f8f8);
|
||||
border-color: color-mix(in srgb, var(--accent, #0d9488) 42%, var(--border-subtle, #333));
|
||||
background:
|
||||
linear-gradient(180deg, color-mix(in srgb, var(--accent, #0d9488) 8%, transparent), transparent),
|
||||
var(--bg-hover, #171717);
|
||||
color: var(--fg-body, #eee);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.subtitle-removal-preset.is-active {
|
||||
border-color: var(--accent, #0d9488);
|
||||
background: color-mix(in srgb, var(--accent, #0d9488) 6%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent, #0d9488) 72%, transparent);
|
||||
background: color-mix(in srgb, var(--accent, #0d9488) 13%, var(--bg-inset, #111));
|
||||
color: var(--accent, #0d9488);
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent, #0d9488) 8%, transparent) inset;
|
||||
}
|
||||
|
||||
.subtitle-removal-preset strong {
|
||||
max-width: 100%;
|
||||
color: var(--fg-body, #eee);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subtitle-removal-preset span {
|
||||
max-width: 100%;
|
||||
color: var(--fg-muted, #aaa);
|
||||
font-size: 11px;
|
||||
opacity: 0.55;
|
||||
line-height: 1.35;
|
||||
opacity: 0.82;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle-removal-preset__visual {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-page, #f0f0f0);
|
||||
border: 1px solid var(--border-weak, #e0e0e0);
|
||||
width: 54px;
|
||||
height: 34px;
|
||||
border: 1px solid color-mix(in srgb, var(--border-subtle, #333) 78%, white 10%);
|
||||
border-radius: 6px;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(255 255 255 / 0.026), transparent),
|
||||
color-mix(in srgb, var(--bg-elevated, #161616) 92%, black 8%);
|
||||
box-shadow: 0 1px 0 rgb(255 255 255 / 0.035) inset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -55,9 +79,9 @@
|
||||
right: 0;
|
||||
top: var(--region-top);
|
||||
height: var(--region-height);
|
||||
background: color-mix(in srgb, var(--accent, #0d9488) 30%, transparent);
|
||||
border-top: 1.5px dashed var(--accent, #0d9488);
|
||||
border-bottom: 1.5px dashed var(--accent, #0d9488);
|
||||
background: color-mix(in srgb, var(--accent, #0d9488) 24%, transparent);
|
||||
border-top: 1px dashed var(--accent, #0d9488);
|
||||
border-bottom: 1px dashed var(--accent, #0d9488);
|
||||
}
|
||||
|
||||
.subtitle-removal-preview {
|
||||
|
||||
@@ -2319,3 +2319,669 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Workbench new-conversation commercial polish. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page {
|
||||
--wb-elevated-line: rgba(210, 255, 232, 0.12);
|
||||
--wb-elevated-line-active: rgba(var(--accent-rgb), 0.38);
|
||||
--wb-elevated-fill: rgba(18, 22, 21, 0.94);
|
||||
--wb-control-fill: rgba(255, 255, 255, 0.052);
|
||||
--wb-control-fill-hover: rgba(var(--accent-rgb), 0.105);
|
||||
--wb-copy-dim: rgba(202, 215, 207, 0.68);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__title {
|
||||
color: rgba(246, 250, 247, 0.96);
|
||||
font-weight: 760;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__content {
|
||||
border-color: var(--wb-elevated-line);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.072), rgba(255, 255, 255, 0.024)),
|
||||
var(--wb-elevated-fill);
|
||||
box-shadow:
|
||||
0 20px 46px rgba(0, 0, 0, 0.28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.065);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__content:focus-within {
|
||||
border-color: var(--wb-elevated-line-active);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(var(--accent-rgb), 0.12),
|
||||
0 24px 56px rgba(0, 0, 0, 0.32),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__textarea,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__highlight {
|
||||
color: rgba(246, 250, 247, 0.95);
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar {
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-top-color: rgba(210, 255, 232, 0.095);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar-left {
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-inline-chip__trigger,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-mode-switch__button,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-select-chip__trigger {
|
||||
border-color: rgba(210, 255, 232, 0.105);
|
||||
background: var(--wb-control-fill);
|
||||
color: rgba(246, 250, 247, 0.86);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-inline-chip__trigger:hover:not(:disabled),
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-select-chip__trigger:hover,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-mode-switch__button:hover {
|
||||
border-color: rgba(var(--accent-rgb), 0.34);
|
||||
background: var(--wb-control-fill-hover);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__billing-estimate {
|
||||
color: var(--wb-copy-dim);
|
||||
font-size: 11px;
|
||||
font-weight: 580;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__send-primary {
|
||||
box-shadow:
|
||||
0 12px 26px rgba(var(--accent-rgb), 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.26);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__send-primary.is-loading {
|
||||
cursor: progress;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-upload {
|
||||
border-color: rgba(var(--accent-rgb), 0.36);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(var(--accent-rgb), 0.11), rgba(var(--accent-rgb), 0.045)),
|
||||
rgba(255, 255, 255, 0.02);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-label,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-count {
|
||||
color: rgba(210, 255, 232, 0.84);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__suggestions {
|
||||
align-items: center;
|
||||
margin-top: -2px;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-suggestion-chip {
|
||||
border-color: rgba(210, 255, 232, 0.105);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.052), rgba(255, 255, 255, 0.022)),
|
||||
rgba(255, 255, 255, 0.018);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-suggestion-chip__icon {
|
||||
color: rgba(var(--accent-rgb), 0.88);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases .wb-showcase__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-showcase__header h2 {
|
||||
color: rgba(224, 236, 229, 0.72);
|
||||
font-size: 12px;
|
||||
font-weight: 680;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card {
|
||||
border-color: rgba(210, 255, 232, 0.085);
|
||||
background: #090e0d;
|
||||
box-shadow:
|
||||
0 14px 28px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card:hover,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card:focus-visible {
|
||||
border-color: rgba(var(--accent-rgb), 0.32);
|
||||
box-shadow:
|
||||
0 18px 36px rgba(0, 0, 0, 0.3),
|
||||
0 0 0 1px rgba(var(--accent-rgb), 0.08);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card > div {
|
||||
gap: 5px;
|
||||
background:
|
||||
linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.54) 24%, rgba(0, 0, 0, 0.9));
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card strong {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
line-height: 1.38;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card em {
|
||||
color: rgba(210, 224, 216, 0.68);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases__state {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
gap: 6px;
|
||||
min-height: 188px;
|
||||
padding: 26px;
|
||||
border: 1px dashed rgba(210, 255, 232, 0.13);
|
||||
border-radius: 16px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.014)),
|
||||
rgba(255, 255, 255, 0.018);
|
||||
color: rgba(246, 250, 247, 0.9);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases__state strong {
|
||||
font-size: 14px;
|
||||
font-weight: 720;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases__state span {
|
||||
color: var(--wb-copy-dim);
|
||||
font-size: 12px;
|
||||
font-weight: 520;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-skeleton {
|
||||
display: block;
|
||||
min-height: 160px;
|
||||
grid-row: span 16;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(210, 255, 232, 0.07);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.035)),
|
||||
rgba(255, 255, 255, 0.025);
|
||||
background-size: 220% 100%;
|
||||
animation: wb-prompt-case-loading 1.15s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-skeleton--1,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-skeleton--3 {
|
||||
grid-row: span 22;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-active .ai-chat-message-list > .conversation-sidebar__empty {
|
||||
border-color: rgba(210, 255, 232, 0.14);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.018)),
|
||||
rgba(255, 255, 255, 0.018);
|
||||
color: rgba(210, 224, 216, 0.78);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar {
|
||||
border-left-color: rgba(210, 255, 232, 0.095);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.045), transparent 40%),
|
||||
rgba(12, 15, 15, 0.97);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__header {
|
||||
border-bottom-color: rgba(210, 255, 232, 0.095);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__new {
|
||||
border-color: rgba(var(--accent-rgb), 0.38);
|
||||
background:
|
||||
linear-gradient(180deg, rgba(var(--accent-rgb), 0.17), rgba(var(--accent-rgb), 0.095)),
|
||||
rgba(255, 255, 255, 0.02);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__item {
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
background var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__item:hover {
|
||||
transform: translateX(-1px);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__item-title {
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .conversation-sidebar__empty {
|
||||
border-color: rgba(210, 255, 232, 0.12);
|
||||
background: rgba(255, 255, 255, 0.022);
|
||||
}
|
||||
|
||||
@keyframes wb-prompt-case-loading {
|
||||
0% {
|
||||
background-position: 120% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -120% 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .ai-workbench-shell > .conversation-sidebar.is-collapsed {
|
||||
inset: calc(56px + var(--dg-mobile-nav-space, 70px) + 16px) 8px auto auto !important;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__composer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__content,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__content {
|
||||
padding: 13px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs {
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-upload {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-upload:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__textarea,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__highlight,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__textarea,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__highlight {
|
||||
min-height: 88px;
|
||||
padding-right: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: end;
|
||||
padding-top: 9px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar-left {
|
||||
max-width: none;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar-left::-webkit-scrollbar,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__suggestions::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__toolbar-right {
|
||||
align-self: stretch;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
justify-items: end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__billing-estimate {
|
||||
max-width: 112px;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases__state {
|
||||
min-height: 156px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home {
|
||||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__title {
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__input-row {
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-inline-chip__trigger,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-mode-switch__button,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-select-chip__trigger {
|
||||
height: 32px;
|
||||
max-width: 132px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-suggestion-chip {
|
||||
min-height: 32px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases__grid {
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-card {
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-case-skeleton {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Browser feedback: scale the launch composer with large canvases and keep reference previews intact. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__composer {
|
||||
width: min(100%, clamp(920px, 72vw, 1160px));
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__content {
|
||||
padding: clamp(18px, 1.25vw, 24px);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__textarea,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__highlight {
|
||||
min-height: clamp(78px, 8svh, 112px);
|
||||
max-height: clamp(180px, 24svh, 260px);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__suggestions {
|
||||
max-width: min(100%, clamp(920px, 72vw, 1160px));
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-card {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-zoom {
|
||||
z-index: 12;
|
||||
width: min(280px, calc(100vw - 48px));
|
||||
height: auto;
|
||||
min-height: 188px;
|
||||
max-height: min(340px, calc(100svh - 180px));
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 8px;
|
||||
border-color: rgba(210, 255, 232, 0.18);
|
||||
border-radius: 16px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.018)),
|
||||
rgba(5, 8, 8, 0.96);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-zoom img,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-zoom video {
|
||||
border-radius: 11px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__composer,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__suggestions {
|
||||
width: min(100%, clamp(1040px, 64vw, 1240px));
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__composer,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-home__suggestions {
|
||||
width: 100%;
|
||||
max-width: 920px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__content {
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__textarea,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__highlight {
|
||||
min-height: 88px;
|
||||
max-height: 190px;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-zoom {
|
||||
width: min(230px, calc(100vw - 32px));
|
||||
min-height: 168px;
|
||||
}
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-stack {
|
||||
top: calc(100% + 12px);
|
||||
bottom: auto;
|
||||
isolation: isolate;
|
||||
z-index: 120;
|
||||
width: min(320px, calc(100vw - 64px));
|
||||
max-width: calc(100vw - 64px);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-zoom {
|
||||
top: 0;
|
||||
transform: translateY(0) scale(0.98);
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__ref-card:hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* Keep reference previews above the feed, and open lower-row thumbnails upward. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__composer {
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-home__suggestions {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-prompt-cases {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs.has-items.is-open {
|
||||
z-index: 220;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-stack {
|
||||
z-index: 240;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5) .wb-composer__ref-zoom {
|
||||
top: auto;
|
||||
bottom: calc(100% + 12px);
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5):hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5) .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 4) .wb-composer__ref-zoom {
|
||||
top: auto;
|
||||
bottom: calc(100% + 10px);
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 4):hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:nth-child(n + 4) .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-stack {
|
||||
isolation: isolate;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card {
|
||||
isolation: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-add-more {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:hover,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:focus-within {
|
||||
z-index: 180;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-zoom {
|
||||
left: 0;
|
||||
top: calc(100% + 12px);
|
||||
z-index: 80;
|
||||
transform: translateY(6px) scale(0.98);
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-card:hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-stack {
|
||||
top: calc(100% + 8px);
|
||||
width: min(230px, calc(100vw - 24px));
|
||||
max-width: calc(100vw - 24px);
|
||||
}
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-card:hover,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-card:focus-within {
|
||||
z-index: 140;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-card:hover .wb-composer__ref-preview,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__ref-card:focus-within .wb-composer__ref-preview {
|
||||
border-color: rgba(var(--accent-rgb), 0.48);
|
||||
box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.18);
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card {
|
||||
isolation: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-add-more {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card:hover,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card:focus-within {
|
||||
z-index: 180;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-zoom {
|
||||
left: 0;
|
||||
top: calc(100% + 12px);
|
||||
z-index: 80;
|
||||
transform: translateY(6px) scale(0.98);
|
||||
transform-origin: left top;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__ref-card:hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* Final override for multi-row reference stacks on the launch composer. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5) .wb-composer__ref-zoom {
|
||||
top: auto;
|
||||
bottom: calc(100% + 12px);
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5):hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-launch .wb-composer__refs .wb-composer__ref-card:nth-child(n + 5) .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* Keep the reference stack balanced in the active bottom composer. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-stack {
|
||||
grid-template-columns: repeat(4, 58px);
|
||||
width: min(286px, calc(100vw - 40px));
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .wb-composer__refs .wb-composer__ref-stack {
|
||||
grid-template-columns: repeat(3, 58px);
|
||||
width: min(218px, calc(100vw - 24px));
|
||||
}
|
||||
}
|
||||
|
||||
/* The active bottom composer sits below the upload stack, so previews should open upward. */
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-active .wb-composer__refs .wb-composer__ref-card .wb-composer__ref-zoom {
|
||||
top: auto;
|
||||
bottom: calc(100% + 12px);
|
||||
transform: translateY(-6px) scale(0.98);
|
||||
transform-origin: left bottom;
|
||||
}
|
||||
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-active .wb-composer__refs .wb-composer__ref-card:hover .wb-composer__ref-zoom,
|
||||
.web-shell[data-ui-theme="dark-green"][data-view="workbench"] .ai-workbench-page.is-active .wb-composer__refs .wb-composer__ref-preview:focus-visible + .wb-composer__ref-zoom {
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user