feat(播客生成): 添加短格式播客脚本生成功能并改进视频标签处理

在helpers.js中添加对视频标签的处理,保留src属性
新增getSystemPromptShortPodcastFormatting函数用于生成短格式播客脚本
修改handleGenAIPodcastScript以支持同时生成完整版和精简版播客脚本
在生成页面添加按钮状态反馈防止重复提交
This commit is contained in:
justlovemaki
2025-06-17 22:15:39 +08:00
parent b02a786952
commit 53dc5124d1
4 changed files with 64 additions and 24 deletions

View File

@@ -6,7 +6,7 @@ import { generateGenAiPageHtml } from '../htmlGenerators.js';
import { dataSources } from '../dataFetchers.js'; // Import dataSources
import { getSystemPromptSummarizationStepOne } from '../prompt/summarizationPromptStepOne.js';
import { getSystemPromptSummarizationStepTwo } from '../prompt/summarizationPromptStepTwo.js';
import { getSystemPromptPodcastFormatting } from '../prompt/podcastFormattingPrompt.js';
import { getSystemPromptPodcastFormatting, getSystemPromptShortPodcastFormatting } from '../prompt/podcastFormattingPrompt.js';
import { getSystemPromptDailyAnalysis } from '../prompt/dailyAnalysisPrompt.js'; // Import new prompt
import { insertFoot } from '../foot.js';
@@ -17,8 +17,8 @@ export async function handleGenAIPodcastScript(request, env) {
let outputOfCall1 = null; // This will be the summarized content from Call 1
let userPromptPodcastFormattingData = null;
let fullPromptForCall2_System = null;
let fullPromptForCall2_User = null;
let fullPromptForCall3_System = null;
let fullPromptForCall3_User = null;
let finalAiResponse = null;
try {
@@ -32,40 +32,64 @@ export async function handleGenAIPodcastScript(request, env) {
return new Response(errorHtml, { status: 400, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
userPromptPodcastFormattingData = outputOfCall1;
fullPromptForCall2_System = getSystemPromptPodcastFormatting(env);
fullPromptForCall2_User = userPromptPodcastFormattingData;
console.log("Call 2 to Chat (Podcast Formatting): User prompt length:", userPromptPodcastFormattingData.length);
fullPromptForCall3_System = getSystemPromptPodcastFormatting(env);
userPromptPodcastFormattingData = outputOfCall1;
fullPromptForCall3_User = userPromptPodcastFormattingData;
console.log("Call 3 to Chat (Podcast Formatting): User prompt length:", userPromptPodcastFormattingData.length);
try {
let podcastChunks = [];
for await (const chunk of callChatAPIStream(env, userPromptPodcastFormattingData, fullPromptForCall2_System)) {
for await (const chunk of callChatAPIStream(env, userPromptPodcastFormattingData, fullPromptForCall3_System)) {
podcastChunks.push(chunk);
}
finalAiResponse = podcastChunks.join('');
if (!finalAiResponse || finalAiResponse.trim() === "") throw new Error("Chat podcast formatting call returned empty content.");
finalAiResponse = removeMarkdownCodeBlock(finalAiResponse); // Clean the output
console.log("Call 2 (Podcast Formatting) successful. Final output length:", finalAiResponse.length);
console.log("Call 3 (Podcast Formatting) successful. Final output length:", finalAiResponse.length);
} catch (error) {
console.error("Error in Chat API Call 2 (Podcast Formatting):", error);
const errorHtml = generateGenAiPageHtml(env, '生成AI播客脚本出错(播客文案)', `<p><strong>Failed during podcast formatting:</strong> ${escapeHtml(error.message)}</p>${error.stack ? `<pre>${escapeHtml(error.stack)}</pre>` : ''}`, dateStr, true, selectedItemsParams, null, null, fullPromptForCall2_System, fullPromptForCall2_User);
console.error("Error in Chat API Call 3 (Podcast Formatting):", error);
const errorHtml = generateGenAiPageHtml(env, '生成AI播客脚本出错(播客文案)', `<p><strong>Failed during podcast formatting:</strong> ${escapeHtml(error.message)}</p>${error.stack ? `<pre>${escapeHtml(error.stack)}</pre>` : ''}`, dateStr, true, selectedItemsParams, null, null, fullPromptForCall3_System, fullPromptForCall3_User);
return new Response(errorHtml, { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
let finalAiResponseOut = `## Full: Podcast Formatting ` + `\n\n` + finalAiResponse;
let promptsMarkdownContent = `# Prompts for ${dateStr}\n\n`;
promptsMarkdownContent += `## Call 2: Podcast Formatting\n\n`;
if (fullPromptForCall2_System) promptsMarkdownContent += `### System Instruction\n\`\`\`\n${fullPromptForCall2_System}\n\`\`\`\n\n`;
if (fullPromptForCall2_User) promptsMarkdownContent += `### User Input (Output of Call 1)\n\`\`\`\n${fullPromptForCall2_User}\n\`\`\`\n\n`;
promptsMarkdownContent += `## Call 3: Podcast Formatting\n\n`;
if (fullPromptForCall3_System) promptsMarkdownContent += `### System One Instruction\n\`\`\`\n${fullPromptForCall3_System}\n\`\`\`\n\n`;
let podcastScriptMarkdownContent = `# ${env.PODCAST_TITLE} ${formatDateToChinese(dateStr)}\n\n${removeMarkdownCodeBlock(finalAiResponse)}`;
let fullPromptForCall4_System = getSystemPromptShortPodcastFormatting(env);
console.log("Call 4 to Chat (Podcast Formatting): User prompt length:", userPromptPodcastFormattingData.length);
try {
let podcastChunks = [];
for await (const chunk of callChatAPIStream(env, userPromptPodcastFormattingData, fullPromptForCall4_System)) {
podcastChunks.push(chunk);
}
finalAiResponse = podcastChunks.join('');
if (!finalAiResponse || finalAiResponse.trim() === "") throw new Error("Chat podcast formatting call returned empty content.");
finalAiResponse = removeMarkdownCodeBlock(finalAiResponse); // Clean the output
console.log("Call 4 (Podcast Formatting) successful. Final output length:", finalAiResponse.length);
} catch (error) {
console.error("Error in Chat API Call 4 (Podcast Formatting):", error);
const errorHtml = generateGenAiPageHtml(env, '生成AI播客脚本出错(播客文案)', `<p><strong>Failed during podcast formatting:</strong> ${escapeHtml(error.message)}</p>${error.stack ? `<pre>${escapeHtml(error.stack)}</pre>` : ''}`, dateStr, true, selectedItemsParams, null, null, fullPromptForCall3_System, fullPromptForCall3_User);
return new Response(errorHtml, { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
finalAiResponseOut += `\n\n` + `## Short: Podcast Formatting ` + `\n\n` + finalAiResponse;
let fullPromptForCallSystem = fullPromptForCall3_System + `\n\n` + fullPromptForCall4_System;
promptsMarkdownContent += `## Call 4: Podcast Formatting\n\n`;
if (fullPromptForCall4_System) promptsMarkdownContent += `### System Two Instruction\n\`\`\`\n${fullPromptForCall4_System}\n\`\`\`\n\n`;
if (fullPromptForCall3_User) promptsMarkdownContent += `### User Input (Output of Call 1)\n\`\`\`\n${fullPromptForCall3_User}\n\`\`\`\n\n`;
let podcastScriptMarkdownContent = `# ${env.PODCAST_TITLE} ${formatDateToChinese(dateStr)}\n\n${removeMarkdownCodeBlock(finalAiResponseOut)}`;
const successHtml = generateGenAiPageHtml(
env,
'AI播客脚本',
escapeHtml(finalAiResponse),
escapeHtml(finalAiResponseOut),
dateStr, false, selectedItemsParams,
null, null, // No Call 1 prompts for this page
fullPromptForCall2_System, fullPromptForCall2_User,
fullPromptForCallSystem, fullPromptForCall3_User,
convertEnglishQuotesToChinese(removeMarkdownCodeBlock(promptsMarkdownContent)),
outputOfCall1, // No daily summary for this page
convertEnglishQuotesToChinese(podcastScriptMarkdownContent)
@@ -76,7 +100,7 @@ export async function handleGenAIPodcastScript(request, env) {
console.error("Error in /genAIPodcastScript (outer try-catch):", error);
const pageDateForError = dateStr || getISODate();
const itemsForActionOnError = Array.isArray(selectedItemsParams) ? selectedItemsParams : [];
const errorHtml = generateGenAiPageHtml(env, '生成AI播客脚本出错', `<p><strong>Unexpected error:</strong> ${escapeHtml(error.message)}</p>${error.stack ? `<pre>${escapeHtml(error.stack)}</pre>` : ''}`, pageDateForError, true, itemsForActionOnError, null, null, fullPromptForCall2_System, fullPromptForCall2_User);
const errorHtml = generateGenAiPageHtml(env, '生成AI播客脚本出错', `<p><strong>Unexpected error:</strong> ${escapeHtml(error.message)}</p>${error.stack ? `<pre>${escapeHtml(error.stack)}</pre>` : ''}`, pageDateForError, true, itemsForActionOnError, null, null, fullPromptForCall3_System, fullPromptForCall3_User);
return new Response(errorHtml, { status: 500, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
}
}

View File

@@ -105,7 +105,10 @@ export function stripHtml(html) {
});
processedHtml = processedHtml.replace(/<img[^>]*src="([^"]*)"[^>]*>/gi, '[图片: $1]');
// 移除所有其他 HTML 標籤,並正規化空白
// 处理 video 标签,保留其 src 属性
processedHtml = processedHtml.replace(/<video[^>]*src="([^"]*)"[^>]*>.*?<\/video>/gi, '[视频: $1]');
// 移除所有其他 HTML 标签,并规范化空白
return processedHtml.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}

View File

@@ -242,7 +242,10 @@ export function generateContentSelectionPageHtml(env, dateStr, allData, dataCate
event.preventDefault(); // Prevent form submission
return false;
}
const button = event.currentTarget; // 获取触发事件的按钮
if (confirm('确定要从选中内容生成 AI 日报吗?此操作将调用 AI 模型生成内容。')) {
button.innerText = '生成中...'; // 更改按钮文案
//button.disabled = true; // 禁用按钮,防止重复提交
return true; // Allow form submission
} else {
event.preventDefault(); // Prevent form submission
@@ -299,8 +302,9 @@ export function generateGenAiPageHtml(env, title, bodyContent, pageDate, isError
${selectedItemsForAction.map(item => `<input type="hidden" name="selectedItems" value="${escapeHtml(item)}">`).join('')}
<input type="hidden" name="summarizedContent" value="${escapeHtml(convertEnglishQuotesToChinese(dailyMd))}">
<button type="submit" class="button-link regenerate-button">${isErrorPage ? '重试生成' : '重新生成'}</button>
</form>`;
}
</form>
`;
}
let githubSaveFormHtml = '';
let generatePodcastButtonHtml = '';

View File

@@ -1,4 +1,13 @@
// Add new data sources
export function getSystemPromptShortPodcastFormatting(env) {
return `
你是一位播客主持人,你需要根据提供的内容,将内容改写为播客的文案。内容以中文撰写,内容中不能出现时间。
你的任务是根据收到的内容改编成一个紧凑,简洁的单人播客脚本。
将原始副本转化为自然、口语化的表达,就像与听众聊天一样,每部分内容都能用一句话表述清楚。
不要有解释性语句,不要有过渡性语言,直接播报新闻,只用在表达上稍微美化。
开场白结束语:固定的开场白:“${env.PODCAST_BEGIN}”,并以固定的结束语结束:“${env.PODCAST_END}”。
`;
}
export function getSystemPromptPodcastFormatting(env) {
return `
你是一位经验丰富的播客脚本撰写人和编辑。你的任务是根据收到的内容改编成一个引人入胜的单人播客脚本。
@@ -20,4 +29,4 @@ export function getSystemPromptPodcastFormatting(env) {
结尾处的关键词列表。
不要包含任何其他解释性文字。
`;
}
}