248 lines
7.6 KiB
TypeScript
248 lines
7.6 KiB
TypeScript
/**
|
||
* 视频文案仿写优化器
|
||
* 特点:
|
||
* 1. 支持更复杂的提示词模板
|
||
* 2. 智能分批处理多个文案
|
||
* 3. 更好的错误恢复
|
||
* 4. 支持自定义的风格转换
|
||
*/
|
||
|
||
interface RewriteConfig {
|
||
style?: 'casual' | 'professional' | 'emotional' | 'humorous'; // 文案风格
|
||
length?: 'short' | 'medium' | 'long'; // 文案长度
|
||
keepHashtags?: boolean; // 是否保留话题标签
|
||
keepEmoji?: boolean; // 是否保留emoji
|
||
maxRetries?: number; // 重试次数
|
||
}
|
||
|
||
interface RewriteResult {
|
||
original: string;
|
||
rewritten: string;
|
||
confidence: number; // 0-1 的置信度
|
||
}
|
||
|
||
class VideoRewriteOptimizer {
|
||
/**
|
||
* 构建优化的仿写提示词
|
||
*/
|
||
static buildRewritePrompt(
|
||
content: string,
|
||
config: RewriteConfig = {},
|
||
customPrompt?: string
|
||
): string {
|
||
// 如果提供了自定义提示词,使用自定义的
|
||
if (customPrompt && customPrompt.trim()) {
|
||
return this.replacePromptVariables(customPrompt, content);
|
||
}
|
||
|
||
// 否则使用预设的提示词
|
||
return this.buildDefaultPrompt(content, config);
|
||
}
|
||
|
||
/**
|
||
* 替换提示词中的变量
|
||
*/
|
||
private static replacePromptVariables(template: string, content: string): string {
|
||
return template
|
||
.replace(/\{\{content\}\}/g, content)
|
||
.replace(/\{content\}/g, content)
|
||
.replace(/\{\{text\}\}/g, content)
|
||
.replace(/\{text\}/g, content);
|
||
}
|
||
|
||
/**
|
||
* 构建默认提示词
|
||
*/
|
||
private static buildDefaultPrompt(content: string, config: RewriteConfig): string {
|
||
const styleGuide = this.getStyleGuide(config.style || 'casual');
|
||
const lengthGuide = this.getLengthGuide(config.length || 'medium');
|
||
|
||
return `你是一个专业的视频内容创意编写专家,擅长创作吸引人的视频文案。
|
||
|
||
## 原始文案:
|
||
${content}
|
||
|
||
## 仿写要求:
|
||
1. **风格**: ${styleGuide}
|
||
2. **长度**: ${lengthGuide}
|
||
3. **核心保持**: 保留原文案的核心信息和主题
|
||
4. **创意提升**: 在措辞、表述角度、情感吸引力上创新
|
||
5. **格式**:
|
||
${config.keepHashtags !== false ? '- 保留原有话题标签(#开头的内容)' : '- 不使用话题标签'}
|
||
${config.keepEmoji !== false ? '- 可以适当添加表情符号增加生动性' : '- 不使用表情符号'}
|
||
6. **禁止事项**:
|
||
- 不要改变事实信息
|
||
- 不要添加虚假承诺
|
||
- 不要包含敏感词汇
|
||
|
||
请直接输出仿写后的文案,不需要任何额外说明或标记。`;
|
||
}
|
||
|
||
/**
|
||
* 获取风格指南
|
||
*/
|
||
private static getStyleGuide(style: string): string {
|
||
const guides: Record<string, string> = {
|
||
casual: '轻松随意、接地气、易产生共鸣,像和朋友聊天一样',
|
||
professional: '正式专业、有信服力、适合商务或教育内容',
|
||
emotional: '充满情感、富有感染力、能打动人心',
|
||
humorous: '幽默诙谐、容易引起笑声和转发、保持积极态度'
|
||
};
|
||
return guides[style] || guides.casual;
|
||
}
|
||
|
||
/**
|
||
* 获取长度指南
|
||
*/
|
||
private static getLengthGuide(length: string): string {
|
||
const guides: Record<string, string> = {
|
||
short: '简洁有力,100字以内,快速传达核心信息',
|
||
medium: '适中篇幅,100-300字,既能完整表达又不显冗长',
|
||
long: '详细深入,300-500字,充分展开论点和细节'
|
||
};
|
||
return guides[length] || guides.medium;
|
||
}
|
||
|
||
/**
|
||
* 批量仿写文案(支持多个内容同时处理)
|
||
*/
|
||
static buildBatchRewritePrompt(
|
||
contents: string[],
|
||
config: RewriteConfig = {},
|
||
customPrompt?: string
|
||
): string {
|
||
if (customPrompt && customPrompt.trim()) {
|
||
// 自定义提示词需要特殊处理,只能一个接一个
|
||
return this.replacePromptVariables(customPrompt, contents[0]);
|
||
}
|
||
|
||
const styleGuide = this.getStyleGuide(config.style || 'casual');
|
||
const lengthGuide = this.getLengthGuide(config.length || 'medium');
|
||
|
||
const itemsText = contents
|
||
.map((content, i) => `${i + 1}. ${content}`)
|
||
.join('\n');
|
||
|
||
return `你是一个专业的视频内容创意编写专家,擅长创作吸引人的视频文案。
|
||
|
||
## 原始文案列表:
|
||
${itemsText}
|
||
|
||
## 仿写要求:
|
||
1. **风格**: ${styleGuide}
|
||
2. **长度**: ${lengthGuide}
|
||
3. **核心保持**: 保留每篇原文案的核心信息和主题
|
||
4. **创意提升**: 在措辞、表述角度、情感吸引力上创新
|
||
5. **格式**:
|
||
${config.keepHashtags !== false ? '- 保留原有话题标签(#开头的内容)' : '- 不使用话题标签'}
|
||
${config.keepEmoji !== false ? '- 可以适当添加表情符号增加生动性' : '- 不使用表情符号'}
|
||
6. **禁止事项**:
|
||
- 不要改变事实信息
|
||
- 不要添加虚假承诺
|
||
- 不要包含敏感词汇
|
||
|
||
## 输出格式:
|
||
请按顺序输出仿写后的文案,每个文案一行,不需要编号或任何额外标记。`;
|
||
}
|
||
|
||
/**
|
||
* 解析仿写后的文案(处理可能的编号、特殊字符等)
|
||
*/
|
||
static parseRewriteResponse(response: string, count: number): string[] {
|
||
const lines = response
|
||
.trim()
|
||
.split('\n')
|
||
.map(line => line.trim())
|
||
.filter(line => line.length > 0);
|
||
|
||
// 清理可能的编号前缀
|
||
const cleaned = lines.map(line => {
|
||
// 去掉开头的编号(1. 2. 1) 2) 等)
|
||
return line
|
||
.replace(/^[\d\.\)]+\s*/, '')
|
||
.replace(/^[-•·\*]\s*/, '')
|
||
.trim();
|
||
});
|
||
|
||
// 如果数量不匹配,尝试智能截断或填充
|
||
if (cleaned.length > count) {
|
||
return cleaned.slice(0, count);
|
||
}
|
||
|
||
if (cleaned.length < count) {
|
||
// 填充原始文案(如果API返回不足)
|
||
while (cleaned.length < count) {
|
||
cleaned.push('精彩内容分享');
|
||
}
|
||
}
|
||
|
||
return cleaned;
|
||
}
|
||
|
||
/**
|
||
* 智能评估仿写质量
|
||
*/
|
||
static assessQuality(original: string, rewritten: string): number {
|
||
let score = 0.5; // 基础分数
|
||
|
||
// 加分项
|
||
if (rewritten.length > 0) score += 0.1; // 有内容
|
||
if (rewritten.length > original.length * 0.5 && rewritten.length < original.length * 1.5) {
|
||
score += 0.1; // 长度合理(±50%)
|
||
}
|
||
if (rewritten.includes(',') || rewritten.includes('。')) {
|
||
score += 0.05; // 有标点符号
|
||
}
|
||
|
||
// 检查是否过于相似(直接复制)
|
||
const similarity = this.calculateSimilarity(original, rewritten);
|
||
if (similarity > 0.9) {
|
||
score = Math.max(0.2, score - 0.3); // 过于相似减分
|
||
}
|
||
|
||
return Math.min(1.0, Math.max(0.0, score));
|
||
}
|
||
|
||
/**
|
||
* 计算两个文本的相似度(0-1)
|
||
*/
|
||
private static calculateSimilarity(text1: string, text2: string): number {
|
||
if (!text1 || !text2) return 0;
|
||
|
||
// 简单的字符重叠率计算
|
||
const chars1 = new Set(text1);
|
||
const chars2 = new Set(text2);
|
||
|
||
const intersection = Array.from(chars1).filter(char => chars2.has(char)).length;
|
||
const union = chars1.size + chars2.size - intersection;
|
||
|
||
return intersection / (union || 1);
|
||
}
|
||
|
||
/**
|
||
* 优化提示词中的文案内容部分
|
||
* 用于处理很长的原始文案
|
||
*/
|
||
static truncateContentForPrompt(content: string, maxLength: number = 500): string {
|
||
if (content.length <= maxLength) {
|
||
return content;
|
||
}
|
||
|
||
// 优先保留开头和结尾的关键信息
|
||
const startLength = Math.ceil(maxLength * 0.6);
|
||
const endLength = Math.floor(maxLength * 0.4);
|
||
|
||
const start = content.substring(0, startLength);
|
||
const end = content.substring(content.length - endLength);
|
||
|
||
// 找到最近的句号/标点符号作为截断点
|
||
const startEnd = start.lastIndexOf('。') >= 0
|
||
? start.lastIndexOf('。') + 1
|
||
: startLength;
|
||
|
||
return start.substring(0, startEnd) + ' [...] ' + end;
|
||
}
|
||
}
|
||
|
||
export { VideoRewriteOptimizer, RewriteConfig, RewriteResult };
|