mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 19:35:24 +02:00
feat: switch AI to Gemini and improve translation workflow
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow, COLUMNS } from '../types';
|
||||||
import { X, Sparkles, Save, Loader2 } from 'lucide-react';
|
import { X, Sparkles, Save, Loader2, Languages } from 'lucide-react';
|
||||||
import { generateDescription } from '../services/anthropic';
|
import { generateGemini } from '../services/gemini';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
interface EditPanelProps {
|
interface EditPanelProps {
|
||||||
@@ -40,31 +40,33 @@ export function EditPanel({ row, rowIndex, onSave, onClose }: EditPanelProps) {
|
|||||||
let prompt = '';
|
let prompt = '';
|
||||||
const baseContext = `Article Name: ${row[COLUMNS.ARTICLE_NAME]}\nArticle Details (EN): ${row[COLUMNS.DETAILS_EN] || 'N/A'}\nArticle Details (DE): ${row[COLUMNS.DETAILS_DE] || 'N/A'}`;
|
const baseContext = `Article Name: ${row[COLUMNS.ARTICLE_NAME]}\nArticle Details (EN): ${row[COLUMNS.DETAILS_EN] || 'N/A'}\nArticle Details (DE): ${row[COLUMNS.DETAILS_DE] || 'N/A'}`;
|
||||||
|
|
||||||
const systemPrompt = "You are a professional copywriter for CRAZE GmbH, a German toy company. Write commercial product descriptions for B2B buyers (retailers, distributors). Use clear, engaging, professional language. Never invent features not mentioned in the source material.";
|
const systemPrompt = "You are a professional copywriter and expert translator for CRAZE GmbH, a German toy company. You excel at translating product descriptions from German to English, maintaining the commercial and professional tone while ensuring all technical toy details are accurate. Use clear, engaging language.";
|
||||||
|
|
||||||
if (field === 'longDe') {
|
if (field === 'longDe') {
|
||||||
prompt = `Based on the following product details, generate a long commercial description in German. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long. Include features, benefits, and material if available.\n\n${baseContext}`;
|
prompt = `Based on the following product details, generate a long commercial description in German. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long. Include features, benefits, and material if available.\n\n${baseContext}`;
|
||||||
} else if (field === 'longEn') {
|
} else if (field === 'longEn') {
|
||||||
if (formData.longDe) {
|
if (formData.longDe) {
|
||||||
prompt = `Translate the following German product description into perfect English. Keep the same tone and length.\n\nGerman Description:\n${formData.longDe}`;
|
prompt = `Translate exactly this German product description into professional English for the toy market. Ensure a natural flow and high-quality vocabulary:\n\n${formData.longDe}`;
|
||||||
} else {
|
} else {
|
||||||
prompt = `Based on the following product details, generate a long commercial description in English. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long. Include features, benefits, and material if available.\n\n${baseContext}`;
|
prompt = `Based on the following product details, generate a long commercial description in English. It should be fluent, oriented towards B2B toy buyers, 3-5 paragraphs long.\n\n${baseContext}`;
|
||||||
}
|
}
|
||||||
} else if (field === 'shortDe') {
|
} else if (field === 'shortDe') {
|
||||||
if (formData.longDe) {
|
if (formData.longDe) {
|
||||||
prompt = `Create a short version (2-4 sentences max) of the following German product description. Include only the most relevant info. Use a direct and commercial tone.\n\nGerman Description:\n${formData.longDe}`;
|
prompt = `Create a short version (2-4 sentences max) of the following German product description:\n\n${formData.longDe}`;
|
||||||
} else {
|
} else {
|
||||||
prompt = `Based on the following product details, generate a short commercial description in German (2-4 sentences max). Include only the most relevant info. Use a direct and commercial tone.\n\n${baseContext}`;
|
prompt = `Based on the following product details, generate a short commercial description in German (2-4 sentences max).\n\n${baseContext}`;
|
||||||
}
|
}
|
||||||
} else if (field === 'shortEn') {
|
} else if (field === 'shortEn') {
|
||||||
if (formData.shortDe) {
|
if (formData.shortDe) {
|
||||||
prompt = `Translate the following short German product description into perfect English.\n\nGerman Description:\n${formData.shortDe}`;
|
prompt = `Translate exactly this short German product description into professional English for the toy market:\n\n${formData.shortDe}`;
|
||||||
|
} else if (formData.longEn) {
|
||||||
|
prompt = `Create a short version (2-4 sentences max) of the following English product description:\n\n${formData.longEn}`;
|
||||||
} else {
|
} else {
|
||||||
prompt = `Based on the following product details, generate a short commercial description in English (2-4 sentences max). Include only the most relevant info. Use a direct and commercial tone.\n\n${baseContext}`;
|
prompt = `Based on the following product details, generate a short commercial description in English (2-4 sentences max).\n\n${baseContext}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatedText = await generateDescription(prompt, systemPrompt);
|
const generatedText = await generateGemini(prompt, systemPrompt);
|
||||||
setFormData(prev => ({ ...prev, [field]: generatedText.trim() }));
|
setFormData(prev => ({ ...prev, [field]: generatedText.trim() }));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message || 'An error occurred during generation.');
|
setError(err.message || 'An error occurred during generation.');
|
||||||
@@ -86,15 +88,25 @@ export function EditPanel({ row, rowIndex, onSave, onClose }: EditPanelProps) {
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm font-medium text-slate-300">{title}</label>
|
<label className="text-sm font-medium text-slate-300">{title}</label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{((field === 'longEn' && formData.longDe) ||
|
||||||
|
(field === 'shortDe' && formData.longDe) ||
|
||||||
|
(field === 'shortEn' && (formData.shortDe || formData.longEn))) && (
|
||||||
|
<div className="flex items-center gap-1 text-[10px] text-slate-500 italic">
|
||||||
|
<Languages className="w-3 h-3" />
|
||||||
|
Can translate from existing
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleGenerate(field)}
|
onClick={() => handleGenerate(field)}
|
||||||
disabled={loadingField !== null}
|
disabled={loadingField !== null}
|
||||||
className="flex items-center gap-1.5 text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors disabled:opacity-50"
|
className="flex items-center gap-1.5 text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loadingField === field ? <Loader2 className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
|
{loadingField === field ? <Loader2 className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
|
||||||
Generate with AI
|
{((field === 'longEn' && formData.longDe) || (field === 'shortEn' && formData.shortDe)) ? 'Translate with Gemini' : 'Generate with Gemini'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData[field]}
|
value={formData[field]}
|
||||||
onChange={e => setFormData(prev => ({ ...prev, [field]: e.target.value }))}
|
onChange={e => setFormData(prev => ({ ...prev, [field]: e.target.value }))}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
export async function generateGemini(prompt: string, systemPrompt: string): Promise<string> {
|
||||||
|
const apiKey = import.meta.env.GEMINI_API_KEY || localStorage.getItem('GEMINI_API_KEY') || '';
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('Gemini API Key not found. Please set GEMINI_API_KEY in .env or GEMINI_API_KEY in localStorage.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${apiKey}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
contents: [{
|
||||||
|
parts: [{
|
||||||
|
text: `${systemPrompt}\n\nTask: ${prompt}`
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
generationConfig: {
|
||||||
|
maxOutputTokens: 1000,
|
||||||
|
temperature: 0.7
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({}));
|
||||||
|
throw new Error(errorData.error?.message || 'Failed to generate text from Gemini API');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.candidates[0].content.parts[0].text;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user