Files
video-gen/video-gen-admin/src/pages/adminReplication/components/VideoPromptSchemaViewer.tsx
T

172 lines
6.9 KiB
TypeScript

import React from 'react';
import { Card, Collapse, Descriptions, Empty, Space, Table, Tag, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { JsonBlock } from './JsonCollapse';
import type {
VideoPromptSchemaFieldConfig,
VideoPromptSchemaSectionConfig,
} from '../../../utils/videoPromptSchema';
import {
hasSchemaConfigSnapshot,
inferSchemaSections,
normalizeSchemaSections,
stringifySchemaValue,
} from '../../../utils/videoPromptSchema';
const isRecord = (value: unknown): value is Record<string, unknown> => !!value && typeof value === 'object' && !Array.isArray(value);
const isLongText = (value: unknown): boolean => typeof value === 'string' && value.length > 80;
const renderValue = (value: unknown): React.ReactNode => {
if (value === null || value === undefined || value === '') return <Typography.Text type="secondary">-</Typography.Text>;
if (typeof value === 'boolean') return value ? '是' : '否';
if (typeof value === 'number') return value;
if (typeof value === 'string') {
return <Typography.Paragraph style={{ marginBottom: 0, whiteSpace: 'pre-wrap' }}>{value}</Typography.Paragraph>;
}
return <JsonBlock value={value} maxHeight={220} />;
};
const tagOfEditable = (editable?: boolean) => editable ? <Tag color="processing">可编辑</Tag> : <Tag color="default">只读</Tag>;
const readSectionValue = (schema: Record<string, any>, key: string): unknown => schema[key];
const renderObjectSection = (schema: Record<string, any>, section: VideoPromptSchemaSectionConfig): React.ReactNode => {
const value = readSectionValue(schema, section.key);
if (!isRecord(value)) return <JsonBlock value={value ?? {}} maxHeight={220} />;
const fields = section.children.filter((field) => field.enabled);
if (!fields.length) return <JsonBlock value={value} maxHeight={220} />;
return (
<Descriptions size="small" column={2} bordered>
{fields.map((field) => (
<Descriptions.Item key={field.key} label={<Space size={4}>{field.label}{tagOfEditable(field.editable)}</Space>} span={isLongText(value[field.key]) || isRecord(value[field.key]) || Array.isArray(value[field.key]) ? 2 : 1}>
{renderValue(value[field.key])}
</Descriptions.Item>
))}
</Descriptions>
);
};
const buildFlowFields = (section: VideoPromptSchemaSectionConfig, rows: Record<string, unknown>[]): VideoPromptSchemaFieldConfig[] => {
const configured = section.itemFields.filter((field) => field.enabled);
if (configured.length) {
return [{ key: '时间段', label: '时间段', enabled: true, editable: false }, ...configured];
}
const inferred = Array.from(new Set(rows.flatMap((row) => Object.keys(row))));
return inferred.map((key) => ({ key, label: key, enabled: true, editable: false }));
};
const renderFlowSection = (schema: Record<string, any>, section: VideoPromptSchemaSectionConfig): React.ReactNode => {
const value = readSectionValue(schema, section.key);
if (!Array.isArray(value) || !value.every(isRecord)) return <JsonBlock value={value ?? []} maxHeight={260} />;
const rows = value as Record<string, unknown>[];
const fields = buildFlowFields(section, rows);
const columns: ColumnsType<Record<string, unknown>> = fields.map((field) => ({
title: <Space size={4}>{field.label}{tagOfEditable(field.editable)}</Space>,
dataIndex: field.key,
key: field.key,
width: isLongText(rows.find((row) => row[field.key])?.[field.key]) ? 320 : 160,
render: (cell: unknown) => renderValue(cell),
}));
return (
<Table
size="small"
rowKey={(_, index) => `${section.key}-${index}`}
columns={columns}
dataSource={rows}
pagination={false}
scroll={{ x: Math.max(900, fields.length * 180) }}
tableLayout="fixed"
/>
);
};
const renderPrimitiveSection = (schema: Record<string, any>, section: VideoPromptSchemaSectionConfig): React.ReactNode => {
const value = readSectionValue(schema, section.key);
return renderValue(value);
};
interface VideoPromptSchemaViewerProps {
schema?: Record<string, any> | null;
finalPrompt?: string | null;
schemaConfigSnapshot?: Record<string, any> | null;
schemaConfigSource?: string | null;
schemaConfigVersion?: string | null;
schemaConfigIsFallback?: boolean | null;
}
const VideoPromptSchemaViewer: React.FC<VideoPromptSchemaViewerProps> = ({
schema,
finalPrompt,
schemaConfigSnapshot,
schemaConfigSource,
schemaConfigVersion,
schemaConfigIsFallback,
}) => {
const hasSchema = !!schema && Object.keys(schema).length > 0;
if (!hasSchema && !finalPrompt) {
return <Empty description="暂无视频提词 schema" />;
}
const safeSchema = hasSchema ? schema as Record<string, any> : {};
const hasSnapshot = hasSchemaConfigSnapshot(schemaConfigSnapshot);
const sections = hasSnapshot ? normalizeSchemaSections(schemaConfigSnapshot) : inferSchemaSections(safeSchema);
const schemaItems = sections
.filter((section) => section.key in safeSchema)
.map((section) => ({
key: section.key,
label: (
<Space size={8} wrap>
<span>{section.label}</span>
{tagOfEditable(section.editable)}
{!hasSnapshot ? <Tag color="warning">管理后台兜底展示</Tag> : null}
</Space>
),
children: section.type === 'object'
? renderObjectSection(safeSchema, section)
: section.type === 'flow'
? renderFlowSection(safeSchema, section)
: renderPrimitiveSection(safeSchema, section),
}));
return (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Card size="small" title="Schema 配置来源">
<Descriptions size="small" column={3} bordered>
<Descriptions.Item label="来源">{schemaConfigSource || schemaConfigSnapshot?.source || (hasSnapshot ? '-' : 'viewer_fallback')}</Descriptions.Item>
<Descriptions.Item label="版本">{schemaConfigVersion || schemaConfigSnapshot?.version || '-'}</Descriptions.Item>
<Descriptions.Item label="历史兜底">{schemaConfigIsFallback || !hasSnapshot ? <Tag color="warning"></Tag> : <Tag color="success"></Tag>}</Descriptions.Item>
</Descriptions>
</Card>
{finalPrompt ? (
<Card size="small" title="最终视频提示词">
<Typography.Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>{finalPrompt}</Typography.Paragraph>
</Card>
) : null}
{schemaItems.length ? (
<Collapse size="small" items={schemaItems} />
) : hasSchema ? (
<Card size="small" title="视频提词 Schema">
<JsonBlock value={safeSchema} maxHeight={360} />
</Card>
) : null}
{hasSchema ? (
<Collapse
size="small"
items={[{
key: 'raw_schema',
label: '原始 JSON / 历史脏字段排查',
children: <JsonBlock value={safeSchema} maxHeight={420} />,
}]}
/>
) : null}
</Space>
);
};
export default VideoPromptSchemaViewer;