Add opener audio functionality to Assistant model and related schemas, enabling audio generation and playback features. Update API routes and frontend components to support opener audio management, including status retrieval and generation controls.
This commit is contained in:
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { Plus, Search, Play, Copy, Trash2, Mic, MessageSquare, Save, Video, PhoneOff, Camera, ArrowLeftRight, Send, Phone, Rocket, AlertTriangle, PhoneCall, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Wrench, Globe, Terminal, X, ClipboardCheck, Sparkles, Volume2, Timer, ChevronDown, Database, Server, Zap, ExternalLink, Key, BrainCircuit, Ear, Book, Filter } from 'lucide-react';
|
||||
import { Button, Input, Badge, Drawer, Dialog } from '../components/UI';
|
||||
import { ASRModel, Assistant, KnowledgeBase, LLMModel, TabValue, Tool, Voice } from '../types';
|
||||
import { createAssistant, deleteAssistant, fetchASRModels, fetchAssistants, fetchKnowledgeBases, fetchLLMModels, fetchTools, fetchVoices, updateAssistant as updateAssistantApi } from '../services/backendApi';
|
||||
import { createAssistant, deleteAssistant, fetchASRModels, fetchAssistants, fetchKnowledgeBases, fetchLLMModels, fetchTools, fetchVoices, generateAssistantOpenerAudio, updateAssistant as updateAssistantApi } from '../services/backendApi';
|
||||
|
||||
const isOpenAICompatibleVendor = (vendor?: string) => {
|
||||
const normalized = String(vendor || '').trim().toLowerCase();
|
||||
@@ -108,6 +108,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [persistedAssistantSnapshotById, setPersistedAssistantSnapshotById] = useState<Record<string, string>>({});
|
||||
const [unsavedDebugConfirmOpen, setUnsavedDebugConfirmOpen] = useState(false);
|
||||
const [openerAudioGenerating, setOpenerAudioGenerating] = useState(false);
|
||||
|
||||
const selectedAssistant = assistants.find(a => a.id === selectedId) || null;
|
||||
const serializeAssistant = (assistant: Assistant) => JSON.stringify(assistant);
|
||||
@@ -164,6 +165,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
firstTurnMode: 'bot_first',
|
||||
opener: '',
|
||||
generatedOpenerEnabled: false,
|
||||
openerAudioEnabled: false,
|
||||
prompt: '',
|
||||
knowledgeBaseId: '',
|
||||
language: 'zh',
|
||||
@@ -269,6 +271,31 @@ export const AssistantsPage: React.FC = () => {
|
||||
setDebugOpen(true);
|
||||
};
|
||||
|
||||
const handleGenerateOpenerAudio = async () => {
|
||||
if (!selectedAssistant) return;
|
||||
setOpenerAudioGenerating(true);
|
||||
try {
|
||||
const status = await generateAssistantOpenerAudio(selectedAssistant.id, {
|
||||
text: selectedAssistant.opener || '',
|
||||
});
|
||||
setAssistants((prev) => prev.map((item) => {
|
||||
if (item.id !== selectedAssistant.id) return item;
|
||||
return {
|
||||
...item,
|
||||
openerAudioEnabled: status.enabled,
|
||||
openerAudioReady: status.ready,
|
||||
openerAudioDurationMs: status.duration_ms,
|
||||
openerAudioUpdatedAt: status.updated_at || '',
|
||||
};
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert((error as Error)?.message || '生成开场白预加载音频失败');
|
||||
} finally {
|
||||
setOpenerAudioGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmOpenDebug = () => {
|
||||
setUnsavedDebugConfirmOpen(false);
|
||||
setDebugOpen(true);
|
||||
@@ -676,6 +703,58 @@ export const AssistantsPage: React.FC = () => {
|
||||
? '通话接通后将根据提示词自动生成开场白。'
|
||||
: '接通通话后的第一句话。'}
|
||||
</p>
|
||||
|
||||
<div className="mt-3 p-3 rounded-lg border border-white/10 bg-white/[0.03] space-y-2">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<label className="text-xs font-semibold text-white flex items-center">
|
||||
<Volume2 className="w-4 h-4 mr-2 text-primary" />
|
||||
使用预加载开场音频
|
||||
</label>
|
||||
<div className="inline-flex rounded-lg border border-white/10 bg-white/5 p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateAssistant('openerAudioEnabled', false)}
|
||||
className={`px-3 py-1 text-xs rounded-md transition-colors ${
|
||||
selectedAssistant.openerAudioEnabled === true
|
||||
? 'text-muted-foreground hover:text-foreground'
|
||||
: 'bg-primary text-primary-foreground shadow-sm'
|
||||
}`}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateAssistant('openerAudioEnabled', true)}
|
||||
className={`px-3 py-1 text-xs rounded-md transition-colors ${
|
||||
selectedAssistant.openerAudioEnabled === true
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
开启
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
状态:
|
||||
{selectedAssistant.openerAudioReady
|
||||
? `已生成 (${Math.round((selectedAssistant.openerAudioDurationMs || 0) / 1000)}s)`
|
||||
: '未生成'}
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleGenerateOpenerAudio}
|
||||
disabled={openerAudioGenerating || selectedAssistant.generatedOpenerEnabled === true}
|
||||
>
|
||||
{openerAudioGenerating ? '生成中...' : '生成开场预加载音频'}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
使用当前 TTS 配置生成并保存到后端;引擎可直接播放以降低首包延迟。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user