ai创建修改
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Alert, Input, Space, Tag, Typography } from 'antd';
|
||||
import { Alert, Input, Radio, Space, Tag, Typography } from 'antd';
|
||||
import type {
|
||||
JsonValue,
|
||||
VideoPromptSchemaFieldConfig,
|
||||
@@ -54,10 +54,24 @@ function EditableInput({ value, maxLength, onChange }: { value: JsonValue; maxLe
|
||||
return <Input {...commonProps} />;
|
||||
}
|
||||
|
||||
function renderFieldTag(field: VideoPromptSchemaFieldConfig) {
|
||||
return field.editable ? <Tag color="processing">可编辑</Tag> : <Tag color="default">只读</Tag>;
|
||||
function BooleanRadio({ value, onChange }: { value: JsonValue; onChange: (nextValue: JsonValue) => void }) {
|
||||
const boolValue = value === true || value === 'true';
|
||||
return (
|
||||
<Radio.Group
|
||||
value={boolValue}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
style={{ display: 'flex', gap: 24 }}
|
||||
>
|
||||
<Radio value={true} style={{ fontSize: 14, color: '#475569' }}>是</Radio>
|
||||
<Radio value={false} style={{ fontSize: 14, color: '#475569' }}>否</Radio>
|
||||
</Radio.Group>
|
||||
);
|
||||
}
|
||||
|
||||
// function renderFieldTag(field: VideoPromptSchemaFieldConfig) {
|
||||
// return field.editable ? <Tag color="processing">可编辑</Tag> : <Tag color="default">只读</Tag>;
|
||||
// }
|
||||
|
||||
const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value, schemaConfigSnapshot, onChange }) => {
|
||||
const safeValue = isRecord(value) ? value : {};
|
||||
const sections = normalizeSchemaSections(schemaConfigSnapshot);
|
||||
@@ -93,21 +107,28 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div key={section.key} style={{ marginBottom: 18 }}>
|
||||
<Space style={{ marginBottom: 8 }}>
|
||||
<Text strong style={{ color: '#334155', fontSize: 13 }}>{section.label}</Text>
|
||||
{renderFieldTag(section)}
|
||||
{/* {renderFieldTag(section)} */}
|
||||
</Space>
|
||||
<div style={{ borderLeft: '3px solid #6366f1', paddingLeft: 12, marginLeft: 4 }}>
|
||||
{fields.map((field) => (
|
||||
<div key={field.key} style={{ marginBottom: 12 }}>
|
||||
<Space style={{ marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 12 }}>{field.label}</Text>
|
||||
{renderFieldTag(field)}
|
||||
{/* {renderFieldTag(field)} */}
|
||||
</Space>
|
||||
{field.editable ? (
|
||||
<EditableInput
|
||||
value={sectionValue[field.key]}
|
||||
maxLength={field.maxLength}
|
||||
onChange={(nextText) => onChange(setObjectField(safeValue, section.key, field.key, nextText))}
|
||||
/>
|
||||
field.type === 'boolean' ? (
|
||||
<BooleanRadio
|
||||
value={sectionValue[field.key]}
|
||||
onChange={(nextValue) => onChange(setObjectField(safeValue, section.key, field.key, nextValue))}
|
||||
/>
|
||||
) : (
|
||||
<EditableInput
|
||||
value={sectionValue[field.key]}
|
||||
maxLength={field.maxLength}
|
||||
onChange={(nextText) => onChange(setObjectField(safeValue, section.key, field.key, nextText))}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<ReadonlyBlock value={sectionValue[field.key]} />
|
||||
)}
|
||||
@@ -142,14 +163,21 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div key={field.key} style={{ marginBottom: 10 }}>
|
||||
<Space style={{ marginBottom: 4 }}>
|
||||
<Text style={{ color: '#64748b', fontSize: 12 }}>{field.label}</Text>
|
||||
{renderFieldTag(field)}
|
||||
{/* {renderFieldTag(field)} */}
|
||||
</Space>
|
||||
{field.editable ? (
|
||||
<EditableInput
|
||||
value={row[field.key]}
|
||||
maxLength={field.maxLength}
|
||||
onChange={(nextText) => onChange(setArrayObjectField(safeValue, section.key, index, field.key, nextText))}
|
||||
/>
|
||||
field.type === 'boolean' ? (
|
||||
<BooleanRadio
|
||||
value={row[field.key]}
|
||||
onChange={(nextValue) => onChange(setArrayObjectField(safeValue, section.key, index, field.key, nextValue))}
|
||||
/>
|
||||
) : (
|
||||
<EditableInput
|
||||
value={row[field.key]}
|
||||
maxLength={field.maxLength}
|
||||
onChange={(nextText) => onChange(setArrayObjectField(safeValue, section.key, index, field.key, nextText))}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<ReadonlyBlock value={row[field.key]} />
|
||||
)}
|
||||
@@ -170,7 +198,7 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
<div key={section.key} style={{ marginBottom: 18 }}>
|
||||
<Space style={{ marginBottom: 8 }}>
|
||||
<Text strong style={{ color: '#334155', fontSize: 13 }}>{section.label}</Text>
|
||||
{renderFieldTag(section)}
|
||||
{/* {renderFieldTag(section)} */}
|
||||
</Space>
|
||||
{section.editable ? (
|
||||
<EditableInput
|
||||
@@ -191,13 +219,13 @@ const VideoPromptSchemaEditor: React.FC<VideoPromptSchemaEditorProps> = ({ value
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Alert
|
||||
{/* <Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 14 }}
|
||||
message="仅可修改后台 Schema 配置允许编辑的字段"
|
||||
description="视频规格、时间段、流程条目数量、输出规格、质量控制、合规控制等锁定内容由服务端最终校验。"
|
||||
/>
|
||||
/> */}
|
||||
{sections.map((section) => {
|
||||
if (!(section.key in safeValue)) return null;
|
||||
if (section.type === 'object') return renderObjectSection(section);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Button, Modal, Slider, Spin, Typography } from 'antd';
|
||||
import { PauseOutlined, PlayCircleFilled } from '@ant-design/icons';
|
||||
import { PauseCircleFilled, PlayCircleFilled } from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -469,16 +469,29 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
width={980}
|
||||
destroyOnHidden
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel} disabled={loading}>
|
||||
<Button key="cancel" onClick={onCancel} disabled={loading} style={{ borderRadius: 8, border: '1px solid rgba(99, 102, 241, 0.2)', color: '#6366f1' }}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={loading} disabled={loading || disabledByDuration || !integerDuration}>
|
||||
<Button key="confirm" type="primary" onClick={handleConfirm} loading={loading} disabled={loading || disabledByDuration || !integerDuration} style={{ borderRadius: 8, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', boxShadow: '0 4px 12px rgba(99, 102, 241, 0.3)' }}>
|
||||
确认拆镜
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', background: '#fff' }}>
|
||||
<style>{`
|
||||
.ant-slider-handle::after {
|
||||
height: 80px !important;
|
||||
width: 10px !important;
|
||||
border-radius: 10px !important;
|
||||
top: 95% !important;
|
||||
transform: translateY(-50%) !important;
|
||||
}
|
||||
.ant-slider-handle {
|
||||
height: 80px !important;
|
||||
margin-top: -37px !important;
|
||||
}
|
||||
`}</style>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', background: '#fff', borderRadius: 16, overflow: 'hidden' }}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={videoUrl}
|
||||
@@ -497,26 +510,26 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
}}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onPause={() => setPlaying(false)}
|
||||
style={{ width: '100%', maxHeight: 420, objectFit: 'contain', background: '#111', borderRadius: 8 }}
|
||||
style={{ maxHeight: 320, objectFit: 'contain', background: '#111' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 24,
|
||||
background: '#f3f6ff',
|
||||
padding: '26px 34px 18px',
|
||||
borderRadius: 20,
|
||||
background: '#f0f5ff',
|
||||
padding: '20px 24px',
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 28, marginBottom: 22 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, marginBottom: 18 }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={playing ? <PauseOutlined /> : <PlayCircleFilled />}
|
||||
icon={playing ? <PauseCircleFilled /> : <PlayCircleFilled />}
|
||||
onClick={handlePlaySelected}
|
||||
disabled={!integerDuration || disabledByDuration}
|
||||
style={{ fontSize: 22, color: '#111' }}
|
||||
style={{ fontSize: 20, color: '#1e293b', padding: 0 }}
|
||||
/>
|
||||
<span style={{ fontSize: 26, color: '#111', letterSpacing: 1 }}>
|
||||
<span style={{ fontSize: 18, color: '#1e293b', fontFamily: 'monospace' }}>
|
||||
{formatTime(currentTime)} / {formatTime(integerDuration)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -528,8 +541,9 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
gridTemplateColumns: `repeat(${Math.max(frames.length, 1)}, minmax(42px, 1fr))`,
|
||||
height: 86,
|
||||
overflow: 'hidden',
|
||||
borderRadius: 10,
|
||||
background: '#dbe2ff',
|
||||
borderRadius: 12,
|
||||
background: '#fff',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{frameLoading && frames.length === 0 ? (
|
||||
@@ -549,7 +563,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
height: 86,
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
background: '#eef2ff',
|
||||
background: 'transparent',
|
||||
overflow: 'hidden',
|
||||
cursor: disabledByDuration ? 'not-allowed' : 'pointer',
|
||||
position: 'relative',
|
||||
@@ -562,7 +576,7 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
<Spin size="small" />
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#64748b', fontSize: 12, background: '#eef2ff' }}>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#64748b', fontSize: 12, background: '#fff' }}>
|
||||
<span>{frame.second}s</span>
|
||||
<span style={{ fontSize: 11 }}>无有效帧</span>
|
||||
</div>
|
||||
@@ -586,9 +600,10 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
暂无帧预览,请确认视频是否加载完成
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 0, padding: '0 8px 0' }}>
|
||||
<div style={{ position: 'absolute', top: -6, left: 10, right: 0, padding: '0 8px 0' }}>
|
||||
<Slider
|
||||
range
|
||||
min={0}
|
||||
@@ -601,38 +616,35 @@ const VideoTrimPicker: React.FC<VideoTrimPickerProps> = ({
|
||||
disabled={!integerDuration || disabledByDuration}
|
||||
styles={{
|
||||
track: {
|
||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||
height: 6,
|
||||
background: '#6969dd63',
|
||||
height: 70,
|
||||
borderRadius: 3,
|
||||
margin: '0 ',
|
||||
},
|
||||
rail: {
|
||||
background: '#e2e8f0',
|
||||
height: 6,
|
||||
// background: '#0e447e70',
|
||||
height: 70,
|
||||
borderRadius: 3,
|
||||
},
|
||||
handle: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
width: 20,
|
||||
height: 70,
|
||||
marginTop: 0,
|
||||
// backgroundColor: '#fff',
|
||||
// border: '3px solid #6366f1',
|
||||
// boxShadow: '0 2px 8px rgba(99, 102, 241, 0.3)',
|
||||
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
// border: '2px solid #6366f1',
|
||||
borderRadius: '50%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 30, textAlign: 'center', color: '#64748b', fontSize: 16 }}>
|
||||
<div style={{ marginTop: 16, textAlign: 'center', color: '#64748b', fontSize: 14 }}>
|
||||
已选取 {selectedDuration}s
|
||||
<span style={{ marginLeft: 12, fontSize: 13, color: '#94a3b8' }}>
|
||||
最小 {minDuration}s,最大 {maxDuration}s
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{localError && (
|
||||
<div style={{ marginTop: 12, textAlign: 'center', color: '#ef4444', fontSize: 13 }}>
|
||||
<div style={{ marginTop: 12, padding: '10px 14px', borderRadius: 8, background: 'rgba(239, 68, 68, 0.08)', border: '1px solid rgba(239, 68, 68, 0.15)', color: '#ef4444', fontSize: 13, textAlign: 'center' }}>
|
||||
{localError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user