voice route updated

This commit is contained in:
ValueOn AG 2025-11-01 09:05:10 +01:00
parent 259ccabbe3
commit 37aad732f5
7 changed files with 51 additions and 59 deletions

View file

@ -723,15 +723,16 @@ class ConnectorGoogleSpeech:
logger.info("🌐 Getting available languages from Google Cloud TTS")
# List voices from Google Cloud TTS
voices = self.tts_client.list_voices()
response = self.tts_client.list_voices()
# Extract unique language codes
# Note: Google TTS API doesn't provide language descriptions, only codes
language_codes = set()
for voice in voices:
for voice in response.voices:
if voice.language_codes:
language_codes.update(voice.language_codes)
# Convert to sorted list
# Convert to sorted list of language codes
available_languages = sorted(list(language_codes))
logger.info(f"✅ Found {len(available_languages)} available languages")
@ -763,11 +764,11 @@ class ConnectorGoogleSpeech:
logger.info(f"🎤 Getting available voices from Google Cloud TTS, language filter: {languageCode}")
# List voices from Google Cloud TTS
voices = self.tts_client.list_voices()
response = self.tts_client.list_voices()
availableVoices = []
for voice in voices:
for voice in response.voices:
# Extract language code from voice name (e.g., 'de-DE-Wavenet-A' -> 'de-DE')
voiceLanguage = voice.language_codes[0] if voice.language_codes else None
@ -783,15 +784,24 @@ class ConnectorGoogleSpeech:
elif voice.name.endswith(('-B', '-D')):
gender = "Female"
# Create voice info
# Create voice info with all available fields from Google API
voiceInfo = {
"name": voice.name,
"language_code": voiceLanguage,
"language_codes": list(voice.language_codes) if voice.language_codes else [],
"gender": gender,
"ssml_gender": voice.ssml_gender.name if voice.ssml_gender else "NEUTRAL",
"natural_sample_rate_hertz": voice.natural_sample_rate_hertz
}
# Include any additional fields if available from Google API
# Check for common fields that might exist
for field_name in ['description', 'display_name', 'labels']:
if hasattr(voice, field_name):
field_value = getattr(voice, field_name, None)
if field_value:
voiceInfo[field_name] = field_value
availableVoices.append(voiceInfo)
# Sort by language code, then by gender, then by name

View file

@ -9,7 +9,7 @@ import uuid
class VoiceSettings(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="Primary key", frontend_type="text", frontend_readonly=True, frontend_required=False)
userId: str = Field(description="ID of the user these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=True)
mandateId: str = Field(description="ID of the mandate these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=False)
mandateId: str = Field(description="ID of the mandate these settings belong to", frontend_type="text", frontend_readonly=True, frontend_required=True)
sttLanguage: str = Field(default="de-DE", description="Speech-to-Text language", frontend_type="select", frontend_readonly=False, frontend_required=True)
ttsLanguage: str = Field(default="de-DE", description="Text-to-Speech language", frontend_type="select", frontend_readonly=False, frontend_required=True)
ttsVoice: str = Field(default="de-DE-KatjaNeural", description="Text-to-Speech voice", frontend_type="select", frontend_readonly=False, frontend_required=True)

View file

@ -268,6 +268,12 @@ class VoiceObjects:
try:
logger.info(f"Creating voice settings: {settingsData}")
# Ensure mandateId is set from user context if not provided
if "mandateId" not in settingsData or not settingsData["mandateId"]:
if not self.currentUser or not self.currentUser.mandateId:
raise ValueError("mandateId is required but not provided and user context has no mandateId")
settingsData["mandateId"] = self.currentUser.mandateId
# Add timestamps
currentTime = getUtcTimestamp()
settingsData["creationDate"] = currentTime
@ -330,6 +336,7 @@ class VoiceObjects:
# Create default settings if none exist
defaultSettings = {
"userId": userId,
"mandateId": self.currentUser.mandateId,
"sttLanguage": "de-DE",
"ttsLanguage": "de-DE",
"ttsVoice": "de-DE-Wavenet-A",

View file

@ -87,7 +87,7 @@ class RendererDocx(BaseRenderer):
# Process each section in order
sections = json_content.get("sections", [])
for section in sections:
self._render_json_section(doc, section, styles)
self._renderJsonSection(doc, section, styles)
# Save to buffer
buffer = io.BytesIO()

View file

@ -96,7 +96,7 @@ class RendererPdf(BaseRenderer):
title_style = self._createTitleStyle(styles)
story.append(Paragraph(document_title, title_style))
story.append(Spacer(1, 50)) # Increased spacing to prevent overlap
story.append(Paragraph(f"Generated: {self._format_timestamp()}", self._createNormalStyle(styles)))
story.append(Paragraph(f"Generated: {self._formatTimestamp()}", self._createNormalStyle(styles)))
story.append(Spacer(1, 30)) # Add spacing before page break
story.append(PageBreak())

View file

@ -95,9 +95,12 @@ Instructions:
- Continue from where it stopped add NEW items only; do not repeat existing items.
- Generate remaining content to complete the user request.
- Fill with actual content (no placeholders or instructional text such as "Add more...").
- IMPORTANT: Ensure "filename" in each document has meaningful name with appropriate extension matching the content.
- When the request is fully satisfied, add "complete_response": true at root level.
- Output JSON only; no markdown fences or extra text.
IMPORTANT: Before responding, analyse the remaining data to fully satisfy user request.
Continue generating:
"""
else:
@ -114,6 +117,7 @@ Instructions:
- Return ONLY valid JSON (strict). No comments. No trailing commas. Use double quotes.
- Do NOT reuse example section IDs; create your own.
- Generate complete content based on the user request.
- IMPORTANT: Set a meaningful "filename" in each document with appropriate file extension (e.g., "prime_numbers.txt", "report.docx", "data.json"). The filename should reflect the content and task objective.
- When the request is fully satisfied, add "complete_response": true at root level.
- Output JSON only; no markdown fences or extra text.

View file

@ -83,10 +83,12 @@ class ContentValidator:
availableModels = modelRegistry.getAvailableModels()
# Create options for PLAN operation (what validation uses)
# Use default values for priority and processingMode (will use defaults from AiCallOptions)
from modules.datamodels.datamodelAi import PriorityEnum, ProcessingModeEnum
options = AiCallOptions(
operationType=OperationTypeEnum.PLAN,
priority=None,
processingMode=None
priority=PriorityEnum.BALANCED,
processingMode=ProcessingModeEnum.BASIC
)
# Get failover model list to find the model that will be used
@ -118,60 +120,28 @@ class ContentValidator:
return 8 * 1024
def _analyzeDocumentsWithSizeLimit(self, documents: List[Any], maxTotalBytes: int) -> List[Dict[str, Any]]:
"""Analyze documents with size limit, dividing available space evenly among documents."""
"""
Analyze documents for validation - METADATA ONLY (no document content/previews).
For planning/validation, we only need metadata to assess format, type, and size compatibility.
"""
if not documents:
return []
# Reserve space for JSON structure overhead (approximately 200 bytes per document)
jsonOverheadPerDoc = 200
reservedOverhead = len(documents) * jsonOverheadPerDoc
availableForContent = max(0, maxTotalBytes - reservedOverhead)
# Divide available space evenly among documents
bytesPerDoc = availableForContent // len(documents) if documents else 0
# Ensure minimum space per document (at least 100 bytes)
bytesPerDoc = max(bytesPerDoc, 100)
logger.debug(f"Document summary space: total={maxTotalBytes} bytes, available={availableForContent} bytes, perDoc={bytesPerDoc} bytes")
summaries = []
for doc in documents:
try:
data = getattr(doc, 'documentData', None)
name = getattr(doc, 'documentName', 'Unknown')
mimeType = getattr(doc, 'mimeType', 'unknown')
formatExt = self._detectFormat(doc)
sizeInfo = self._calculateSize(doc)
# Create preview with size limit
preview = None
if data is not None:
if isinstance(data, (dict, list)):
preview = json.dumps(data, indent=2, ensure_ascii=False)
else:
preview = str(data)
# Truncate preview to fit within bytesPerDoc (accounting for JSON structure)
# Estimate: preview takes ~70% of document summary space
maxPreviewBytes = int(bytesPerDoc * 0.7)
previewBytes = len(preview.encode('utf-8'))
if previewBytes > maxPreviewBytes:
# Truncate to fit
truncated = preview.encode('utf-8')[:maxPreviewBytes]
# Try to decode safely
try:
preview = truncated.decode('utf-8', errors='ignore')
except:
preview = truncated[:maxPreviewBytes-50].decode('utf-8', errors='ignore')
preview += f"\n\n[Truncated - {self._formatBytes(sizeInfo['bytes'])} total]"
# Only include metadata - NO document content/previews
# This keeps prompts small and focused on validation criteria
summary = {
"name": name,
"mimeType": mimeType,
"format": formatExt,
"size": sizeInfo["readable"],
"preview": preview
"size": sizeInfo["readable"]
}
summaries.append(summary)
except Exception as e:
@ -181,7 +151,6 @@ class ContentValidator:
"mimeType": getattr(doc, 'mimeType', 'unknown'),
"format": "unknown",
"size": "0 B",
"preview": None,
"error": str(e)
})
@ -291,12 +260,14 @@ EXPECTED FORMAT: {intent.get('expectedFormat', 'unknown')}
SUCCESS CRITERIA ({criteriaCount} items): {successCriteria}
VALIDATION RULES:
1. Check if delivered documents match expected data type
IMPORTANT: You only have document METADATA (filename, format, size, mimeType) - NOT document content.
Validate based on metadata only:
1. Check if filenames are meaningful and match approximately the task objective
2. Check if delivered formats are compatible with expected format
3. Verify each success criterion is met based on document content/metadata
4. Check document sizes are reasonable for the task
5. Rate overall quality (0.0-1.0)
6. Identify specific gaps based on what the user requested
3. Check if document sizes are reasonable for the task objective
4. Assess if filename and size combination suggests correct data type
5. Rate overall quality (0.0-1.0) based on metadata indicators
6. Identify specific gaps based on what the user requested (infer from filename, size, format - NOT content)
OUTPUT FORMAT - JSON ONLY (no prose):
{{
@ -306,13 +277,13 @@ OUTPUT FORMAT - JSON ONLY (no prose):
"formatMatch": false,
"documentCount": {len(documents)},
"successCriteriaMet": {[False] * criteriaCount},
"gapAnalysis": "Describe what is missing or incorrect",
"gapAnalysis": "Describe what is missing or incorrect based on filename, size, format metadata",
"improvementSuggestions": ["General action to improve overall result"],
"validationDetails": [
{{
"documentName": "document.ext",
"issues": ["Specific problem with this document"],
"suggestions": ["Specific fix for this document's issues"]
"issues": ["Issue inferred from metadata (e.g., filename doesn't match task, size too small for objective)"],
"suggestions": ["Specific fix based on metadata analysis"]
}}
]
}}