@heripo/research-radar 1.2.6 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var google = require('@ai-sdk/google');
3
4
  var openai = require('@ai-sdk/openai');
4
5
  var core = require('@llm-newsletter-kit/core');
5
6
  var cheerio = require('cheerio');
@@ -799,12 +800,11 @@ const parseNrichNoticeList = (html) => {
799
800
  const $ = cheerio__namespace.load(html);
800
801
  const posts = [];
801
802
  const baseUrl = 'https://www.nrich.go.kr';
802
- $('table.table-list tbody tr').each((index, element) => {
803
- const columns = $(element).find('td');
804
- if (columns.length === 0) {
803
+ $('ul.list-body li').each((index, element) => {
804
+ if ($(element).hasClass('bg-notice')) {
805
805
  return;
806
806
  }
807
- const titleElement = columns.eq(1).find('a');
807
+ const titleElement = $(element).find('.col2 a.cont-link');
808
808
  const relativeHref = titleElement.attr('href');
809
809
  if (!relativeHref) {
810
810
  return;
@@ -812,33 +812,38 @@ const parseNrichNoticeList = (html) => {
812
812
  const fullUrl = new URL(`/kor/${relativeHref}`, baseUrl);
813
813
  const detailUrl = fullUrl.href;
814
814
  const uniqId = fullUrl.searchParams.get('bbs_idx') ?? undefined;
815
- const title = titleElement.attr('title')?.trim() ?? titleElement.text()?.trim() ?? '';
816
- const date = getDate(columns.eq(3).text().trim());
817
- posts.push({
818
- uniqId,
819
- title,
820
- date,
821
- detailUrl: cleanUrl(detailUrl),
822
- dateType: core.DateType.REGISTERED,
823
- });
815
+ const title = titleElement.text()?.trim() ?? '';
816
+ const date = getDate($(element).find('.col5 .cont-txt').text().trim());
817
+ if (posts.length < 10) {
818
+ posts.push({
819
+ uniqId,
820
+ title,
821
+ date,
822
+ detailUrl: cleanUrl(detailUrl),
823
+ dateType: core.DateType.REGISTERED,
824
+ });
825
+ }
824
826
  });
825
827
  return posts;
826
828
  };
827
829
  const parseNrichMajorEventList = (html) => {
828
830
  const $ = cheerio__namespace.load(html);
829
831
  const posts = [];
830
- $('ul.event-list li a').each((index, element) => {
831
- const uniqId = getUniqIdFromNrichMajorEvent($(element));
832
+ $('ul.list-body li').each((index, element) => {
833
+ const linkElement = $(element).find('a[onclick]').first();
834
+ const uniqId = getUniqIdFromNrichMajorEvent(linkElement);
832
835
  const detailUrl = `https://www.nrich.go.kr/kor/majorView.do?menuIdx=286&bbs_idx=${uniqId}`;
833
- const title = $(element)
834
- .find('strong')
835
- .clone()
836
- .children('span')
837
- .remove()
838
- .end()
839
- .text()
840
- .trim();
841
- const dateSplit = getDate($(element).find('span.date').text().replaceAll('행사기간 : ', '').trim()).split(' ~ ');
836
+ const title = $(element).find('.info-tit strong.tit').text().trim();
837
+ let dateText = '';
838
+ $(element)
839
+ .find('.info-detail .detail-box')
840
+ .each((i, box) => {
841
+ const boxTitle = $(box).find('span.tit').text().trim();
842
+ if (boxTitle === '행사기간') {
843
+ dateText = $(box).find('span.cont').text().trim();
844
+ }
845
+ });
846
+ const dateSplit = getDate(dateText).split(' ~ ');
842
847
  const startDate = dateSplit[0];
843
848
  const endDate = dateSplit[1];
844
849
  const hasEndDate = startDate !== endDate;
@@ -858,21 +863,17 @@ const parseNrichJournalList = (html) => {
858
863
  const $ = cheerio__namespace.load(html);
859
864
  const posts = [];
860
865
  const baseUrl = 'https://www.nrich.go.kr';
861
- $('table.table-list tbody tr').each((index, element) => {
862
- const columns = $(element).find('td');
863
- if (columns.length === 0) {
864
- return;
865
- }
866
- const titleElement = columns.eq(1).find('a');
866
+ $('ul.list-body li').each((index, element) => {
867
+ const titleElement = $(element).find('.col2 a.cont-link');
867
868
  const relativeHref = titleElement.attr('href');
868
869
  if (!relativeHref) {
869
870
  return;
870
871
  }
871
872
  const fullUrl = new URL(`${relativeHref}`, baseUrl);
872
873
  const detailUrl = fullUrl.href;
873
- const uniqId = fullUrl.searchParams.get('bbs_idx') ?? undefined;
874
- const title = titleElement.attr('title')?.trim() ?? titleElement.text()?.trim() ?? '';
875
- const date = getDate(columns.eq(2).text().trim());
874
+ const uniqId = fullUrl.searchParams.get('idx') ?? undefined;
875
+ const title = titleElement.text()?.trim() ?? '';
876
+ const date = getDate($(element).find('.col3 .cont-txt').text().trim());
876
877
  posts.push({
877
878
  uniqId,
878
879
  title,
@@ -914,18 +915,16 @@ const parseNrichPortalList = (html) => {
914
915
  };
915
916
  const parseNrichNoticeDetail = (html) => {
916
917
  const $ = cheerio__namespace.load(html);
917
- const trList = $('table.table-view tbody tr');
918
- const content = trList.eq(3).find('td');
918
+ const content = $('.view-content .info-txt');
919
919
  return {
920
920
  detailContent: new TurndownService().turndown(content.html() ?? ''),
921
- hasAttachedFile: trList.length > 4,
921
+ hasAttachedFile: $('.board-file').length > 0,
922
922
  hasAttachedImage: content.find('img').length > 0,
923
923
  };
924
924
  };
925
925
  const parseNrichMajorEventDetail = (html) => {
926
926
  const $ = cheerio__namespace.load(html);
927
- const trList = $('table.table-view tbody tr');
928
- const content = trList.eq(4).find('td');
927
+ const content = $('.view-content');
929
928
  return {
930
929
  detailContent: new TurndownService().turndown(content.html() ?? ''),
931
930
  hasAttachedFile: false,
@@ -936,16 +935,12 @@ const parseNrichJournalDetail = (html) => {
936
935
  const $ = cheerio__namespace.load(html);
937
936
  $('script, style').remove();
938
937
  const articles = [];
939
- // 테이블의행을 순회하면서 논문 정보 추출
940
- $('table.table-list tbody tr').each((index, element) => {
941
- const columns = $(element).find('td');
942
- if (columns.length === 0) {
943
- return;
944
- }
945
- const number = columns.eq(0).text().trim();
946
- const titleElement = columns.eq(1).find('a');
938
+ // 리스트의항목을 순회하면서 논문 정보 추출
939
+ $('ul.list-body li').each((index, element) => {
940
+ const number = $(element).find('.col1 .cont-txt').text().trim();
941
+ const titleElement = $(element).find('.col2 a.cont-link');
947
942
  const title = titleElement.text().trim();
948
- const author = columns.eq(2).text().trim();
943
+ const author = $(element).find('.col3 .cont-txt').text().trim();
949
944
  if (title && author) {
950
945
  articles.push(`${number}. **${title}**\n 저자: ${author}`);
951
946
  }
@@ -2219,21 +2214,21 @@ const createNewsletterHtmlTemplate = (targets) => `<!DOCTYPE html>
2219
2214
 
2220
2215
  /**
2221
2216
  * Content generation provider implementation
2222
- * - LLM-based newsletter content generation
2217
+ * - LLM-based newsletter content generation (Google Generative AI)
2223
2218
  * - HTML template provisioning
2224
2219
  * - Newsletter persistence
2225
2220
  */
2226
2221
  class ContentGenerateProvider {
2227
- openai;
2222
+ google;
2228
2223
  articleRepository;
2229
2224
  newsletterRepository;
2230
2225
  _issueOrder = null;
2231
2226
  model;
2232
- constructor(openai, articleRepository, newsletterRepository) {
2233
- this.openai = openai;
2227
+ constructor(google, articleRepository, newsletterRepository) {
2228
+ this.google = google;
2234
2229
  this.articleRepository = articleRepository;
2235
2230
  this.newsletterRepository = newsletterRepository;
2236
- this.model = this.openai('gpt-5.1');
2231
+ this.model = this.google('gemini-3-pro-preview');
2237
2232
  }
2238
2233
  /** LLM temperature setting for content generation */
2239
2234
  temperature = llmConfig.generation.temperature;
@@ -2400,6 +2395,7 @@ class TaskService {
2400
2395
  * ```typescript
2401
2396
  * const generator = createNewsletterGenerator({
2402
2397
  * openAIApiKey: process.env.OPENAI_API_KEY,
2398
+ * googleGenerativeAIApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
2403
2399
  * taskRepository: new PrismaTaskRepository(prisma),
2404
2400
  * articleRepository: new PrismaArticleRepository(prisma),
2405
2401
  * tagRepository: new PrismaTagRepository(prisma),
@@ -2419,11 +2415,14 @@ function createNewsletterGenerator(dependencies) {
2419
2415
  const openai$1 = openai.createOpenAI({
2420
2416
  apiKey: dependencies.openAIApiKey,
2421
2417
  });
2418
+ const google$1 = google.createGoogleGenerativeAI({
2419
+ apiKey: dependencies.googleGenerativeAIApiKey,
2420
+ });
2422
2421
  const dateService = new DateService();
2423
2422
  const taskService = new TaskService(dependencies.taskRepository);
2424
2423
  const crawlingProvider = new CrawlingProvider(dependencies.articleRepository);
2425
2424
  const analysisProvider = new AnalysisProvider(openai$1, dependencies.articleRepository, dependencies.tagRepository);
2426
- const contentGenerateProvider = new ContentGenerateProvider(openai$1, dependencies.articleRepository, dependencies.newsletterRepository);
2425
+ const contentGenerateProvider = new ContentGenerateProvider(google$1, dependencies.articleRepository, dependencies.newsletterRepository);
2427
2426
  return new core.GenerateNewsletter({
2428
2427
  contentOptions,
2429
2428
  dateService,
@@ -2453,6 +2452,7 @@ function createNewsletterGenerator(dependencies) {
2453
2452
  * ```typescript
2454
2453
  * const newsletterId = await generateNewsletter({
2455
2454
  * openAIApiKey: process.env.OPENAI_API_KEY,
2455
+ * googleGenerativeAIApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
2456
2456
  * taskRepository: new PrismaTaskRepository(prisma),
2457
2457
  * articleRepository: new PrismaArticleRepository(prisma),
2458
2458
  * tagRepository: new PrismaTagRepository(prisma),
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { UrlString, ParsedTarget, CrawlingTargetGroup, CrawlingTarget, UnscoredArticle, ArticleForUpdateByAnalysis, ArticleForGenerateContent, Newsletter, AppLogger, EmailService, EmailMessage, DateService as DateService$1, IsoDateString, TaskService as TaskService$1, AnalysisProvider as AnalysisProvider$1, ContentGenerateProvider as ContentGenerateProvider$1, HtmlTemplate, CrawlingProvider as CrawlingProvider$1 } from '@llm-newsletter-kit/core';
2
2
  import { OpenAIProvider } from '@ai-sdk/openai';
3
+ import { GoogleGenerativeAIProvider } from '@ai-sdk/google';
3
4
 
4
5
  /**
5
6
  * Repository interface for task management
@@ -88,11 +89,9 @@ interface NewsletterRepository {
88
89
  }
89
90
 
90
91
  /**
91
- * This implementation is currently hardcoded to use OpenAI.
92
- * To use a different LLM provider, you need to modify:
93
- * - This file: Change `createOpenAI` import and initialization
94
- * - src/providers/analysis.provider.ts: Change OpenAIProvider type and model names
95
- * - src/providers/content-generate.provider.ts: Change OpenAIProvider type and model name
92
+ * Uses two LLM providers:
93
+ * - OpenAI (gpt-5-mini, gpt-5.1): Article analysis (tag classification, image analysis, importance scoring)
94
+ * - Google Generative AI (gemini-3-pro-preview): Newsletter content generation
96
95
  *
97
96
  * For details on switching providers, see README.md section:
98
97
  * "⚠️ Fork하여 나만의 뉴스레터 만들기 > 4. LLM 프로바이더 변경"
@@ -113,8 +112,10 @@ interface PreviewNewsletterOptions {
113
112
  * Newsletter generator dependencies interface
114
113
  */
115
114
  interface NewsletterGeneratorDependencies {
116
- /** OpenAI API key */
115
+ /** OpenAI API key (used for article analysis: tag classification, image analysis, importance scoring) */
117
116
  openAIApiKey: string;
117
+ /** Google Generative AI API key (used for newsletter content generation) */
118
+ googleGenerativeAIApiKey: string;
118
119
  /** Task management repository */
119
120
  taskRepository: TaskRepository;
120
121
  /** Article management repository */
@@ -138,6 +139,7 @@ interface NewsletterGeneratorDependencies {
138
139
  * ```typescript
139
140
  * const newsletterId = await generateNewsletter({
140
141
  * openAIApiKey: process.env.OPENAI_API_KEY,
142
+ * googleGenerativeAIApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
141
143
  * taskRepository: new PrismaTaskRepository(prisma),
142
144
  * articleRepository: new PrismaArticleRepository(prisma),
143
145
  * tagRepository: new PrismaTagRepository(prisma),
@@ -231,17 +233,17 @@ declare class AnalysisProvider implements AnalysisProvider$1 {
231
233
 
232
234
  /**
233
235
  * Content generation provider implementation
234
- * - LLM-based newsletter content generation
236
+ * - LLM-based newsletter content generation (Google Generative AI)
235
237
  * - HTML template provisioning
236
238
  * - Newsletter persistence
237
239
  */
238
240
  declare class ContentGenerateProvider implements ContentGenerateProvider$1 {
239
- private readonly openai;
241
+ private readonly google;
240
242
  private readonly articleRepository;
241
243
  private readonly newsletterRepository;
242
244
  private _issueOrder;
243
- model: ReturnType<OpenAIProvider>;
244
- constructor(openai: OpenAIProvider, articleRepository: ArticleRepository, newsletterRepository: NewsletterRepository);
245
+ model: ReturnType<GoogleGenerativeAIProvider>;
246
+ constructor(google: GoogleGenerativeAIProvider, articleRepository: ArticleRepository, newsletterRepository: NewsletterRepository);
245
247
  /** LLM temperature setting for content generation */
246
248
  temperature: number;
247
249
  /** Newsletter brand name */
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
1
2
  import { createOpenAI } from '@ai-sdk/openai';
2
3
  import { DateType, GenerateNewsletter } from '@llm-newsletter-kit/core';
3
4
  import * as cheerio from 'cheerio';
@@ -778,12 +779,11 @@ const parseNrichNoticeList = (html) => {
778
779
  const $ = cheerio.load(html);
779
780
  const posts = [];
780
781
  const baseUrl = 'https://www.nrich.go.kr';
781
- $('table.table-list tbody tr').each((index, element) => {
782
- const columns = $(element).find('td');
783
- if (columns.length === 0) {
782
+ $('ul.list-body li').each((index, element) => {
783
+ if ($(element).hasClass('bg-notice')) {
784
784
  return;
785
785
  }
786
- const titleElement = columns.eq(1).find('a');
786
+ const titleElement = $(element).find('.col2 a.cont-link');
787
787
  const relativeHref = titleElement.attr('href');
788
788
  if (!relativeHref) {
789
789
  return;
@@ -791,33 +791,38 @@ const parseNrichNoticeList = (html) => {
791
791
  const fullUrl = new URL(`/kor/${relativeHref}`, baseUrl);
792
792
  const detailUrl = fullUrl.href;
793
793
  const uniqId = fullUrl.searchParams.get('bbs_idx') ?? undefined;
794
- const title = titleElement.attr('title')?.trim() ?? titleElement.text()?.trim() ?? '';
795
- const date = getDate(columns.eq(3).text().trim());
796
- posts.push({
797
- uniqId,
798
- title,
799
- date,
800
- detailUrl: cleanUrl(detailUrl),
801
- dateType: DateType.REGISTERED,
802
- });
794
+ const title = titleElement.text()?.trim() ?? '';
795
+ const date = getDate($(element).find('.col5 .cont-txt').text().trim());
796
+ if (posts.length < 10) {
797
+ posts.push({
798
+ uniqId,
799
+ title,
800
+ date,
801
+ detailUrl: cleanUrl(detailUrl),
802
+ dateType: DateType.REGISTERED,
803
+ });
804
+ }
803
805
  });
804
806
  return posts;
805
807
  };
806
808
  const parseNrichMajorEventList = (html) => {
807
809
  const $ = cheerio.load(html);
808
810
  const posts = [];
809
- $('ul.event-list li a').each((index, element) => {
810
- const uniqId = getUniqIdFromNrichMajorEvent($(element));
811
+ $('ul.list-body li').each((index, element) => {
812
+ const linkElement = $(element).find('a[onclick]').first();
813
+ const uniqId = getUniqIdFromNrichMajorEvent(linkElement);
811
814
  const detailUrl = `https://www.nrich.go.kr/kor/majorView.do?menuIdx=286&bbs_idx=${uniqId}`;
812
- const title = $(element)
813
- .find('strong')
814
- .clone()
815
- .children('span')
816
- .remove()
817
- .end()
818
- .text()
819
- .trim();
820
- const dateSplit = getDate($(element).find('span.date').text().replaceAll('행사기간 : ', '').trim()).split(' ~ ');
815
+ const title = $(element).find('.info-tit strong.tit').text().trim();
816
+ let dateText = '';
817
+ $(element)
818
+ .find('.info-detail .detail-box')
819
+ .each((i, box) => {
820
+ const boxTitle = $(box).find('span.tit').text().trim();
821
+ if (boxTitle === '행사기간') {
822
+ dateText = $(box).find('span.cont').text().trim();
823
+ }
824
+ });
825
+ const dateSplit = getDate(dateText).split(' ~ ');
821
826
  const startDate = dateSplit[0];
822
827
  const endDate = dateSplit[1];
823
828
  const hasEndDate = startDate !== endDate;
@@ -837,21 +842,17 @@ const parseNrichJournalList = (html) => {
837
842
  const $ = cheerio.load(html);
838
843
  const posts = [];
839
844
  const baseUrl = 'https://www.nrich.go.kr';
840
- $('table.table-list tbody tr').each((index, element) => {
841
- const columns = $(element).find('td');
842
- if (columns.length === 0) {
843
- return;
844
- }
845
- const titleElement = columns.eq(1).find('a');
845
+ $('ul.list-body li').each((index, element) => {
846
+ const titleElement = $(element).find('.col2 a.cont-link');
846
847
  const relativeHref = titleElement.attr('href');
847
848
  if (!relativeHref) {
848
849
  return;
849
850
  }
850
851
  const fullUrl = new URL(`${relativeHref}`, baseUrl);
851
852
  const detailUrl = fullUrl.href;
852
- const uniqId = fullUrl.searchParams.get('bbs_idx') ?? undefined;
853
- const title = titleElement.attr('title')?.trim() ?? titleElement.text()?.trim() ?? '';
854
- const date = getDate(columns.eq(2).text().trim());
853
+ const uniqId = fullUrl.searchParams.get('idx') ?? undefined;
854
+ const title = titleElement.text()?.trim() ?? '';
855
+ const date = getDate($(element).find('.col3 .cont-txt').text().trim());
855
856
  posts.push({
856
857
  uniqId,
857
858
  title,
@@ -893,18 +894,16 @@ const parseNrichPortalList = (html) => {
893
894
  };
894
895
  const parseNrichNoticeDetail = (html) => {
895
896
  const $ = cheerio.load(html);
896
- const trList = $('table.table-view tbody tr');
897
- const content = trList.eq(3).find('td');
897
+ const content = $('.view-content .info-txt');
898
898
  return {
899
899
  detailContent: new TurndownService().turndown(content.html() ?? ''),
900
- hasAttachedFile: trList.length > 4,
900
+ hasAttachedFile: $('.board-file').length > 0,
901
901
  hasAttachedImage: content.find('img').length > 0,
902
902
  };
903
903
  };
904
904
  const parseNrichMajorEventDetail = (html) => {
905
905
  const $ = cheerio.load(html);
906
- const trList = $('table.table-view tbody tr');
907
- const content = trList.eq(4).find('td');
906
+ const content = $('.view-content');
908
907
  return {
909
908
  detailContent: new TurndownService().turndown(content.html() ?? ''),
910
909
  hasAttachedFile: false,
@@ -915,16 +914,12 @@ const parseNrichJournalDetail = (html) => {
915
914
  const $ = cheerio.load(html);
916
915
  $('script, style').remove();
917
916
  const articles = [];
918
- // 테이블의행을 순회하면서 논문 정보 추출
919
- $('table.table-list tbody tr').each((index, element) => {
920
- const columns = $(element).find('td');
921
- if (columns.length === 0) {
922
- return;
923
- }
924
- const number = columns.eq(0).text().trim();
925
- const titleElement = columns.eq(1).find('a');
917
+ // 리스트의항목을 순회하면서 논문 정보 추출
918
+ $('ul.list-body li').each((index, element) => {
919
+ const number = $(element).find('.col1 .cont-txt').text().trim();
920
+ const titleElement = $(element).find('.col2 a.cont-link');
926
921
  const title = titleElement.text().trim();
927
- const author = columns.eq(2).text().trim();
922
+ const author = $(element).find('.col3 .cont-txt').text().trim();
928
923
  if (title && author) {
929
924
  articles.push(`${number}. **${title}**\n 저자: ${author}`);
930
925
  }
@@ -2198,21 +2193,21 @@ const createNewsletterHtmlTemplate = (targets) => `<!DOCTYPE html>
2198
2193
 
2199
2194
  /**
2200
2195
  * Content generation provider implementation
2201
- * - LLM-based newsletter content generation
2196
+ * - LLM-based newsletter content generation (Google Generative AI)
2202
2197
  * - HTML template provisioning
2203
2198
  * - Newsletter persistence
2204
2199
  */
2205
2200
  class ContentGenerateProvider {
2206
- openai;
2201
+ google;
2207
2202
  articleRepository;
2208
2203
  newsletterRepository;
2209
2204
  _issueOrder = null;
2210
2205
  model;
2211
- constructor(openai, articleRepository, newsletterRepository) {
2212
- this.openai = openai;
2206
+ constructor(google, articleRepository, newsletterRepository) {
2207
+ this.google = google;
2213
2208
  this.articleRepository = articleRepository;
2214
2209
  this.newsletterRepository = newsletterRepository;
2215
- this.model = this.openai('gpt-5.1');
2210
+ this.model = this.google('gemini-3-pro-preview');
2216
2211
  }
2217
2212
  /** LLM temperature setting for content generation */
2218
2213
  temperature = llmConfig.generation.temperature;
@@ -2379,6 +2374,7 @@ class TaskService {
2379
2374
  * ```typescript
2380
2375
  * const generator = createNewsletterGenerator({
2381
2376
  * openAIApiKey: process.env.OPENAI_API_KEY,
2377
+ * googleGenerativeAIApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
2382
2378
  * taskRepository: new PrismaTaskRepository(prisma),
2383
2379
  * articleRepository: new PrismaArticleRepository(prisma),
2384
2380
  * tagRepository: new PrismaTagRepository(prisma),
@@ -2398,11 +2394,14 @@ function createNewsletterGenerator(dependencies) {
2398
2394
  const openai = createOpenAI({
2399
2395
  apiKey: dependencies.openAIApiKey,
2400
2396
  });
2397
+ const google = createGoogleGenerativeAI({
2398
+ apiKey: dependencies.googleGenerativeAIApiKey,
2399
+ });
2401
2400
  const dateService = new DateService();
2402
2401
  const taskService = new TaskService(dependencies.taskRepository);
2403
2402
  const crawlingProvider = new CrawlingProvider(dependencies.articleRepository);
2404
2403
  const analysisProvider = new AnalysisProvider(openai, dependencies.articleRepository, dependencies.tagRepository);
2405
- const contentGenerateProvider = new ContentGenerateProvider(openai, dependencies.articleRepository, dependencies.newsletterRepository);
2404
+ const contentGenerateProvider = new ContentGenerateProvider(google, dependencies.articleRepository, dependencies.newsletterRepository);
2406
2405
  return new GenerateNewsletter({
2407
2406
  contentOptions,
2408
2407
  dateService,
@@ -2432,6 +2431,7 @@ function createNewsletterGenerator(dependencies) {
2432
2431
  * ```typescript
2433
2432
  * const newsletterId = await generateNewsletter({
2434
2433
  * openAIApiKey: process.env.OPENAI_API_KEY,
2434
+ * googleGenerativeAIApiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
2435
2435
  * taskRepository: new PrismaTaskRepository(prisma),
2436
2436
  * articleRepository: new PrismaArticleRepository(prisma),
2437
2437
  * tagRepository: new PrismaTagRepository(prisma),
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@heripo/research-radar",
3
3
  "private": false,
4
4
  "type": "module",
5
- "version": "1.2.6",
5
+ "version": "2.0.0",
6
6
  "description": "AI-driven intelligence for Korean cultural heritage. This package serves as both a ready-to-use newsletter service and a practical implementation example for the LLM-Newsletter-Kit.",
7
7
  "main": "dist/index.cjs",
8
8
  "module": "dist/index.js",
@@ -42,19 +42,20 @@
42
42
  "author": "kimhongyeon",
43
43
  "license": "Apache-2.0",
44
44
  "dependencies": {
45
- "@ai-sdk/openai": "^3.0.25",
45
+ "@ai-sdk/google": "^3.0.22",
46
+ "@ai-sdk/openai": "^3.0.26",
46
47
  "cheerio": "^1.2.0",
47
48
  "turndown": "^7.2.2"
48
49
  },
49
50
  "peerDependencies": {
50
- "@llm-newsletter-kit/core": "~1.1.0"
51
+ "@llm-newsletter-kit/core": "~1.2.0"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@eslint/js": "^9.39.2",
54
- "@llm-newsletter-kit/core": "^1.1.5",
55
+ "@llm-newsletter-kit/core": "^1.2.0",
55
56
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
56
57
  "@types/express": "^5.0.6",
57
- "@types/node": "^25.2.0",
58
+ "@types/node": "^25.2.1",
58
59
  "@types/turndown": "^5.0.6",
59
60
  "eslint": "^9.39.2",
60
61
  "eslint-plugin-unused-imports": "^4.3.0",