Update
This commit is contained in:
@@ -4,6 +4,8 @@ import {AIProviderFactory} from '../providers/ai-provider.factory';
|
||||
import {GenerateCommentDto} from '../dto/generate-comment.dto';
|
||||
import {buildCommentPrompt} from '../prompts/comment.templates';
|
||||
import {ProviderRouterService} from "./provider-router.service";
|
||||
import {TranslatorService} from "./translator.service";
|
||||
import {getUuid4} from "../../../shared/helper";
|
||||
|
||||
@Injectable()
|
||||
export class CommentWriterService {
|
||||
@@ -12,10 +14,27 @@ export class CommentWriterService {
|
||||
constructor(
|
||||
private factory: AIProviderFactory,
|
||||
private router: ProviderRouterService,
|
||||
private translatorService: TranslatorService
|
||||
) {
|
||||
}
|
||||
|
||||
async generateComment(dto: GenerateCommentDto) {
|
||||
// async generateComment() {}
|
||||
|
||||
async generateComment(
|
||||
dto: GenerateCommentDto,
|
||||
allowTranslateToVi = true
|
||||
): Promise<{
|
||||
commentTransVi?: string;
|
||||
commentTransModel?: string;
|
||||
comment: string;
|
||||
tokensUsed: number;
|
||||
tokenTranslatorUsed?: number;
|
||||
model: string;
|
||||
language: "en" | "vi" | "ja" | "ko" | "cn",
|
||||
input?: GenerateCommentDto,
|
||||
uid:string,
|
||||
}> {
|
||||
// const uid = getUuid4();
|
||||
const decision = this.router.route({
|
||||
language: dto.language,
|
||||
contentType: 'comment',
|
||||
@@ -29,13 +48,7 @@ export class CommentWriterService {
|
||||
const provider = this.factory.get(decision.writer);
|
||||
this.logger.log(`==> Comment routing: ${decision.reason} ==>`);
|
||||
// console.log({dto})
|
||||
const {system, user} = buildCommentPrompt({
|
||||
originalPost: dto.originalPost,
|
||||
angle: dto.angle,
|
||||
language: dto.language,
|
||||
persona: dto.persona,
|
||||
tone: dto.tone,
|
||||
});
|
||||
const {system, user} = buildCommentPrompt(dto);
|
||||
|
||||
this.logger.debug({dto, system, user})
|
||||
|
||||
@@ -54,12 +67,35 @@ export class CommentWriterService {
|
||||
|
||||
// Clean output: bỏ quotes nếu AI lỡ wrap
|
||||
const cleaned = res.content.replace(/^["""']|["""']$/g, '').trim();
|
||||
this.logger.debug({
|
||||
post: dto.originalPost,
|
||||
output: cleaned
|
||||
});
|
||||
//transla if language != vi
|
||||
let commentTransVi = '';
|
||||
let commentTransModel = '';
|
||||
let tokenTranslatorUsed = 0;
|
||||
if (allowTranslateToVi && dto.language !== 'vi') {
|
||||
const resT = await this.translatorService.translator({
|
||||
text: cleaned,
|
||||
target_lang: 'vi',
|
||||
target_model: 'google'
|
||||
})
|
||||
commentTransVi = resT.content;
|
||||
commentTransModel = resT.model;
|
||||
tokenTranslatorUsed = resT.tokensUsed;
|
||||
}
|
||||
|
||||
return {
|
||||
commentTransVi,
|
||||
commentTransModel,
|
||||
comment: cleaned,
|
||||
tokensUsed: res.tokensUsed,
|
||||
tokenTranslatorUsed,
|
||||
model: res.model,
|
||||
language: dto.language,
|
||||
input: dto,
|
||||
uid: '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@ import {ProviderName} from '../providers/ai-provider.factory';
|
||||
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||
import {ContentTone, isEdgyTone} from "../enum/tone.enum";
|
||||
import {AngleEnum} from "../enum/angle.enum";
|
||||
import {getRandomElement} from "../../../shared/helper";
|
||||
|
||||
interface ProviderPair {
|
||||
writer: ProviderName;
|
||||
reviewer: ProviderName;
|
||||
}
|
||||
export type ContentType = 'post' | 'comment';
|
||||
export type ContentType = 'post' | 'comment' | 'translation';
|
||||
interface RoutingDecision {
|
||||
writer: ProviderName;
|
||||
reviewer: ProviderName;
|
||||
@@ -55,6 +56,24 @@ export class ProviderRouterService {
|
||||
}): RoutingDecision {
|
||||
const { language, contentType, style, tone } = params;
|
||||
|
||||
if(contentType ==='translation') {
|
||||
if (language === 'cn') {
|
||||
// Default EN
|
||||
return {
|
||||
writer: 'deepseek',
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: false,
|
||||
reason: 'CN default: GPT reliable',
|
||||
};
|
||||
}
|
||||
return {
|
||||
writer: getRandomElement([ 'openai',]),
|
||||
reviewer: 'openai',
|
||||
useXEnrichment: false,
|
||||
reason: 'EN default: GPT reliable',
|
||||
};
|
||||
}
|
||||
|
||||
if (tone === ContentTone.EMPATHETIC) {
|
||||
return {
|
||||
writer: 'openai', // warmest voice, less "AI-ish"
|
||||
@@ -69,7 +88,7 @@ export class ProviderRouterService {
|
||||
if (tone && isEdgyTone(tone)) {
|
||||
if (language === 'en') {
|
||||
return {
|
||||
writer: 'grok',
|
||||
writer: getRandomElement(['openai', 'google', 'grok']),
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: false,
|
||||
reason: `Edgy tone (${tone}) EN: Grok (handles edge best)`,
|
||||
@@ -77,14 +96,14 @@ export class ProviderRouterService {
|
||||
}
|
||||
if (tone === ContentTone.SPICY) {
|
||||
return {
|
||||
writer: 'openai',
|
||||
writer: getRandomElement(['deepseek', 'google', 'openai',]),
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: false,
|
||||
reason: `Edgy tone (${tone}) EN: Grok (handles edge best)`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
writer: 'deepseek',
|
||||
writer: getRandomElement(['deepseek', 'google',]),
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: false,
|
||||
reason: `Edgy tone (${tone}) ${language}: DeepSeek (less refusal)`,
|
||||
@@ -96,9 +115,9 @@ export class ProviderRouterService {
|
||||
// Breaking news EN -> Grok (real-time + X-native)
|
||||
if (style === ContentStyle.BREAKING_NEWS) {
|
||||
return {
|
||||
writer: 'grok',
|
||||
writer: 'google',
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: true,
|
||||
useXEnrichment: false,
|
||||
reason: 'EN breaking news: Grok has real-time X context',
|
||||
};
|
||||
}
|
||||
@@ -106,7 +125,7 @@ export class ProviderRouterService {
|
||||
// Comment EN casual/witty -> Grok
|
||||
if (contentType === 'comment' && tone !== 'professional') {
|
||||
return {
|
||||
writer: 'grok',
|
||||
writer: 'google',
|
||||
reviewer: 'deepseek',
|
||||
useXEnrichment: false,
|
||||
reason: 'EN casual comment: Grok sounds most human on X',
|
||||
|
||||
@@ -14,6 +14,7 @@ import {ContentTone, isEdgyTone} from '../enum/tone.enum';
|
||||
import {calculateTokenBudget} from "../../../common/utils/token-calculator";
|
||||
import {QuoteType} from "../enum/quote-type.enum";
|
||||
import {ContentSafetyService} from "./content-safety.service";
|
||||
import {TranslatorService} from "./translator.service";
|
||||
|
||||
@Injectable()
|
||||
export class QuoteWriterService {
|
||||
@@ -25,10 +26,11 @@ export class QuoteWriterService {
|
||||
private lengthStrategy: LengthStrategyService,
|
||||
private reviewer: ReviewerService,
|
||||
private config: ConfigService,
|
||||
private safety: ContentSafetyService
|
||||
private safety: ContentSafetyService,
|
||||
private translatorService: TranslatorService
|
||||
) {}
|
||||
|
||||
async generateQuote(dto: GenerateQuoteDto, _retryCount = 0) {
|
||||
async generateQuote(dto: GenerateQuoteDto, _retryCount = 0 , allowTranslateToVi =true) {
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
if (_retryCount >= MAX_RETRIES) {
|
||||
@@ -145,6 +147,21 @@ export class QuoteWriterService {
|
||||
);
|
||||
}
|
||||
|
||||
//transla if language != vi
|
||||
let quoteTransVi = '';
|
||||
let quoteTransModel = '';
|
||||
let tokenTranslatorUsed = 0;
|
||||
if (allowTranslateToVi && dto.language !== 'vi') {
|
||||
const resT = await this.translatorService.translator({
|
||||
text: quote,
|
||||
target_lang: 'vi',
|
||||
target_model: 'google'
|
||||
})
|
||||
quoteTransVi = resT.content;
|
||||
quoteTransModel = resT.model;
|
||||
tokenTranslatorUsed = resT.tokensUsed;
|
||||
}
|
||||
|
||||
return {
|
||||
quote,
|
||||
quoteType,
|
||||
@@ -153,6 +170,9 @@ export class QuoteWriterService {
|
||||
reviewNotes,
|
||||
tokensUsed: totalTokens,
|
||||
model: modelUsed,
|
||||
quoteTransVi,
|
||||
quoteTransModel,
|
||||
tokenTranslatorUsed
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,8 @@ export class StyleDetectorService {
|
||||
}
|
||||
|
||||
detectLanguageFromTelegramAutoContent(text: string): Language {
|
||||
if (/nhật[ _]bản/i.test(text)) return "ja";
|
||||
if (/#hàn_quốc/i.test(text)) return "ko";
|
||||
// if (/nhật[ _]bản/i.test(text)) return "ja";
|
||||
// if (/#hàn_quốc/i.test(text)) return "ko";
|
||||
|
||||
// return getLanguageByJSTTime();
|
||||
//
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {AiTranslatorDto} from "../dto/ai-translator.dto";
|
||||
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||
import {LANGUAGE_NAMES} from "../prompts/templates";
|
||||
import {Injectable, Logger} from "@nestjs/common";
|
||||
import {AIProviderFactory, ProviderName} from "../providers/ai-provider.factory";
|
||||
import {ProviderRouterService} from "./provider-router.service";
|
||||
|
||||
@Injectable()
|
||||
export class TranslatorService {
|
||||
private readonly logger = new Logger(TranslatorService.name);
|
||||
|
||||
constructor(
|
||||
private factory: AIProviderFactory,
|
||||
private router: ProviderRouterService,
|
||||
) {
|
||||
}
|
||||
async translator(dto: AiTranslatorDto) {
|
||||
this.logger.log(`Translating ...`);
|
||||
const targetLanguage = LANGUAGE_NAMES[dto.target_lang] || LANGUAGE_NAMES['en'];
|
||||
const systemPrompt =
|
||||
`You are an expert X (Twitter) translator.
|
||||
Rules:
|
||||
- Translate naturally and fluently.
|
||||
- Preserve the original tone, emotion, and internet culture.
|
||||
- Keep the post concise and punchy like an actual X post.
|
||||
- Preserve slang, memes, sarcasm, and crypto terminology.
|
||||
- Do NOT over-formalize the translation.
|
||||
- Keep token names, ticker symbols, usernames, hashtags, and project names unchanged.
|
||||
- Preserve emojis, line breaks, and formatting.
|
||||
- Do not add explanations, notes, or extra commentary.
|
||||
- Avoid robotic or textbook-style translation.
|
||||
- Output ONLY the translated text.`;
|
||||
const USER_PROMPTS_HINT: Record<Language, string> = {
|
||||
'en':'Translate to English:',
|
||||
'cn':'Translate to Chinese:',
|
||||
'vi':'Translate to Vietnamese:',
|
||||
'ja':'Translate to Japanese:',
|
||||
'ko':'Translate to Korean:',
|
||||
}
|
||||
const userPrompt = [
|
||||
`[Target Language: ${targetLanguage}]
|
||||
IMPORTANT: Your previous answer violated language rules.`,
|
||||
USER_PROMPTS_HINT[dto.target_lang],
|
||||
dto.text
|
||||
].filter(Boolean).join('\n');
|
||||
|
||||
if(!dto.target_model) {
|
||||
// 🧭 Smart routing
|
||||
const decision = this.router.route({
|
||||
language: dto.target_lang,
|
||||
contentType: 'translation',
|
||||
});
|
||||
dto.target_model = decision.writer;
|
||||
}
|
||||
const provider = this.factory.get(dto.target_model);
|
||||
const draft = await provider.complete(
|
||||
[
|
||||
{role: 'system', content: systemPrompt},
|
||||
{role: 'user', content: userPrompt},
|
||||
],
|
||||
{ temperature: 0.1},
|
||||
);
|
||||
this.logger.debug(`===> ${draft.model} đã dich xong!`);
|
||||
this.logger.debug(`===> ${draft.content}`);
|
||||
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user