feat(ui): 优化播客创建器文本输入体验和内容长度管理
实现前后端协同的内容长度控制机制。前端通过动态计数器展示 当前输入进度,接近上限时视觉提醒用户。后端API增强参数验证, 针对不同创作模式设定差异化阈值(标准20k/故事30k)。模式 切换时智能裁剪超长内容并友好提示。完善中英日三语国际化 文案支持,提升全球用户使用体验。
This commit is contained in:
@@ -98,6 +98,8 @@
|
||||
"pleaseSelectSpeaker": "Please select a speaker",
|
||||
"pleaseSelectAtLeastOneSpeaker": "Please select at least one podcast speaker.",
|
||||
"podcastGenerationFailed": "Podcast generation failed:",
|
||||
"textTruncated": "Text Truncated",
|
||||
"textTruncatedMessage": "After switching to the current mode, the text has been automatically truncated to {{maxChars}} characters to comply with the limit.",
|
||||
"maximum5Speakers": "You can select up to 5 speakers.",
|
||||
"chinese": "Chinese",
|
||||
"english": "English",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"user_not_logged_in_or_session_expired": "User not logged in or session expired",
|
||||
"request_body_cannot_be_empty": "Request body cannot be empty",
|
||||
"tts_provider_cannot_be_empty": "TTS provider cannot be empty",
|
||||
"input_text_exceeds_limit": "Input text exceeds limit, maximum {{limit}} characters allowed",
|
||||
"please_select_at_least_one_speaker": "Please select at least one podcast speaker",
|
||||
"invalid_speaker_config_format": "Invalid podcast speaker configuration format",
|
||||
"insufficient_points_for_podcast": "Insufficient points, generating a podcast requires {{pointsNeeded}} points, you currently have {{currentPoints}} points.",
|
||||
|
||||
@@ -98,6 +98,8 @@
|
||||
"pleaseSelectSpeaker": "スピーカーを選択してください",
|
||||
"pleaseSelectAtLeastOneSpeaker": "少なくとも1人のポッドキャストスピーカーを選択してください。",
|
||||
"podcastGenerationFailed": "ポッドキャストの生成に失敗しました:",
|
||||
"textTruncated": "テキストが切り詰められました",
|
||||
"textTruncatedMessage": "現在のモードに切り替えた後、テキストは制限に準拠するために自動的に {{maxChars}} 文字に切り詰められました。",
|
||||
"maximum5Speakers": "最大5人のスピーカーを選択できます。",
|
||||
"chinese": "中国語",
|
||||
"english": "英語",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"user_not_logged_in_or_session_expired": "ユーザーがログインしていないか、セッションの有効期限が切れています",
|
||||
"request_body_cannot_be_empty": "リクエストボディは空にできません",
|
||||
"tts_provider_cannot_be_empty": "TTSプロバイダーは空にできません",
|
||||
"input_text_exceeds_limit": "入力テキストが制限を超えています。最大 {{limit}} 文字まで許可されています",
|
||||
"please_select_at_least_one_speaker": "少なくとも1人のポッドキャスト話者を選択してください",
|
||||
"invalid_speaker_config_format": "無効なポッドキャスト話者設定フォーマット",
|
||||
"insufficient_points_for_podcast": "ポイントが不足しています。ポッドキャストを生成するには{{pointsNeeded}}ポイントが必要です。現在{{currentPoints}}ポイントしかありません。",
|
||||
|
||||
@@ -98,6 +98,8 @@
|
||||
"pleaseSelectSpeaker": "请选择说话人",
|
||||
"pleaseSelectAtLeastOneSpeaker": "请至少选择一位播客说话人。",
|
||||
"podcastGenerationFailed": "播客生成失败:",
|
||||
"textTruncated": "文本已截断",
|
||||
"textTruncatedMessage": "切换到当前模式后,文本已自动截断至 {{maxChars}} 字符以符合限制。",
|
||||
"maximum5Speakers": "最多只能选择5个说话人。",
|
||||
"chinese": "中文",
|
||||
"english": "英文",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"user_not_logged_in_or_session_expired": "用户未登录或会话已过期",
|
||||
"request_body_cannot_be_empty": "请求正文不能为空",
|
||||
"tts_provider_cannot_be_empty": "TTS服务提供商不能为空",
|
||||
"input_text_exceeds_limit": "输入文本超过限制,最多允许 {{limit}} 字符",
|
||||
"please_select_at_least_one_speaker": "请至少选择一位播客说话人",
|
||||
"invalid_speaker_config_format": "播客说话人配置格式无效",
|
||||
"insufficient_points_for_podcast": "积分不足,生成一个播客需要 {{pointsNeeded}} 积分,您当前只有 {{currentPoints}} 积分。",
|
||||
|
||||
@@ -33,6 +33,15 @@ export async function POST(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 字符数限制校验 - 沉浸故事模式限制30000字符
|
||||
const MAX_CHARS_AI_STORY = 30000;
|
||||
if (body.input_txt_content.length > MAX_CHARS_AI_STORY) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: t('input_text_exceeds_limit', { limit: MAX_CHARS_AI_STORY }) },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (!body.tts_provider || body.tts_provider.trim().length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: t('tts_provider_cannot_be_empty') },
|
||||
|
||||
@@ -33,6 +33,15 @@ export async function POST(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 字符数限制校验 - AI播客模式限制20000字符
|
||||
const MAX_CHARS_AI_PODCAST = 20000;
|
||||
if (body.input_txt_content.length > MAX_CHARS_AI_PODCAST) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: t('input_text_exceeds_limit', { limit: MAX_CHARS_AI_PODCAST }) },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (!body.tts_provider || body.tts_provider.trim().length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: t('tts_provider_cannot_be_empty') },
|
||||
|
||||
@@ -68,6 +68,26 @@ const PodcastCreator: React.FC<PodcastCreatorProps> = ({
|
||||
const [topic, setTopic] = useState('');
|
||||
const [customInstructions, setCustomInstructions] = useState('');
|
||||
const [selectedMode, setSelectedMode] = useState<'ai-podcast' | 'ai-story'>('ai-podcast');
|
||||
|
||||
// 字符数限制常量
|
||||
const MAX_CHARS_AI_PODCAST = 20000;
|
||||
const MAX_CHARS_AI_STORY = 30000;
|
||||
|
||||
// 获取当前模式的字符数限制
|
||||
const maxChars = selectedMode === 'ai-podcast' ? MAX_CHARS_AI_PODCAST : MAX_CHARS_AI_STORY;
|
||||
|
||||
// 监听模式切换,如果文本超过新模式的限制,则截断
|
||||
useEffect(() => {
|
||||
if (topic.length > maxChars) {
|
||||
const truncatedTopic = topic.substring(0, maxChars);
|
||||
setTopic(truncatedTopic);
|
||||
setItem('podcast-topic', truncatedTopic);
|
||||
error(
|
||||
t('podcastCreator.textTruncated'),
|
||||
t('podcastCreator.textTruncatedMessage', { maxChars })
|
||||
);
|
||||
}
|
||||
}, [selectedMode, maxChars]); // 只在模式切换时触发
|
||||
|
||||
// 初始化时从 localStorage 加载 topic 和 customInstructions
|
||||
useEffect(() => {
|
||||
@@ -366,14 +386,27 @@ const PodcastCreator: React.FC<PodcastCreatorProps> = ({
|
||||
<textarea
|
||||
value={topic}
|
||||
onChange={(e) => {
|
||||
setTopic(e.target.value);
|
||||
setItem('podcast-topic', e.target.value); // 实时保存到 localStorage
|
||||
const newValue = e.target.value;
|
||||
// 如果超过字符数限制,截取到最大长度
|
||||
const finalValue = newValue.length > maxChars ? newValue.substring(0, maxChars) : newValue;
|
||||
setTopic(finalValue);
|
||||
setItem('podcast-topic', finalValue); // 实时保存到 localStorage
|
||||
}}
|
||||
placeholder={t('podcastCreator.enterTextPlaceholder')}
|
||||
className="w-full h-32 resize-none border-none outline-none text-lg placeholder-neutral-400 bg-white"
|
||||
className="w-full h-48 resize-none border-none outline-none text-lg placeholder-neutral-400 bg-white"
|
||||
disabled={isGenerating}
|
||||
/>
|
||||
|
||||
{/* 字符数统计 */}
|
||||
<div className="flex justify-end mt-2">
|
||||
<span className={cn(
|
||||
"text-sm",
|
||||
topic.length > maxChars * 0.9 ? "text-red-500 font-medium" : "text-neutral-400"
|
||||
)}>
|
||||
{topic.length} / {maxChars}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 自定义指令 */}
|
||||
{customInstructions !== undefined && selectedMode === 'ai-podcast' && (
|
||||
<div className="mt-4 pt-4 border-t border-neutral-100">
|
||||
|
||||
Reference in New Issue
Block a user