From a1ee476e0f46e212f7d1957efd49d2c73a13e178 Mon Sep 17 00:00:00 2001 From: bdim404 Date: Thu, 12 Mar 2026 15:42:53 +0800 Subject: [PATCH] feat: Enhance narrator description and instructions in LLMService and Audiobook components --- qwen3-tts-backend/core/audiobook_service.py | 12 +- qwen3-tts-backend/core/llm_service.py | 7 ++ qwen3-tts-frontend/src/pages/Audiobook.tsx | 125 +++++++++++--------- 3 files changed, 89 insertions(+), 55 deletions(-) diff --git a/qwen3-tts-backend/core/audiobook_service.py b/qwen3-tts-backend/core/audiobook_service.py index ce09250..0df2a72 100644 --- a/qwen3-tts-backend/core/audiobook_service.py +++ b/qwen3-tts-backend/core/audiobook_service.py @@ -225,8 +225,16 @@ async def analyze_project(project_id: int, user: User, db: Session, turbo: bool if not has_narrator: characters_data.insert(0, { "name": "narrator", - "description": "旁白叙述者", - "instruct": "中性声音,语速平稳,叙述感强" + "gender": "未知", + "description": "第三人称旁白叙述者", + "instruct": ( + "音色信息:浑厚醇厚的男性中低音,嗓音饱满有力,带有传统说书人的磁性与感染力\n" + "身份背景:中国传统说书艺人,精通评书、章回小说叙述艺术,深谙故事节奏与听众心理\n" + "年龄设定:中年男性,四五十岁,声音历经岁月沉淀,成熟稳重而不失活力\n" + "外貌特征:面容沉稳,气度从容,台风大气,给人以可信赖的叙述者印象\n" + "性格特质:沉稳睿智,叙事冷静客观,情到深处能引发共鸣,不动声色间娓娓道来\n" + "叙事风格:语速适中偏慢,抑扬顿挫,擅长铺垫悬念,停顿恰到好处,语气庄重而生动,富有画面感" + ) }) ps.append_line(key, f"\n\n[完成] 发现 {len(characters_data)} 个角色:{', '.join(c.get('name', '') for c in characters_data)}") diff --git a/qwen3-tts-backend/core/llm_service.py b/qwen3-tts-backend/core/llm_service.py index b93613b..43f8a37 100644 --- a/qwen3-tts-backend/core/llm_service.py +++ b/qwen3-tts-backend/core/llm_service.py @@ -131,6 +131,13 @@ class LLMService: "5. 性格特质:核心性格、情绪模式、表达习惯\n" "6. 叙事风格:语速节奏、停顿习惯、语气色彩、整体叙述感\n\n" "注意:instruct 的第一行(音色信息)必须与 gender 字段保持一致。如果 gender 为女,第一行绝对不能出现'男性'字样。\n\n" + "【特别规定】narrator(旁白)的 instruct 必须固定描述为传统说书人风格,参考如下模板(根据书籍风格可微调措辞,但风格不变):\n" + "音色信息:浑厚醇厚的男性中低音,嗓音饱满有力,带有传统说书人的磁性与感染力\n" + "身份背景:中国传统说书艺人,精通评书、章回小说叙述艺术,深谙故事节奏与听众心理\n" + "年龄设定:中年男性,四五十岁,声音历经岁月沉淀,成熟稳重而不失活力\n" + "外貌特征:面容沉稳,气度从容,台风大气,给人以可信赖的叙述者印象\n" + "性格特质:沉稳睿智,叙事冷静客观,情到深处能引发共鸣,不动声色间娓娓道来\n" + "叙事风格:语速适中偏慢,抑扬顿挫,擅长铺垫悬念,停顿恰到好处,语气庄重而生动,富有画面感\n\n" "只输出JSON,格式如下,不要有其他文字:\n" '{"characters": [{"name": "narrator", "gender": "未知", "description": "第三人称叙述者", "instruct": "音色信息:...\\n身份背景:...\\n年龄设定:...\\n外貌特征:...\\n性格特质:...\\n叙事风格:..."}, ...]}' ) diff --git a/qwen3-tts-frontend/src/pages/Audiobook.tsx b/qwen3-tts-frontend/src/pages/Audiobook.tsx index 6370983..91bfe14 100644 --- a/qwen3-tts-frontend/src/pages/Audiobook.tsx +++ b/qwen3-tts-frontend/src/pages/Audiobook.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' -import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2 } from 'lucide-react' +import { Book, Plus, Trash2, RefreshCw, Download, ChevronDown, ChevronUp, Play, Square, Pencil, Check, X, Loader2, Zap, Settings2, PanelLeftClose, PanelLeftOpen } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' @@ -334,6 +334,8 @@ function ProjectListSidebar({ onNew, onLLM, loading, + collapsed, + onToggle, }: { projects: AudiobookProject[] selectedId: number | null @@ -341,41 +343,55 @@ function ProjectListSidebar({ onNew: () => void onLLM: () => void loading: boolean + collapsed: boolean + onToggle: () => void }) { const { t } = useTranslation('audiobook') return ( -
-
- {t('title')} -
- - -
-
-
- {loading ? ( -
{t('loading')}
- ) : projects.length === 0 ? ( -
{t('noProjects')}
- ) : ( - projects.map(p => ( - - )) +
+
+ {!collapsed && ( +
+ Qwen + {t('title')} +
)} +
+ {!collapsed && ( + <> +
+ + +
+
+ {loading ? ( +
{t('loading')}
+ ) : projects.length === 0 ? ( +
{t('noProjects')}
+ ) : ( + projects.map(p => ( + + )) + )} +
+ + )}
) } @@ -465,8 +481,8 @@ function CharactersPanel({ const charCount = detail?.characters.length ?? 0 return ( -
-
+
+
{t('projectCard.characters.title', { count: charCount })} @@ -659,8 +675,8 @@ function ChaptersPanel({ const hasChapters = detail && detail.chapters.length > 0 && ['ready', 'generating', 'done'].includes(status) return ( -
-
+
+
{t('projectCard.chapters.title', { count: detail?.chapters.length ?? 0 })} @@ -818,6 +834,7 @@ export default function Audiobook() { const [sequentialPlayingId, setSequentialPlayingId] = useState(null) const [showCreate, setShowCreate] = useState(false) const [showLLM, setShowLLM] = useState(false) + const [sidebarOpen, setSidebarOpen] = useState(true) const prevStatusRef = useRef('') const autoExpandedRef = useRef(new Set()) @@ -1145,24 +1162,26 @@ export default function Audiobook() { const isTurboMode = ['analyzing', 'parsing', 'processing'].includes(displayStatus) return ( -
- -
- { - if (id !== selectedProjectId) { - setSelectedProjectId(id) - setIsPolling(false) - setGeneratingChapterIndices(new Set()) - } - }} - onNew={() => { setShowCreate(v => !v); setShowLLM(false) }} - onLLM={() => { setShowLLM(v => !v); setShowCreate(false) }} - loading={loading} - /> -
+
+ { + if (id !== selectedProjectId) { + setSelectedProjectId(id) + setIsPolling(false) + setGeneratingChapterIndices(new Set()) + } + }} + onNew={() => { setShowCreate(v => !v); setShowLLM(false) }} + onLLM={() => { setShowLLM(v => !v); setShowCreate(false) }} + loading={loading} + collapsed={!sidebarOpen} + onToggle={() => setSidebarOpen(v => !v)} + /> +
+ +
{showLLM && (
setShowLLM(false)} />