feat: AI Brand Voice Translator integration and Mesh Content fix

This commit is contained in:
2026-06-03 23:56:58 -05:00
parent ad8622e243
commit 0676e9f0a8
40 changed files with 3186 additions and 1481 deletions
+64 -15
View File
@@ -1,13 +1,16 @@
import React, { useState, useCallback } from 'react';
import { Save, AlertCircle, Crown, FolderOpen, Sparkles } from 'lucide-react';
import { DesignMD, CompanyProfile } from '../types';
import React, { useState, useCallback, useEffect } from 'react';
import { Save, AlertCircle, Crown, FolderOpen, Sparkles, Image as ImageIcon, Film, Volume2, Music } from 'lucide-react';
import { CustomVideoPlayer } from './ui/CustomVideoPlayer';
import { useMediaResolver } from '../hooks/useMediaResolver';
import { DesignMD, CompanyProfile, BrandAsset } from '../types';
import { BrandTabGeneral } from './brand/BrandTabGeneral';
import { BrandTabVisual } from './brand/BrandTabVisual';
import { BrandTabTypography } from './brand/BrandTabTypography';
import { BrandTabMedia } from './brand/BrandTabMedia';
import { BrandTabGenerated } from './brand/BrandTabGenerated';
import { BrandTabVoice } from './brand/BrandTabVoice';
import { BrandPreview } from './brand/BrandPreview';
import { Toast } from './ui/Toast';
import { UnifiedMediaItem } from './content-grid/UnifiedMediaLibrary';
interface BrandArchitectureProps {
company: CompanyProfile;
@@ -22,8 +25,8 @@ const TABS = [
{ id: 'general', label: 'Información', icon: '📋' },
{ id: 'visual', label: 'Visual y Colores', icon: '🎨' },
{ id: 'typography', label: 'Tipografía', icon: '🔤' },
{ id: 'media', label: 'Video y Audio', icon: '🎬' },
{ id: 'generated', label: 'Generados', icon: '' },
{ id: 'voice', label: 'Voz de Marca', icon: '🎙️' },
{ id: 'media', label: 'Librería', icon: '📁' },
] as const;
type TabId = typeof TABS[number]['id'];
@@ -34,7 +37,9 @@ export const BrandArchitecture: React.FC<BrandArchitectureProps> = ({ company, h
const [activeTab, setActiveTab] = useState<TabId>('general');
const [showToast, setShowToast] = useState(false);
const [validationErrors, setValidationErrors] = useState<string[]>([]);
const [selectedMediaItem, setSelectedMediaItem] = useState<BrandAsset | null>(null);
const { getMediaUrl } = useMediaResolver();
const validate = useCallback((): string[] => {
const errors: string[] = [];
if (!company?.name || company.name.trim().length < 2) {
@@ -63,8 +68,8 @@ export const BrandArchitecture: React.FC<BrandArchitectureProps> = ({ company, h
const handleOpenFolder = async () => {
if (window.electronAPI && company?.id) {
const workspacePath = await window.electronAPI.fs.getWorkspacePath();
const folderPath = `${workspacePath}/${company.id}`;
const wp = await window.electronAPI.fs.getWorkspacePath();
const folderPath = `${wp}/${company.id}`;
await window.electronAPI.fs.openFolder(folderPath);
}
};
@@ -210,26 +215,70 @@ export const BrandArchitecture: React.FC<BrandArchitectureProps> = ({ company, h
{activeTab === 'typography' && (
<BrandTabTypography designMD={designMD} handleDesignChange={handleDesignChange} />
)}
{activeTab === 'voice' && (
<BrandTabVoice company={company} handleCompanyChange={handleCompanyChange} />
)}
{activeTab === 'media' && (
<BrandTabMedia
company={company}
designMD={designMD}
handleDesignChange={handleDesignChange}
onEditAsset={onEditAsset}
onSelectAsset={setSelectedMediaItem}
selectedAssetId={selectedMediaItem?.id}
/>
)}
{activeTab === 'generated' && (
<BrandTabGenerated company={company} />
)}
</div>
</div>
{/* Preview Column */}
{activeTab === 'generated' ? (
<div className="flex-1 bg-neutral-950 flex flex-col items-center justify-center text-neutral-500">
<Sparkles className="w-12 h-12 mb-4 opacity-50" />
<p>Selecciona un archivo generado para previsualizarlo.</p>
{activeTab === 'media' ? (
<div className="flex-1 bg-neutral-950 flex flex-col items-center justify-center p-8">
{selectedMediaItem ? (
<div className="w-full h-full flex flex-col items-center justify-center">
<div className="w-full max-h-[80%] flex items-center justify-center bg-black/40 rounded-xl overflow-hidden border border-neutral-800 shadow-2xl">
{selectedMediaItem.type === 'video' ? (
<CustomVideoPlayer
src={getMediaUrl(selectedMediaItem.url || selectedMediaItem.path || '')}
autoPlay
className="w-full h-full"
/>
) : selectedMediaItem.type === 'audio' ? (
<div className="flex flex-col items-center gap-6 p-12">
<div className="w-24 h-24 rounded-full bg-rose-500/10 flex items-center justify-center">
<Music className="w-12 h-12 text-rose-500" />
</div>
<audio
src={getMediaUrl(selectedMediaItem.url || selectedMediaItem.path || '')}
controls
autoPlay
className="w-64"
/>
</div>
) : (
<img
src={getMediaUrl(selectedMediaItem.url || selectedMediaItem.path || '')}
alt={selectedMediaItem.id}
className="max-w-full max-h-full object-contain"
/>
)}
</div>
<div className="mt-6 text-center space-y-1">
<h3 className="text-lg font-medium text-white font-mono">
{selectedMediaItem.id}
</h3>
<p className="text-sm text-neutral-400">
{'date' in selectedMediaItem && selectedMediaItem.date ? new Date((selectedMediaItem as any).date).toLocaleString() : 'Asset Base'}
</p>
</div>
</div>
) : (
<div className="flex flex-col items-center text-neutral-500">
<Sparkles className="w-12 h-12 mb-4 opacity-50" />
<p>Selecciona un archivo para previsualizarlo.</p>
</div>
)}
</div>
) : (
<BrandPreview