diff --git a/web/src/components/conversation/voice-recording-inline.tsx b/web/src/components/conversation/voice-recording-inline.tsx index 9606d997a..9923744f6 100644 --- a/web/src/components/conversation/voice-recording-inline.tsx +++ b/web/src/components/conversation/voice-recording-inline.tsx @@ -108,7 +108,7 @@ export function VoiceRecordingInline({ onTranscribed, onCancel }: VoiceRecording return (
{displayError ? ( diff --git a/web/src/components/model-picker.tsx b/web/src/components/model-picker.tsx index dbcfcd40a..e69efce9f 100644 --- a/web/src/components/model-picker.tsx +++ b/web/src/components/model-picker.tsx @@ -24,6 +24,7 @@ type ModelPickerProps = { placeholder?: string; onMissingConfig?: () => void; showSelectedPrice?: boolean; + showOptionPrices?: boolean; variant?: "default" | "creation"; requirements?: ModelRequirements; showConfiguredModelName?: boolean; @@ -40,6 +41,7 @@ export function ModelPicker({ placeholder = "选择模型", onMissingConfig, showSelectedPrice = true, + showOptionPrices = showSelectedPrice, variant = "default", requirements, showConfiguredModelName = false, @@ -226,7 +228,7 @@ export function ModelPicker({ theme={theme} creationVariant={creationVariant} showConfiguredModelName={showConfiguredModelName} - showPrice={showSelectedPrice && creditsEnabled} + showPrice={showOptionPrices && creditsEnabled} disabledReason={disabledReason} /> {selected ? : null} diff --git a/web/src/pages/create/index.tsx b/web/src/pages/create/index.tsx index 7041868cb..dcc5d3d09 100644 --- a/web/src/pages/create/index.tsx +++ b/web/src/pages/create/index.tsx @@ -1,7 +1,7 @@ -import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type CSSProperties, type PointerEvent, type ReactNode, type RefObject } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type PointerEvent, type ReactNode, type RefObject } from "react"; import { App, Button, Drawer, Modal, Popover, Spin, Tooltip } from "antd"; import { Reorder } from "motion/react"; -import { ArrowDown, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Clapperboard, Clock3, Copy, Download, FileText, Film, History, Image as ImageIcon, LoaderCircle, Maximize2, MessageSquareText, Minimize2, Music2, Paperclip, Plus, RefreshCw, Search, SlidersHorizontal, Sparkles, Trash2, WandSparkles, X } from "lucide-react"; +import { ArrowDown, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Clapperboard, Clock3, Copy, Download, FileText, Film, History, Image as ImageIcon, LoaderCircle, Maximize2, MessageSquareText, Minimize2, Music2, Plus, RefreshCw, Search, SlidersHorizontal, Sparkles, Trash2, WandSparkles, X } from "lucide-react"; import { Link } from "react-router"; import { AIMessageMarkdown } from "@/components/ai/ai-message-markdown"; @@ -171,7 +171,6 @@ export default function CreatePage() { const [libraryOpen, setLibraryOpen] = useState(false); const externalAssetSources = useExternalAssetSources(libraryOpen); const abortRef = useRef(null); - const fileInputRef = useRef(null); const composerFocusRef = useRef(null); const threadScrollRef = useRef(null); const followLatestMessageRef = useRef(true); @@ -418,27 +417,6 @@ export default function CreatePage() { attachment: creationAttachmentFromImage(file, uploaded), }; }; - const addAttachments = (files: FileList | File[]) => { - if ((mode === "image" || mode === "video") && maxReferences === 0) { - toast.warning(mode === "image" ? "当前图片模型不支持参考图" : "当前模型不支持图生视频"); - return; - } - const next = Array.from(files) - .filter((file) => creationFileAccepted(mode, file)) - .slice(0, Math.max(0, maxReferences - attachments.length)); - if (!next.length) return; - void Promise.allSettled(next.map(async (file) => { - const { asset, attachment } = await uploadCreationAsset(file); - if (asset) addAsset(asset); - return attachment; - })).then((settled) => { - const items = settled.flatMap((entry) => entry.status === "fulfilled" ? [entry.value] : []); - const failed = settled.filter((entry) => entry.status === "rejected"); - if (items.length) setAttachments((current) => [...current, ...items].slice(0, maxReferences)); - if (failed.length) toast.error(`${failed.length} 个参考素材上传失败,请重试`); - }); - }; - const uploadLibraryAssets = async (files: FileList | File[]) => { const next = Array.from(files).filter((file) => creationFileAccepted(mode, file)); if (!next.length) return []; @@ -453,11 +431,6 @@ export default function CreatePage() { return assetIds; }; - const handleFileChange = (event: ChangeEvent) => { - if (event.target.files) addAttachments(event.target.files); - event.target.value = ""; - }; - const handleLibrarySelect = (selectedIds: string[]) => { const next = selectedIds.flatMap((id): CreationAttachment[] => { const asset = assets.find((item) => item.id === id); @@ -893,8 +866,6 @@ export default function CreatePage() { onReplaceAttachment: replaceReferenceFromTrack, onReplaceReferenceFiles: replaceReferenceFromFiles, onOpenLibrary: () => setLibraryOpen(true), - fileInputRef, - onFileChange: handleFileChange, onModeChange: selectMode, model: selectedModel, modelRequirements, @@ -1164,8 +1135,6 @@ type ComposerProps = { onReplaceAttachment: (targetAttachmentId: string, replacement: CreationAttachment) => void; onReplaceReferenceFiles: (targetAttachmentId: string, files: File[]) => void; onOpenLibrary: () => void; - fileInputRef: RefObject; - onFileChange: (event: ChangeEvent) => void; onModeChange: (mode: CreationMode) => void; model: string; modelRequirements: ModelRequirements; @@ -1327,7 +1296,6 @@ function CreationComposer(props: ComposerProps) { }; const composer =
-
{ const target = props.references.find((item) => item.id === reference.id); if (target?.attachmentId) props.onReplaceReferenceFiles(target.attachmentId, files); }} /> {props.attachments.length || referencesSupported ?
@@ -1416,8 +1384,7 @@ function CreationComposer(props: ComposerProps) { 优化 : null} - - + {props.mode === "video" || (props.mode === "image" && imageSettingsSupported) ? : null} {props.mode === "video" ? : null}
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 31e8dcc6f..15bbdce60 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -10816,6 +10816,23 @@ html:not(.dark) .creation-home .creation-chat-control:hover, html:not(.dark) .creation-home .creation-chat-control[aria-expanded="true"], html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.canvas-composer-model-picker:hover { background: rgba(28, 32, 37, .06) !important; } +.creation-home .creation-chat-controls > .voice-recording-inline { + width: min(236px, calc(100vw - 120px)); + max-width: 236px; + flex: 0 1 236px; + min-height: 30px; + height: 30px; + overflow: hidden; + border: 0; + border-radius: 7px; + background: transparent !important; + padding: 0 2px; +} +.creation-home .creation-chat-controls > .voice-recording-inline > svg { + flex: 0 1 160px; + max-width: 160px; +} + .creation-chat-intro h1 > .creation-intro-emphasis { display: inline-block; margin: 0 4px; diff --git a/web/test/create-library-button.test.ts b/web/test/create-library-button.test.ts index 4655efd0e..84cfa26db 100644 --- a/web/test/create-library-button.test.ts +++ b/web/test/create-library-button.test.ts @@ -18,14 +18,13 @@ describe("creation library button", () => { expect(dockEnd).toBeGreaterThan(dockStart); const dockSource = compactSource(source.slice(dockStart, dockEnd)); const modePickerIndex = dockSource.indexOf(" { const source = readFileSync(resolve(import.meta.dir, "../src/pages/create/index.tsx"), "utf8"); const pickerSource = readFileSync(resolve(import.meta.dir, "../src/components/assets/asset-library-picker-modal.tsx"), "utf8"); const uploadStart = source.indexOf("const uploadLibraryAssets = async"); - const uploadEnd = source.indexOf("const handleFileChange", uploadStart); + const uploadEnd = source.indexOf("const handleLibrarySelect", uploadStart); expect(uploadStart).toBeGreaterThanOrEqual(0); expect(uploadEnd).toBeGreaterThan(uploadStart); @@ -128,12 +127,12 @@ describe("creation library button", () => { expect(removeCreationAttachment(attachments, "last").map((item) => item.id)).toEqual(["first", "middle"]); }); - test("删除按钮在指针按下阶段隔离拖拽,附件与素材库入口职责独立", () => { + test("删除按钮在指针按下阶段隔离拖拽,素材库入口职责独立", () => { const source = compactSource(readFileSync(resolve(import.meta.dir, "../src/pages/create/index.tsx"), "utf8")); expect(source).toContain("onPointerDownCapture={(event) => event.stopPropagation()}"); expect(source).toContain("onRemove(item.id)"); - expect(source).toContain("onClick={() => props.fileInputRef.current?.click()}"); expect(source).toContain("onClick={props.onOpenLibrary}"); + expect(source).not.toContain("onClick={() => props.fileInputRef.current?.click()}"); }); });