diff --git a/web/src/components/canvas/canvas-config-node-panel.tsx b/web/src/components/canvas/canvas-config-node-panel.tsx index 729a75224..0255bf8a4 100644 --- a/web/src/components/canvas/canvas-config-node-panel.tsx +++ b/web/src/components/canvas/canvas-config-node-panel.tsx @@ -1,4 +1,4 @@ -import { Button, Input, InputNumber, Segmented, Select, Slider } from "antd"; +import { Button, Input, InputNumber, Segmented, Slider } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { useEffect, useState, type CSSProperties, type ReactNode } from "react"; import { ChevronDown, Dice5, Image as ImageIcon, LoaderCircle, MessageSquare, Music2, Play, Sparkles, Video, Workflow as WorkflowIcon } from "lucide-react"; @@ -17,6 +17,7 @@ import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import { workflowProviderPluginEnabled } from "@/lib/plugins/builtin/workflows"; import { usePluginStore } from "@/stores/use-plugin-store"; import type { CanvasGenerationMode, CanvasNodeData, CanvasNodeMetadata, CanvasVideoEditOperation, CanvasWorkspaceMode } from "@/types/canvas"; +import { Select } from "@/components/ui/base/select"; type CanvasConfigNodePanelProps = { node: CanvasNodeData; diff --git a/web/src/components/canvas/canvas-script-node.tsx b/web/src/components/canvas/canvas-script-node.tsx index ad8f627a3..ca3f212d1 100644 --- a/web/src/components/canvas/canvas-script-node.tsx +++ b/web/src/components/canvas/canvas-script-node.tsx @@ -1,4 +1,4 @@ -import { Button, Dropdown, Input, InputNumber, Modal, Segmented, Select, Table } from "antd"; +import { Button, Dropdown, Input, InputNumber, Modal, Segmented, Table } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { Fragment, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type PointerEvent as ReactPointerEvent, type ReactNode } from "react"; @@ -34,6 +34,7 @@ import type { StoryboardVideoInputMode, } from "@/types/canvas"; import type { TaskStatus } from "@/services/api/task-center"; +import { Select } from "@/components/ui/base/select"; const STORYBOARD_PROMPT_MIN_HEIGHT = 40; const STORYBOARD_PROMPT_MAX_HEIGHT = 116; diff --git a/web/src/components/canvas/canvas-share-modal.tsx b/web/src/components/canvas/canvas-share-modal.tsx index b34bdac17..b7dadce97 100644 --- a/web/src/components/canvas/canvas-share-modal.tsx +++ b/web/src/components/canvas/canvas-share-modal.tsx @@ -1,10 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { App, Button, Input, Modal, Select, Spin } from "antd"; +import { App, Button, Input, Modal, Spin } from "antd"; import { Copy, Link2, RefreshCw, Share2, Unlink } from "lucide-react"; import { canvasThemes } from "@/lib/canvas-theme"; import { createCanvasShare, deleteCanvasShare, getCanvasShare, type CanvasShareStatus } from "@/services/api/canvas-share"; import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; +import { Select } from "@/components/ui/base/select"; export function CanvasShareModal({ projectId, open, onClose, beforeCreate }: { projectId: string; open: boolean; onClose: () => void; beforeCreate: () => Promise }) { const { message, modal } = App.useApp(); diff --git a/web/src/components/canvas/canvas-size-picker.tsx b/web/src/components/canvas/canvas-size-picker.tsx index ba84dfa8e..6057c94fc 100644 --- a/web/src/components/canvas/canvas-size-picker.tsx +++ b/web/src/components/canvas/canvas-size-picker.tsx @@ -1,7 +1,8 @@ import { useEffect, useRef, useState } from "react"; -import { Select } from "antd"; + import { cn } from "@/lib/utils"; +import { Select } from "@/components/ui/base/select"; const sizeOptions = ["auto", "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "9:16"]; diff --git a/web/src/components/canvas/canvas-video-segment-dialog.tsx b/web/src/components/canvas/canvas-video-segment-dialog.tsx index 4f9498dea..1e1df3822 100644 --- a/web/src/components/canvas/canvas-video-segment-dialog.tsx +++ b/web/src/components/canvas/canvas-video-segment-dialog.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import { App, Button, Input, InputNumber, Modal, Segmented, Select } from "antd"; +import { App, Button, Input, InputNumber, Modal, Segmented } from "antd"; import { AudioLines, Check, ListVideo, Plus, Scissors, SkipBack, SkipForward, Trash2 } from "lucide-react"; import { nanoid } from "nanoid"; @@ -15,6 +15,7 @@ import { resolveMediaUrl } from "@/services/file-storage"; import { modelDisplayName, type AiConfig } from "@/stores/use-config-store"; import { type CanvasConnection, type CanvasNodeData, type CanvasVideoEditOperation } from "@/types/canvas"; import type { TimelineProject } from "@/types/timeline"; +import { Select } from "@/components/ui/base/select"; export type CanvasVideoSegmentItem = CanvasTimelineSegmentItem; diff --git a/web/src/components/canvas/canvas-workspace-asset-panel.tsx b/web/src/components/canvas/canvas-workspace-asset-panel.tsx index e4784e77b..f708b14cb 100644 --- a/web/src/components/canvas/canvas-workspace-asset-panel.tsx +++ b/web/src/components/canvas/canvas-workspace-asset-panel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { App, Button, Select } from "antd"; +import { App, Button } from "antd"; import { useInfiniteQuery } from "@tanstack/react-query"; import { FileText, Images, Plus, RefreshCw } from "lucide-react"; import { CachedResourceImage } from "@/components/cached-resource-image"; @@ -10,6 +10,7 @@ import { loadAssetLibraryPage } from "@/services/user-data-sync"; import { useUserStore } from "@/stores/use-user-store"; import type { Asset } from "@/stores/use-asset-store"; import { assetPickerItemsToInsertPayloads, type InsertAssetPayload } from "./asset-picker-modal"; +import { Select } from "@/components/ui/base/select"; const categories: AssetCategory[] = ["material", "character", "environment", "prop", "other"]; export function CanvasWorkspaceAssetPanel({ onInsert, onManage, onProjectAssets }: { onInsert: (payloads: InsertAssetPayload[]) => Promise; onManage: () => void; onProjectAssets?: () => void }) { diff --git a/web/src/components/canvas/canvas-workspace-tool-panel.tsx b/web/src/components/canvas/canvas-workspace-tool-panel.tsx index 6a3a8d3c8..4eef7497a 100644 --- a/web/src/components/canvas/canvas-workspace-tool-panel.tsx +++ b/web/src/components/canvas/canvas-workspace-tool-panel.tsx @@ -4,7 +4,7 @@ type FeedTab = ToolScope; import { memo, useEffect, useMemo, useRef, useState, type UIEvent } from "react"; import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Eye, Loader2, MoreHorizontal, Plus, Star, Trash2, Upload, Wrench, X } from "lucide-react"; -import { App, Button, Dropdown, Form, Input, Radio, Select, Space, type MenuProps } from "antd"; +import { App, Button, Dropdown, Form, Input, Radio, Space, type MenuProps } from "antd"; import { CanvasImagePreview } from "@/components/canvas/canvas-image-preview"; import { AppModal } from "@/components/ui/product/app-modal"; @@ -13,6 +13,7 @@ import { WorkspaceErrorState, WorkspaceState } from "@/components/layout/workspa import { uploadMediaFile } from "@/services/file-storage"; import { uploadImage } from "@/services/image-storage"; import { createTool, deleteTool, listTools, setToolFavorite, type ToolScope, type ToolSummary, type ToolType, type ToolVisibility } from "@/services/api/tools"; +import { Select } from "@/components/ui/base/select"; type ToolSubTab = "style" | "effect" | "motion" | "nine_grid"; diff --git a/web/src/components/canvas/director/canvas-director-workbench.tsx b/web/src/components/canvas/director/canvas-director-workbench.tsx index 58efef893..0733e6a5f 100644 --- a/web/src/components/canvas/director/canvas-director-workbench.tsx +++ b/web/src/components/canvas/director/canvas-director-workbench.tsx @@ -1,4 +1,4 @@ -import { App, Button, ColorPicker, Dropdown, Input, InputNumber, Select, Slider } from "antd"; +import { App, Button, ColorPicker, Dropdown, Input, InputNumber, Slider } from "antd"; import { Switch } from "@/components/ui/base/switch"; import type { MenuProps } from "antd"; import { Box, BoxSelect, Camera, Circle, Cuboid, FileUp, Focus, Image as ImageIcon, LampDesk, Lightbulb, Plus, Redo2, RotateCcw, Save, Trash2, Undo2, UserRound, Video, X } from "lucide-react"; @@ -30,6 +30,7 @@ import { useDirectorWorkbenchStore } from "@/stores/canvas/use-director-workbenc import { useActiveTheme } from "@/stores/canvas/use-canvas-theme-store"; import type { CanvasNodeData } from "@/types/canvas"; import type { DirectorCamera, DirectorCameraMove, DirectorHumanoidBone, DirectorKeyframeDeleteTarget, DirectorKeyframeEasing, DirectorLight, DirectorObject, DirectorPose, DirectorQuat, DirectorRenderMode, DirectorRig, DirectorScene, DirectorSceneOutput, DirectorShot, DirectorShotSize, DirectorTransform, DirectorVec3 } from "@/types/director"; +import { Select } from "@/components/ui/base/select"; export function CanvasDirectorWorkbench({ open, scene, imageNodes, onboardingScope, onClose, onChange, onApply, onDeleteImageNode, onFlush }: { open: boolean; scene: DirectorScene | null; imageNodes: CanvasNodeData[]; onboardingScope: string; onClose: () => void; onChange: (scene: DirectorScene) => void; onApply: (output: DirectorSceneOutput) => Promise; onDeleteImageNode: (nodeId: string) => void; onFlush?: () => void | Promise }) { const { message, modal } = App.useApp(); diff --git a/web/src/components/canvas/style-asset-binding-modal.tsx b/web/src/components/canvas/style-asset-binding-modal.tsx index ca180eb3c..5ba78c9b0 100644 --- a/web/src/components/canvas/style-asset-binding-modal.tsx +++ b/web/src/components/canvas/style-asset-binding-modal.tsx @@ -1,9 +1,10 @@ import { useEffect, useMemo, useState, type ReactNode } from "react"; -import { Button, Input, InputNumber, Select } from "antd"; +import { Button, Input, InputNumber } from "antd"; import { AppModal } from "@/components/ui/product/app-modal"; import { Switch } from "@/components/ui/base/switch"; import { AlertTriangle, Box, Check, FileImage, Link2, Plus, Save, SlidersHorizontal, Trash2 } from "lucide-react"; import { nanoid } from "nanoid"; +import { Select } from "@/components/ui/base/select"; import { createStyleProfileSnapshot, diff --git a/web/src/components/canvas/style-profile-editor-modal.tsx b/web/src/components/canvas/style-profile-editor-modal.tsx index 6349dd0c3..0a14c0f24 100644 --- a/web/src/components/canvas/style-profile-editor-modal.tsx +++ b/web/src/components/canvas/style-profile-editor-modal.tsx @@ -1,10 +1,11 @@ import { useEffect, useMemo, useState, type ReactNode } from "react"; -import { App, Button, Input, Segmented, Select } from "antd"; +import { App, Button, Input, Segmented } from "antd"; import { AppModal } from "@/components/ui/product/app-modal"; import { Braces, Image, Layers3, Save, Sparkles } from "lucide-react"; import { StyleAssetBindingModal } from "@/components/canvas/style-asset-binding-modal"; import { createStyleProfileSnapshot, styleProfileValidationMessage, type StyleProfileSnapshot } from "@/lib/canvas/style-profile"; +import { Select } from "@/components/ui/base/select"; type EditorSection = "identity" | "prompt" | "execution"; diff --git a/web/src/components/layout/user-oss-settings-form.tsx b/web/src/components/layout/user-oss-settings-form.tsx index 03ed57556..ee5bac588 100644 --- a/web/src/components/layout/user-oss-settings-form.tsx +++ b/web/src/components/layout/user-oss-settings-form.tsx @@ -1,12 +1,13 @@ -import { App, Button, Form, Input, Select, Tag } from "antd"; +import { App, Button, Form, Input, Tag } from "antd"; import { Switch } from "@/components/ui/base/switch"; import { Cloud, ShieldCheck } from "lucide-react"; import { useEffect, useState } from "react"; -import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, S3_PRESET_OPTIONS, type OSSConnectionTestResult, type OSSProvider, type S3Preset } from "@/lib/oss-settings"; +import { changesRequireOSSRetest, DEFAULT_OSS_PATH_PREFIX, getS3PresetHints, S3_PRESET_OPTIONS, validateOSSConnectionDraft, type OSSConnectionTestResult, type OSSProvider, type S3Preset } from "@/lib/oss-settings"; import { getUserOSSSetting, testUserOSSConnection, updateUserOSSSetting, type UserOSSSetting } from "@/services/api/resources"; import { useUserStore } from "@/stores/use-user-store"; import { StatusBadge } from "@/components/ui/base/badges"; +import { Select } from "@/components/ui/base/select"; type OSSFormValues = { enabled?: boolean; @@ -104,6 +105,11 @@ export function UserOSSSettingsForm() { const testConnection = async () => { const values = await form.validateFields(); + const validationError = validateOSSConnectionDraft(values); + if (validationError) { + message.error(validationError); + return; + } setTesting(true); try { const result = await testUserOSSConnection(connectionInput(values)); diff --git a/web/src/components/layout/workspace-page.tsx b/web/src/components/layout/workspace-page.tsx index 0d7311801..c8461a734 100644 --- a/web/src/components/layout/workspace-page.tsx +++ b/web/src/components/layout/workspace-page.tsx @@ -1,9 +1,10 @@ -import { Button, Select } from "antd"; +import { Button } from "antd"; import { ChevronLeft, ChevronRight, ListFilter, RotateCcw } from "lucide-react"; import type { ReactNode } from "react"; import { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; +import { Select } from "@/components/ui/base/select"; export function WorkspacePage({ children, className, grid = false, fluid = false, scroll = true }: { children: ReactNode; className?: string; grid?: boolean; fluid?: boolean; scroll?: boolean }) { return ( diff --git a/web/src/components/model-capability-editor.tsx b/web/src/components/model-capability-editor.tsx index 4e9dcad19..1139e5d18 100644 --- a/web/src/components/model-capability-editor.tsx +++ b/web/src/components/model-capability-editor.tsx @@ -1,6 +1,6 @@ import { ImageSizePresetsEditor } from "./image-size-presets-editor"; import { imageSizeConfigWithPresets, imageSizePresets } from "@/lib/image-size-presets"; -import { Input, InputNumber, Select } from "antd"; +import { Input, InputNumber } from "antd"; import { Switch } from "@/components/ui/base/switch"; import { SegmentedControl } from "@/components/ui/base/segmented-control"; import type { ReactNode } from "react"; @@ -8,6 +8,7 @@ import type { ReactNode } from "react"; import { defaultImageCapabilityConfig, defaultModelCapabilityConfig, normalizeModelCapabilityConfig, type ImageCapabilityConfig, type ModelCapabilityConfig, type TextCapabilityConfig, type VideoCapabilityConfig } from "@/lib/model-capabilities"; import type { ModelProtocol } from "@/lib/model-protocols"; import { VIDEO_RESOLUTION_CAPABILITY_OPTIONS } from "@/lib/video-generation-options"; +import { Select } from "@/components/ui/base/select"; const ratioOptions = ["16:9", "9:16", "1:1", "4:3", "3:4", "21:9"]; const operationOptions = [ diff --git a/web/src/components/ui/base/buttons/icon-button.tsx b/web/src/components/ui/base/buttons/icon-button.tsx index 4e44ea77d..47b353f5d 100644 --- a/web/src/components/ui/base/buttons/icon-button.tsx +++ b/web/src/components/ui/base/buttons/icon-button.tsx @@ -1,4 +1,4 @@ -import type { ButtonHTMLAttributes } from "react"; +import { useState, type ButtonHTMLAttributes } from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { Loader2, type LucideIcon } from "lucide-react"; @@ -19,7 +19,7 @@ import { cn } from "@/lib/utils"; * 明暗:全部颜色走 token;active 按压底用 bg-surface-active,明暗自动适配。 */ export const iconButtonVariants = cva( - ["inline-flex shrink-0 items-center justify-center rounded-md", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45", "motion-safe:active:scale-[0.96]"], + ["inline-flex shrink-0 items-center justify-center rounded-md", "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45", "motion-safe:active:scale-[0.96]"], { variants: { variant: { @@ -58,9 +58,31 @@ export type IconButtonProps = ButtonHTMLAttributes & active?: boolean; }; -export function IconButton({ variant, size, icon: Icon, loading = false, active = false, className, type = "button", disabled, ...props }: IconButtonProps) { +export function IconButton({ variant, size, icon: Icon, loading = false, active = false, className, type = "button", disabled, onPointerDown, onKeyDown, onBlur, ...props }: IconButtonProps) { + const [inputModality, setInputModality] = useState<"pointer" | "keyboard">("pointer"); return ( - ); diff --git a/web/src/components/ui/base/buttons/tool-button.tsx b/web/src/components/ui/base/buttons/tool-button.tsx index e7cc8f1fe..92eaff387 100644 --- a/web/src/components/ui/base/buttons/tool-button.tsx +++ b/web/src/components/ui/base/buttons/tool-button.tsx @@ -1,4 +1,4 @@ -import type { ButtonHTMLAttributes, ReactNode } from "react"; +import { useState, type ButtonHTMLAttributes, type ReactNode } from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { Loader2 } from "lucide-react"; @@ -21,7 +21,7 @@ import { cn } from "@/lib/utils"; * 明暗:全部颜色走 token;active 用按压底语义 token,明暗自动适配。 */ export const toolButtonVariants = cva( - ["inline-flex select-none items-center justify-center gap-1.5 rounded-md", "font-medium transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45"], + ["inline-flex select-none items-center justify-center gap-1.5 rounded-md px-3", "font-medium transition-colors", "outline-none focus-visible:outline-none data-[input-modality=keyboard]:focus-visible:ring-2 data-[input-modality=keyboard]:focus-visible:ring-ring", "disabled:pointer-events-none disabled:opacity-45"], { variants: { variant: { @@ -74,12 +74,14 @@ export type ToolButtonProps = ButtonHTMLAttributes & label?: ReactNode; }; -export function ToolButton({ variant, size, tone, icon, active = false, expands = false, loading = false, label, className, type = "button", disabled, ...props }: ToolButtonProps) { +export function ToolButton({ variant, size, tone, icon, active = false, expands = false, loading = false, label, className, type = "button", disabled, onPointerDown, onKeyDown, onBlur, ...props }: ToolButtonProps) { + const [inputModality, setInputModality] = useState<"pointer" | "keyboard">("pointer"); const pressed = !expands && active; const expanded = expands && active; return (