voices
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions

This commit is contained in:
Dmitry 2025-12-19 06:03:04 +03:00
parent d8a29eb84a
commit 47005a357a
7 changed files with 80 additions and 18 deletions

View file

@ -11,17 +11,49 @@ export async function upsertCardWithOptionalVoice(
card: GameCardDto,
voice?: PendingVoiceUpload,
): Promise<{ success: boolean; card: GameCardDto }> {
console.log('🔍 upsertCardWithOptionalVoice: Starting', {
cardId: card.id,
hasVoice: !!voice,
voiceUrl: voice?.voiceUrl,
language: voice?.language,
})
const response = await cardsApi.upsertCard(card)
const responseCardId = response.card?.id != null ? String(response.card.id) : ''
const requestCardId = card.id != null ? String(card.id) : ''
const cardId = responseCardId || requestCardId
console.log('🔍 upsertCardWithOptionalVoice: After upsertCard', {
responseCardId,
requestCardId,
cardId,
hasVoice: !!voice,
voiceUrl: voice?.voiceUrl,
})
if (!voice || !voice.voiceUrl || !cardId) {
console.log('⚠️ upsertCardWithOptionalVoice: Skipping voice link', {
hasVoice: !!voice,
voiceUrl: voice?.voiceUrl,
cardId,
})
return response
}
await voicesApi.addCardVoice(cardId, voice.voiceUrl, voice.language)
console.log('✅ upsertCardWithOptionalVoice: Calling addCardVoice', {
cardId,
voiceUrl: voice.voiceUrl,
language: voice.language,
})
try {
await voicesApi.addCardVoice(cardId, voice.voiceUrl, voice.language)
console.log('✅ upsertCardWithOptionalVoice: Successfully added voice')
} catch (error) {
console.error('❌ upsertCardWithOptionalVoice: Error adding voice', error)
throw error
}
return response
}

View file

@ -30,9 +30,16 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage
// Add voice mutation
const addVoiceMutation = useMutation({
mutationFn: ({ voiceUrl, language }: { voiceUrl: string; language: string }) =>
voicesApi.addCardVoice(cardId, voiceUrl, language),
mutationFn: ({ voiceUrl, language }: { voiceUrl: string; language: string }) => {
console.log('🔍 CardVoicesManager: addVoiceMutation.mutationFn called', {
cardId,
voiceUrl,
language,
})
return voicesApi.addCardVoice(cardId, voiceUrl, language)
},
onSuccess: () => {
console.log('✅ CardVoicesManager: addVoiceMutation.onSuccess')
queryClient.invalidateQueries({ queryKey: ['cardVoices', cardId] })
toast.success('Voice added successfully')
setShowAddForm(false)
@ -40,6 +47,7 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage
setNewLanguage('en')
},
onError: (error: unknown) => {
console.error('❌ CardVoicesManager: addVoiceMutation.onError', error)
const axiosError = error as AxiosError<{ message?: string }>
toast.error(axiosError.response?.data?.message || 'Failed to add voice')
},
@ -59,11 +67,24 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage
})
const handleAddVoice = () => {
console.log('🔍 CardVoicesManager: handleAddVoice called', {
newAudio,
newLanguage,
cardId,
})
if (!newAudio) {
console.warn('⚠️ CardVoicesManager: newAudio is empty')
toast.error('Please upload an audio file')
return
}
console.log('✅ CardVoicesManager: Calling addVoiceMutation.mutate', {
cardId,
voiceUrl: newAudio,
language: newLanguage,
})
addVoiceMutation.mutate({
voiceUrl: newAudio,
language: newLanguage,
@ -103,7 +124,10 @@ export function CardVoicesManager({ cardId, disabled = false }: CardVoicesManage
<AudioUpload
label="Audio File"
value={newAudio}
onChange={setNewAudio}
onChange={(value) => {
console.log('🔍 CardVoicesManager: AudioUpload onChange', { value, cardId })
setNewAudio(value)
}}
language={newLanguage}
onLanguageChange={setNewLanguage}
disabled={disabled || addVoiceMutation.isPending}

View file

@ -868,6 +868,8 @@ class AdminCardsApiV2 {
// Create voice record with object ID
print('🔍 addCardVoice: Creating voice record with objectId $objectId for card $cardId');
print('🔍 addCardVoice: VoiceModelsCompanion.insert params: cardId=$cardId, voiceUrl=$objectId, language=${requestDto.language ?? 'en'}');
final voiceId = await _db.packDao.createVoice(
VoiceModelsCompanion.insert(
cardId: cardId,
@ -881,6 +883,10 @@ class AdminCardsApiV2 {
print('🔍 addCardVoice: Linking voice $voiceId to card $cardId');
await _db.packDao.addVoiceToCard(cardId, voiceId);
print('✅ addCardVoice: Successfully linked voice $voiceId to card $cardId');
// Verify the link was created
final verifyVoices = await _db.packDao.getCardVoices(cardId);
print('🔍 addCardVoice: Verification - found ${verifyVoices.length} voices for card $cardId');
final voice = await _db.packDao.getVoiceById(voiceId);

View file

@ -8,7 +8,6 @@ import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import '../../../di/app_scope/app_scope_container.dart';
import '../../../domain/config/api_config_v2.dart';
import '../../../presentation/theme/app_colors.dart';
import '../../../presentation/widgets/pack_card_item.dart';
import '../../../presentation/widgets/pack_details_header.dart';
@ -20,6 +19,7 @@ import '../../../presentation/widgets/card_viewer.dart';
import '../../../presentation/widgets/shuffle_animated_switcher.dart';
import '../../../presentation/widgets/shuffle_movement_wrapper.dart';
import '../../../presentation/widgets/utils/card_movement_utils.dart';
import '../../../utils/card_image_utils.dart';
import '../../../utils/color_extension.dart';
import '../../../utils/responsive.dart';
@ -823,8 +823,8 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
/// Изображение карточки (вынесено в отдельный метод для переиспользования)
Widget _buildCardImage(GameCardDto card, double width, double height) {
// Use presigned URL from DTO
final imageUrl = card.imageUrl;
// Use helper to get image URL (handles imageUrl, image URL, and filename)
final imageUrl = CardImageUtils.getCardImageUrl(card, widget.packId);
if (imageUrl == null || imageUrl.isEmpty) {
return Center(

View file

@ -7,10 +7,10 @@ import 'package:mnemo_cards_web_v2/di/user_scope/user_scope.dart';
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import 'package:yx_state_flutter/yx_state_flutter.dart';
import '../../../domain/config/api_config_v2.dart';
import '../../../domain/state/card_flipper_state_manager.dart';
import '../../../presentation/widgets/card_voice_controls.dart';
import '../../../presentation/widgets/mnemo_text.dart';
import '../../../utils/card_image_utils.dart';
/// Card flipper widget for studying cards
///
@ -720,8 +720,8 @@ class _CardSide extends StatelessWidget {
}
Widget _buildImage() {
// Use presigned URL from DTO
final imageUrl = card.imageUrl;
// Use helper to get image URL (handles imageUrl, image URL, and filename)
final imageUrl = CardImageUtils.getCardImageUrl(card, packId);
if (imageUrl == null || imageUrl.isEmpty) {
return Container(

View file

@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../../domain/config/api_config_v2.dart';
import '../../presentation/theme/app_colors.dart';
import '../../utils/card_image_utils.dart';
import 'card_favorite_button.dart';
import 'card_voice_controls.dart';
import 'mnemo_text.dart';
@ -565,8 +565,8 @@ class _CardSide extends StatelessWidget {
}
Widget _buildImage() {
// Use presigned URL from DTO
final imageUrl = card.imageUrl;
// Use helper to get image URL (handles imageUrl, image URL, and filename)
final imageUrl = CardImageUtils.getCardImageUrl(card, packId);
if (imageUrl == null || imageUrl.isEmpty) {
return Container(
@ -600,8 +600,8 @@ class _CardSide extends StatelessWidget {
}
Widget _buildImageBack() {
// Use presigned URL from DTO
final imageBackUrl = card.imageBackUrl;
// Use helper to get back image URL (handles imageBackUrl, imageBack URL, and filename)
final imageBackUrl = CardImageUtils.getCardImageBackUrl(card, packId);
if (imageBackUrl == null || imageBackUrl.isEmpty) {
return Container(

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../../domain/config/api_config_v2.dart';
import '../../utils/card_image_utils.dart';
import 'mnemo_text.dart';
/// Виджет для отображения отдельной карточки в сетке
@ -260,8 +260,8 @@ class PackCardItem extends StatelessWidget {
/// Отображает изображение карточки
/// Загружает изображение с бэкенда по URL
Widget _buildImage(BuildContext context, double cardWidth, double cardHeight) {
// Use presigned URL from DTO
final imageUrl = card.imageUrl;
// Use helper to get image URL (handles imageUrl, image URL, and filename)
final imageUrl = CardImageUtils.getCardImageUrl(card, packId);
if (imageUrl == null || imageUrl.isEmpty) {
return const SizedBox.shrink();