@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 +53 -53
- package/dist/index.d.ts +12 -10
- package/dist/index.js +53 -53
- package/package.json +6 -5
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
|
-
$('
|
|
803
|
-
|
|
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 =
|
|
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.
|
|
816
|
-
const date = getDate(
|
|
817
|
-
posts.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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.
|
|
831
|
-
const
|
|
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
|
-
|
|
835
|
-
|
|
836
|
-
.
|
|
837
|
-
.
|
|
838
|
-
.
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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
|
-
$('
|
|
862
|
-
const
|
|
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('
|
|
874
|
-
const title = titleElement.
|
|
875
|
-
const date = getDate(
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
$('
|
|
941
|
-
const
|
|
942
|
-
|
|
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 =
|
|
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
|
-
|
|
2222
|
+
google;
|
|
2228
2223
|
articleRepository;
|
|
2229
2224
|
newsletterRepository;
|
|
2230
2225
|
_issueOrder = null;
|
|
2231
2226
|
model;
|
|
2232
|
-
constructor(
|
|
2233
|
-
this.
|
|
2227
|
+
constructor(google, articleRepository, newsletterRepository) {
|
|
2228
|
+
this.google = google;
|
|
2234
2229
|
this.articleRepository = articleRepository;
|
|
2235
2230
|
this.newsletterRepository = newsletterRepository;
|
|
2236
|
-
this.model = this.
|
|
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(
|
|
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
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* -
|
|
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
|
|
241
|
+
private readonly google;
|
|
240
242
|
private readonly articleRepository;
|
|
241
243
|
private readonly newsletterRepository;
|
|
242
244
|
private _issueOrder;
|
|
243
|
-
model: ReturnType<
|
|
244
|
-
constructor(
|
|
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
|
-
$('
|
|
782
|
-
|
|
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 =
|
|
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.
|
|
795
|
-
const date = getDate(
|
|
796
|
-
posts.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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.
|
|
810
|
-
const
|
|
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
|
-
|
|
814
|
-
|
|
815
|
-
.
|
|
816
|
-
.
|
|
817
|
-
.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
$('
|
|
841
|
-
const
|
|
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('
|
|
853
|
-
const title = titleElement.
|
|
854
|
-
const date = getDate(
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
$('
|
|
920
|
-
const
|
|
921
|
-
|
|
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 =
|
|
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
|
-
|
|
2201
|
+
google;
|
|
2207
2202
|
articleRepository;
|
|
2208
2203
|
newsletterRepository;
|
|
2209
2204
|
_issueOrder = null;
|
|
2210
2205
|
model;
|
|
2211
|
-
constructor(
|
|
2212
|
-
this.
|
|
2206
|
+
constructor(google, articleRepository, newsletterRepository) {
|
|
2207
|
+
this.google = google;
|
|
2213
2208
|
this.articleRepository = articleRepository;
|
|
2214
2209
|
this.newsletterRepository = newsletterRepository;
|
|
2215
|
-
this.model = this.
|
|
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(
|
|
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": "
|
|
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/
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|