feat(create): 对话页布局 - 加宽输入区并修正参数弹窗
开始对话后工具条进顶栏、模式切换横排到录音左侧,输入区加宽并去掉黑底;参数弹窗改用工作台表面色,去掉细线描边。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -54,6 +54,7 @@
|
||||
.app-product-workspace .creation-home .creation-submit.ant-btn:not(:disabled):hover .creation-submit-action::after { animation: creation-submit-sheen 640ms cubic-bezier(.16, 1, .3, 1); }
|
||||
.app-product-workspace .creation-home .creation-submit.ant-btn:not(:disabled):hover .creation-submit-action svg { transform: translateY(-1px); }
|
||||
.app-product-workspace .creation-home .creation-submit.ant-btn:not(:disabled):active .creation-submit-action { transform: scale(.97); }
|
||||
.app-product-workspace .creation-home .creation-chat-composer.is-thread .creation-submit.ant-btn .creation-submit-action { height: 34px; padding: 0 14px; }
|
||||
}
|
||||
.creation-inspiration-filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
|
||||
.creation-inspiration-filters button { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; min-height: 36px; border: 0; border-radius: 999px; color: var(--user-ink-muted); background: transparent; font-size: 13px; transition: background var(--user-motion), color var(--user-motion); }
|
||||
@@ -120,3 +121,154 @@
|
||||
.app-user-workspace .creation-submit-action { transition: none !important; transform: none !important; }
|
||||
.app-product-workspace .creation-home .creation-submit.ant-btn:not(:disabled):hover .creation-submit-action::after { animation: none; }
|
||||
}
|
||||
|
||||
/* 参数弹窗挂在 body overlays,必须直接用用户表面 token;描边由 Ant inner 的 1px border 造成。 */
|
||||
.app-user-overlays .creation-control-popover,
|
||||
.app-product-overlays .creation-control-popover {
|
||||
--popover: var(--user-surface-raised);
|
||||
--border: transparent;
|
||||
--creation-text: var(--user-ink);
|
||||
--creation-muted: var(--user-ink-muted);
|
||||
--creation-faint: var(--user-ink-soft);
|
||||
--control-selected-bg: var(--user-control-pressed);
|
||||
--control-selected-fg: var(--user-ink);
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-control-popover .ant-popover-arrow,
|
||||
.app-product-overlays .creation-control-popover .ant-popover-arrow { display: none; }
|
||||
|
||||
.app-user-overlays .creation-control-popover.ant-popover,
|
||||
.app-user-overlays .creation-control-popover .ant-popover-container,
|
||||
.app-user-overlays .creation-control-popover .ant-popover-inner,
|
||||
.app-user-overlays .creation-control-popover-surface,
|
||||
.app-product-overlays .creation-control-popover.ant-popover,
|
||||
.app-product-overlays .creation-control-popover .ant-popover-container,
|
||||
.app-product-overlays .creation-control-popover .ant-popover-inner,
|
||||
.app-product-overlays .creation-control-popover-surface {
|
||||
border: 0 !important;
|
||||
outline: 0 !important;
|
||||
background: var(--user-surface-raised) !important;
|
||||
background-image: none !important;
|
||||
box-shadow: 0 18px 40px rgba(0, 0, 0, .36) !important;
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-control-popover .ant-popover-inner,
|
||||
.app-product-overlays .creation-control-popover .ant-popover-inner {
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-parameter-menu,
|
||||
.app-product-overlays .creation-parameter-menu { padding: 12px !important; }
|
||||
|
||||
.app-user-overlays .creation-parameter-section h3,
|
||||
.app-user-overlays .creation-duration-heading > span,
|
||||
.app-product-overlays .creation-parameter-section h3,
|
||||
.app-product-overlays .creation-duration-heading > span { color: var(--user-ink-muted) !important; }
|
||||
|
||||
.app-user-overlays .creation-parameter-section > header > span,
|
||||
.app-user-overlays .creation-duration-heading strong,
|
||||
.app-product-overlays .creation-parameter-section > header > span,
|
||||
.app-product-overlays .creation-duration-heading strong { color: var(--user-ink) !important; }
|
||||
|
||||
.app-user-overlays .creation-choice-grid,
|
||||
.app-product-overlays .creation-choice-grid { gap: 6px !important; }
|
||||
|
||||
.app-user-overlays .creation-choice-grid button,
|
||||
.app-user-overlays .creation-duration-choices button,
|
||||
.app-product-overlays .creation-choice-grid button,
|
||||
.app-product-overlays .creation-duration-choices button {
|
||||
min-height: 40px !important;
|
||||
border: 0 !important;
|
||||
border-radius: 10px !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
color: var(--user-ink-muted) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-choice-grid.is-resolution button,
|
||||
.app-user-overlays .creation-choice-grid.is-quality button,
|
||||
.app-user-overlays .creation-choice-grid.is-count button,
|
||||
.app-user-overlays .creation-duration-choices button,
|
||||
.app-product-overlays .creation-choice-grid.is-resolution button,
|
||||
.app-product-overlays .creation-choice-grid.is-quality button,
|
||||
.app-product-overlays .creation-choice-grid.is-count button,
|
||||
.app-product-overlays .creation-duration-choices button { min-height: 34px !important; }
|
||||
|
||||
.app-user-overlays .creation-choice-grid button:hover,
|
||||
.app-user-overlays .creation-duration-choices button:hover,
|
||||
.app-product-overlays .creation-choice-grid button:hover,
|
||||
.app-product-overlays .creation-duration-choices button:hover {
|
||||
background: var(--user-surface-hover) !important;
|
||||
color: var(--user-ink) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-choice-grid button.is-selected,
|
||||
.app-user-overlays .creation-duration-choices button.is-selected,
|
||||
.app-product-overlays .creation-choice-grid button.is-selected,
|
||||
.app-product-overlays .creation-duration-choices button.is-selected {
|
||||
background: var(--user-control-pressed) !important;
|
||||
color: var(--user-ink) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-choice-grid .creation-ratio-preview > span,
|
||||
.app-product-overlays .creation-choice-grid .creation-ratio-preview > span {
|
||||
border: 0 !important;
|
||||
border-radius: 3px !important;
|
||||
background: currentColor;
|
||||
opacity: .55;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-choice-grid button.is-selected .creation-ratio-preview > span,
|
||||
.app-product-overlays .creation-choice-grid button.is-selected .creation-ratio-preview > span { opacity: .92; }
|
||||
|
||||
.app-user-overlays .creation-custom-value,
|
||||
.app-product-overlays .creation-custom-value {
|
||||
border: 0 !important;
|
||||
background: var(--user-surface-muted) !important;
|
||||
color: var(--user-ink-muted) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-parameter-menu .image-size-tiers button,
|
||||
.app-user-overlays .creation-parameter-menu .image-size-ratios button,
|
||||
.app-user-overlays .creation-parameter-menu .image-size-auto,
|
||||
.app-product-overlays .creation-parameter-menu .image-size-tiers button,
|
||||
.app-product-overlays .creation-parameter-menu .image-size-ratios button,
|
||||
.app-product-overlays .creation-parameter-menu .image-size-auto {
|
||||
border: 0 !important;
|
||||
background: var(--user-surface-muted) !important;
|
||||
box-shadow: none !important;
|
||||
color: var(--user-ink-muted) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-parameter-menu .image-size-tiers button[aria-pressed="true"],
|
||||
.app-user-overlays .creation-parameter-menu .image-size-ratios button[aria-pressed="true"],
|
||||
.app-user-overlays .creation-parameter-menu .image-size-auto[aria-pressed="true"],
|
||||
.app-product-overlays .creation-parameter-menu .image-size-tiers button[aria-pressed="true"],
|
||||
.app-product-overlays .creation-parameter-menu .image-size-ratios button[aria-pressed="true"],
|
||||
.app-product-overlays .creation-parameter-menu .image-size-auto[aria-pressed="true"] {
|
||||
background: var(--user-control-pressed) !important;
|
||||
box-shadow: none !important;
|
||||
color: var(--user-ink) !important;
|
||||
}
|
||||
|
||||
.app-user-overlays .creation-parameter-menu .image-size-ratio-icon,
|
||||
.app-product-overlays .creation-parameter-menu .image-size-ratio-icon {
|
||||
border: 0 !important;
|
||||
background: currentColor;
|
||||
opacity: .55;
|
||||
}
|
||||
|
||||
html:not(.dark) .app-user-overlays .creation-control-popover.ant-popover,
|
||||
html:not(.dark) .app-user-overlays .creation-control-popover .ant-popover-container,
|
||||
html:not(.dark) .app-user-overlays .creation-control-popover .ant-popover-inner,
|
||||
html:not(.dark) .app-user-overlays .creation-control-popover-surface,
|
||||
html:not(.dark) .app-product-overlays .creation-control-popover.ant-popover,
|
||||
html:not(.dark) .app-product-overlays .creation-control-popover .ant-popover-container,
|
||||
html:not(.dark) .app-product-overlays .creation-control-popover .ant-popover-inner,
|
||||
html:not(.dark) .app-product-overlays .creation-control-popover-surface {
|
||||
box-shadow: 0 16px 40px rgba(25, 25, 30, .12) !important;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export type CreationMessage = {
|
||||
export type CreationConversation = { id: string; title: string; updatedAt: string; canvasId?: string; messages: CreationMessage[] };
|
||||
|
||||
export const modeLabels: Record<CreationMode, string> = { text: "文本", image: "图片", video: "视频" };
|
||||
export const defaultCreationMode: CreationMode = "image";
|
||||
export const shotScriptLabels: Record<CreationMode, string> = { text: "创作思路", image: "画面指令", video: "镜头脚本" };
|
||||
export const ratioOptions = [
|
||||
{ value: "1:1", label: "方形" },
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ImageSizePicker } from "@/components/image-size-picker";
|
||||
import { imageResolutionUsesQuality } from "@/lib/image-size-presets";
|
||||
import { createPortal } from "react-dom";
|
||||
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type PointerEvent, type ReactNode, type RefObject } from "react";
|
||||
import { App, Button, Dropdown, Popover } from "antd";
|
||||
import { AppDrawer } from "@/components/ui/product/app-drawer";
|
||||
import { AppModal } from "@/components/ui/product/app-modal";
|
||||
import { useWorkspaceTopBarMount } from "@/components/layout/workspace-top-bar-extension";
|
||||
import { Tooltip } from "@/components/ui/base/tooltip";
|
||||
import { Reorder, LayoutGroup, motion, useReducedMotion } from "motion/react";
|
||||
import { ArrowDown, ArrowUp, Brain, ChevronDown, ChevronLeft, ChevronRight, Clapperboard, Clock3, Copy, Download, FileText, Film, History, Image as ImageIcon, LoaderCircle, Maximize2, MessageSquareText, Minimize2, MoreHorizontal, Music2, Pencil, Plus, RefreshCw, Search, SlidersHorizontal, Sparkles, Trash2, UserRound, WandSparkles, Waves, X } from "lucide-react";
|
||||
@@ -174,7 +176,8 @@ export function CreationWorkspaceToolbar({ shots, onJumpToShot, onNewConversatio
|
||||
window.addEventListener("mousedown", onPointerDown);
|
||||
return () => window.removeEventListener("mousedown", onPointerDown);
|
||||
}, [railOpen]);
|
||||
return <header className="creation-thread-toolbar">
|
||||
const mount = useWorkspaceTopBarMount();
|
||||
const toolbar = <header className="creation-thread-toolbar">
|
||||
<div className="creation-toolbar-shots" ref={railRef}>
|
||||
<button type="button" className="creation-rail-trigger" aria-expanded={railOpen} aria-haspopup="listbox" onClick={() => setRailOpen((open) => !open)}><Clapperboard />镜头时间线{shots.length > 0 ? <em className="creation-rail-count">{shots.length}</em> : null}</button>
|
||||
{railOpen ? <div className="creation-rail-pop" role="listbox" aria-label="镜头时间线">
|
||||
@@ -196,6 +199,9 @@ export function CreationWorkspaceToolbar({ shots, onJumpToShot, onNewConversatio
|
||||
<Tooltip title="历史对话"><button type="button" aria-label="查看历史对话" className="creation-toolbar-action" onClick={onOpenHistory}><History /></button></Tooltip>
|
||||
</div>
|
||||
</header>;
|
||||
if (mount) return createPortal(toolbar, mount);
|
||||
if (mount === null) return null;
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
export function CreationMessageView({ item, shotNumber, onRetryFailure, onCreateVariant, onEditUserMessage, onContinueCanvas, openingCanvas }: { item: CreationMessage; shotNumber: number; onRetryFailure: () => void; onCreateVariant: () => void; onEditUserMessage: (text: string) => void; onContinueCanvas: (ids?: string[]) => void; openingCanvas: boolean }) {
|
||||
@@ -492,7 +498,6 @@ export function CreationComposer(props: ComposerProps) {
|
||||
spotlightColor="color-mix(in srgb, var(--user-ink) 12%, transparent)"
|
||||
spotlightRadius={280}
|
||||
>
|
||||
{props.variant === "thread" ? <div className="creation-composer-mode-row"><ModePicker mode={props.mode} onModeChange={props.onModeChange} /></div> : null}
|
||||
<div className="creation-chat-writing-surface">
|
||||
<div className="creation-chat-editor">
|
||||
<CanvasResourceMentionTextarea ref={props.composerFocusRef} value={props.prompt} references={props.references} mentionMenuWidth={400} sendOnEnter onFocus={props.onPromptFocus} onChange={props.setPrompt} onSubmit={props.onSubmit} containerClassName="creation-chat-mention-container" className="creation-chat-mention-editor creation-scrollbar" style={{ color: "var(--creation-text)" }} placeholder={props.placeholderOverride || (props.variant === "empty" ? emptyPlaceholder : placeholder)} aria-label="创作提示词,可使用 @ 引用当前参考内容或技能;回车发送,Shift+回车换行" spellCheck disabled={interactionBusy} activeDropReferenceId={dropTargetReferenceId} onReferenceFilesDrop={(reference, files) => { const target = props.references.find((item) => item.id === reference.id); if (target?.attachmentId) props.onReplaceReferenceFiles(target.attachmentId, files); }} />
|
||||
@@ -564,6 +569,7 @@ export function CreationComposer(props: ComposerProps) {
|
||||
</div>
|
||||
<footer className="creation-chat-dock">
|
||||
<div className="creation-chat-controls">
|
||||
{props.variant === "thread" ? <ModePicker mode={props.mode} onModeChange={props.onModeChange} /> : null}
|
||||
<VoiceRecordingButton
|
||||
className="creation-voice-trigger"
|
||||
disabled={interactionBusy}
|
||||
@@ -663,7 +669,7 @@ export function CreationModeTabs({ mode, onModeChange, agentActive = false, onAg
|
||||
}
|
||||
|
||||
function ModePicker({ mode, onModeChange }: { mode: CreationMode; onModeChange: (mode: CreationMode) => void }) {
|
||||
return <CreationModeTabs mode={mode} onModeChange={onModeChange} orientation="vertical" />;
|
||||
return <CreationModeTabs mode={mode} onModeChange={onModeChange} />;
|
||||
}
|
||||
|
||||
function GenerationSettingsMenu(props: ComposerProps) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { createPluginHostContext } from "@/services/plugin-host";
|
||||
import { usePluginStore } from "@/stores/use-plugin-store";
|
||||
import { buildCreationMentionReferences, expandCreationPrompt, reconcileCreationAttachmentLimit, removeCreationReferenceTokens, replaceCreationAttachmentReference, selectedCreationReferences, type CreationReference } from "./creation-references";
|
||||
import { creationAttachmentFromAsset, creationAttachmentFromAudio, creationAttachmentFromAudioAsset, creationAttachmentFromDocument, creationAttachmentFromExternalAsset, creationAttachmentFromImage, creationAttachmentFromVideo, creationAttachmentFromVideoAsset, creationAttachmentKind, creationAudioAsset, creationFileAccepted, creationImageAsset, creationMediaAspectRatio, creationUploadAccept, creationVideoAsset, removeCreationAttachment, splitCreationAttachments, type CreationAttachment } from "./creation-assets";
|
||||
import { modeLabels, type CreationConversation, type CreationMessage, type CreationMode, type CreationRetryContext, type CreationSettings, type CreationShotRailEntry, type CreationStatus } from "./creation-types";
|
||||
import { defaultCreationMode, modeLabels, type CreationConversation, type CreationMessage, type CreationMode, type CreationRetryContext, type CreationSettings, type CreationShotRailEntry, type CreationStatus } from "./creation-types";
|
||||
import { attachCreationTaskContexts, completedCreationGenerationTask, conversationTimestamp, creationShotRail, creationVideoShotOrdinal, isImageAttachment, isVideoAttachment, materializeCreationTaskResults, newConversation, newMessage, reconcileCreationTaskMessages } from "./creation-conversations";
|
||||
import { CreationComposer, CreationEmptySuggest, CreationFeaturedWorks, CreationHistoryDrawer, CreationMessageView, CreationModeTabs, CreationWorkspaceToolbar, creationAssetCategoryLabels } from "./creation-workspace";
|
||||
import { CreationAgentEntry } from "./creation-agent-entry";
|
||||
@@ -83,7 +83,7 @@ export default function CreatePage() {
|
||||
const [activeId, setActiveId] = useState("");
|
||||
const activeIdRef = useRef("");
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [mode, setMode] = useState<CreationMode>(() => initialComposerPreferences.mode || "video");
|
||||
const [mode, setMode] = useState<CreationMode>(() => initialComposerPreferences.mode || defaultCreationMode);
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [attachments, setAttachments] = useState<CreationAttachment[]>([]);
|
||||
const promptRef = useRef(prompt);
|
||||
@@ -178,7 +178,7 @@ export default function CreatePage() {
|
||||
useEffect(() => {
|
||||
if (!composerPreferencesHydrated || composerPreferencesInitialized) return;
|
||||
const saved = useCreationPreferencesStore.getState().preferences;
|
||||
const nextMode = saved.mode || "video";
|
||||
const nextMode = saved.mode || defaultCreationMode;
|
||||
setMode(nextMode);
|
||||
if (nextMode === "image" && saved.image) {
|
||||
if (saved.image.ratio) setRatio(saved.image.ratio);
|
||||
|
||||
Reference in New Issue
Block a user