import { type ClientOptions, OpenAI } from "openai"; import { config, validateConfig } from "./config.js"; // Validate config on module load validateConfig(); const clientOptions: ClientOptions = { apiKey: config.openai.apiKey, baseURL: config.openai.endpoint, }; const openai = new OpenAI(clientOptions); export async function openAI_ClassifyFeed(title: string): Promise { if (!title || title.trim() === "") { throw new Error("Feed title is required for classification"); } const prompt = ` 以下のRSSフィードのタイトルを見て、適切なトピックカテゴリに分類してください。 フィードタイトル: ${title} 以下のカテゴリから1つを選択してください: - テクノロジー - ビジネス - エンターテインメント - スポーツ - 科学 - 健康 - 政治 - 環境 - 教育 - その他 分類結果を上記カテゴリのいずれか1つだけ返してください。 `; try { const response = await openai.chat.completions.create({ model: config.openai.modelName, messages: [{ role: "user", content: prompt.trim() }], temperature: 0.2, }); const category = response.choices[0]?.message?.content?.trim(); if (!category) { console.warn("OpenAI returned empty category, using default"); return "その他"; } return category; } catch (error) { console.error("Error classifying feed:", error); throw new Error( `Failed to classify feed: ${error instanceof Error ? error.message : "Unknown error"}`, ); } } export async function openAI_GeneratePodcastContent( title: string, items: Array<{ title: string; link: string; content?: string; description?: string; }>, ): Promise { if (!title || title.trim() === "") { throw new Error("Feed title is required for podcast content generation"); } if (!items || items.length === 0) { throw new Error( "At least one news item is required for podcast content generation", ); } // Validate items const validItems = items.filter((item) => item.title && item.link); if (validItems.length === 0) { throw new Error("No valid news items found (title and link required)"); } // Build detailed article information including content const articleDetails = validItems .map((item, i) => { let articleInfo = `${i + 1}. タイトル: ${item.title}\nURL: ${item.link}`; // Add content if available const content = item.content || item.description; if (content && content.trim()) { // Limit content length to avoid token limits const maxContentLength = 2000; const truncatedContent = content.length > maxContentLength ? content.substring(0, maxContentLength) + "..." : content; articleInfo += `\n内容: ${truncatedContent}`; } return articleInfo; }) .join("\n\n"); const prompt = ` You are a professional podcaster. Create a detailed podcast script based on the provided feed title/topic, following these specific requirements: **Content Requirements:** - Provide detailed summaries and analysis based on the actual content of news articles - Translate all news article titles to Japanese and introduce them at the beginning - Create substantive commentary that adds value beyond just reading headlines - Conclude with a comprehensive summary of the overall topic - The podcast name must be "自動生成ポッドキャスト" **Format Requirements:** - Length: 1000-5000 characters (Japanese) - Use natural, conversational Japanese language suitable for audio - Write in a engaging podcast style with smooth transitions between topics - There is only **one speaker** in the podcast, so use first-person perspective **Technical Requirements:** - Since this will be read by Japanese Text-to-Speech, convert ALL English words, abbreviations, and acronyms to katakana - Examples: - "AI" → "エーアイ" - "NASA" → "ナサ" - "GitHub" → "ギットハブ" - "OpenAI" → "オープンエーアイ" - Don't include any urls in the script. - Don't include any non-Japanese characters in the script. - Apply this conversion consistently throughout the script **Structure:** 1. Opening with translated article titles 2. Detailed discussion of each news item with context and analysis 3. Overall topic summary and conclusion Create a valuable, informative podcast script that utilizes the actual article content rather than just surface-level information. `; const sendContent = ` FeedTitle: ${title} Content: ${articleDetails} `; try { const response = await openai.chat.completions.create({ model: config.openai.modelName, messages: [ { role: "system", content: prompt.trim() }, { role: "user", content: sendContent.trim() }, ], temperature: 0.4, max_completion_tokens: 1700, reasoning_effort: "medium", }); const scriptText = response.choices[0]?.message?.content?.trim(); if (!scriptText) { throw new Error("OpenAI returned empty podcast content"); } return scriptText; } catch (error) { console.error("Error generating podcast content:", error); throw new Error( `Failed to generate podcast content: ${error instanceof Error ? error.message : "Unknown error"}`, ); } } export async function openAI_ClassifyEpisode( title: string, description?: string, content?: string, ): Promise { if (!title || title.trim() === "") { throw new Error("Episode title is required for classification"); } // Build the text for classification based on available data let textForClassification = `タイトル: ${title}`; if (description && description.trim()) { textForClassification += `\n説明: ${description}`; } if (content && content.trim()) { const maxContentLength = 1500; const truncatedContent = content.length > maxContentLength ? content.substring(0, maxContentLength) + "..." : content; textForClassification += `\n内容: ${truncatedContent}`; } const prompt = ` 以下のポッドキャストエピソードの情報を見て、適切なトピックカテゴリに分類してください。 ${textForClassification} 以下のカテゴリから1つを選択してください: - テクノロジー - ビジネス - エンターテインメント - スポーツ - 科学 - 健康 - 政治 - 環境 - 教育 - その他 エピソードの内容に最も適合するカテゴリを上記から1つだけ返してください。 `; try { const response = await openai.chat.completions.create({ model: config.openai.modelName, messages: [{ role: "user", content: prompt.trim() }], temperature: 0.2, }); const category = response.choices[0]?.message?.content?.trim(); if (!category) { console.warn("OpenAI returned empty episode category, using default"); return "その他"; } return category; } catch (error) { console.error("Error classifying episode:", error); throw new Error( `Failed to classify episode: ${error instanceof Error ? error.message : "Unknown error"}`, ); } }