mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-19 15:09:36 +00:00
Merge pull request #3091 from qixing-jk/feat-aisummary-wordcount
Feat aisummary wordcount
This commit is contained in:
4
lib/cache/cache_manager.js
vendored
4
lib/cache/cache_manager.js
vendored
@@ -20,12 +20,12 @@ export async function getDataFromCache(key, force) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDataToCache(key, data) {
|
||||
export async function setDataToCache(key, data, customCacheTime) {
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
// console.trace('[API-->>缓存写入]:', key)
|
||||
await getApi().setCache(key, data)
|
||||
await getApi().setCache(key, data, customCacheTime)
|
||||
}
|
||||
|
||||
export async function delCacheData(key) {
|
||||
|
||||
4
lib/cache/memory_cache.js
vendored
4
lib/cache/memory_cache.js
vendored
@@ -7,8 +7,8 @@ export async function getCache(key, options) {
|
||||
return await cache.get(key)
|
||||
}
|
||||
|
||||
export async function setCache(key, data) {
|
||||
await cache.put(key, data, cacheTime * 1000)
|
||||
export async function setCache(key, data, customCacheTime) {
|
||||
await cache.put(key, data, (customCacheTime || cacheTime) * 1000)
|
||||
}
|
||||
|
||||
export async function delCache(key) {
|
||||
|
||||
@@ -38,9 +38,10 @@ export const siteConfig = (key, defaultVal = null, extendConfig = {}) => {
|
||||
case 'TAG_SORT_BY_COUNT':
|
||||
case 'THEME':
|
||||
case 'LINK':
|
||||
case 'NPM_CDN_BASE':
|
||||
case 'CDNJS_CDN_BASE':
|
||||
case 'JSDELIVR_CDN_BASE':
|
||||
case 'AI_SUMMARY_API':
|
||||
case 'AI_SUMMARY_KEY':
|
||||
case 'AI_SUMMARY_CACHE_TIME':
|
||||
case 'AI_SUMMARY_WORD_LIMIT':
|
||||
// LINK比较特殊,
|
||||
if (key === 'LINK') {
|
||||
if (!extendConfig || Object.keys(extendConfig).length === 0) {
|
||||
|
||||
@@ -87,5 +87,8 @@ export default {
|
||||
SUBSCRIBE: 'Subscribe',
|
||||
MSG: 'Get the latest news and articles to your inbox every month.',
|
||||
EMAIL: 'Email'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'AI intelligent summary',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
URL_COPIED: "L'URL est copé!",
|
||||
TABLE_OF_CONTENTS: 'Sommaire',
|
||||
RELATE_POSTS: 'Article similaire',
|
||||
COPYRIGHT: 'Droit d\'auteur',
|
||||
COPYRIGHT: "Droit d'auteur",
|
||||
AUTHOR: 'Auteur',
|
||||
URL: 'Link',
|
||||
ANALYTICS: 'Analytique',
|
||||
@@ -29,7 +29,8 @@ export default {
|
||||
ARTICLE: 'Article(s)',
|
||||
VISITORS: 'Visiteurs',
|
||||
VIEWS: 'Views',
|
||||
COPYRIGHT_NOTICE: 'Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0)',
|
||||
COPYRIGHT_NOTICE:
|
||||
'Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0)',
|
||||
RESULT_OF_SEARCH: 'Résultats',
|
||||
ARTICLE_DETAIL: 'Plus de détails',
|
||||
PASSWORD_ERROR: 'Mot de passe est incorrect!',
|
||||
@@ -50,5 +51,8 @@ export default {
|
||||
POST: {
|
||||
BACK: 'Page precedente',
|
||||
TOP: 'Haut'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: "Résumé intelligent par l'IA",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,5 +58,8 @@ export default {
|
||||
POST: {
|
||||
BACK: '前のページに戻る',
|
||||
TOP: '上に戻る'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'AIインテリジェントサマリー',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@ export default {
|
||||
POST: {
|
||||
BACK: 'Geri',
|
||||
TOP: 'Yukarı'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'Yapay Zeka Akıllı Özet',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,5 +87,8 @@ export default {
|
||||
SUBSCRIBE: '邮件订阅',
|
||||
MSG: '订阅以获取每月更新的新闻和文章,直接发送至您的邮箱。',
|
||||
EMAIL: '邮箱'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'AI智能摘要',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ export default {
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'AI智能摘要',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,5 +52,8 @@ export default {
|
||||
POST: {
|
||||
BACK: '返回',
|
||||
TOP: '回到頂端'
|
||||
},
|
||||
AI_SUMMARY: {
|
||||
NAME: 'AI智能摘要',
|
||||
}
|
||||
}
|
||||
|
||||
32
lib/plugins/aiSummary.js
Normal file
32
lib/plugins/aiSummary.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* get Ai summary
|
||||
* @returns {Promise<string>}
|
||||
* @param aiSummaryAPI
|
||||
* @param aiSummaryKey
|
||||
* @param truncatedText
|
||||
*/
|
||||
export async function getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText) {
|
||||
try {
|
||||
console.log('请求文章摘要', truncatedText.slice(0, 100))
|
||||
const response = await fetch(aiSummaryAPI, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: aiSummaryKey,
|
||||
content: truncatedText
|
||||
})
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
return data.summary
|
||||
} else {
|
||||
throw new Error('Response not ok')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ChucklePostAI:请求失败', error)
|
||||
return '获取文章摘要失败,请稍后再试。'
|
||||
}
|
||||
}
|
||||
27
lib/plugins/wordCount.js
Normal file
27
lib/plugins/wordCount.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 更新字数统计和阅读时间
|
||||
*/
|
||||
export function countWords(pageContentText) {
|
||||
const wordCount = fnGetCpmisWords(pageContentText)
|
||||
// 阅读速度 300-500每分钟
|
||||
const readTime = Math.floor(wordCount / 400) + 1
|
||||
return { wordCount, readTime }
|
||||
}
|
||||
|
||||
// 用word方式计算正文字数
|
||||
function fnGetCpmisWords(str) {
|
||||
if (!str) {
|
||||
return 0
|
||||
}
|
||||
let sLen = 0
|
||||
try {
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
str = str.replace(/(\r\n+|\s+| +)/g, '龘')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
str = str.replace(/[\x00-\xff]/g, 'm')
|
||||
str = str.replace(/m+/g, '*')
|
||||
str = str.replace(/龘+/g, '')
|
||||
sLen = str.length
|
||||
} catch (e) {}
|
||||
return sLen
|
||||
}
|
||||
@@ -2,6 +2,15 @@
|
||||
* 文章相关工具
|
||||
*/
|
||||
import { checkStartWithHttp } from '.'
|
||||
import { getPostBlocks } from '@/lib/db/getSiteData'
|
||||
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { getDataFromCache, setDataToCache } from '@/lib/cache/cache_manager'
|
||||
import { getPageContentText } from '@/pages/search/[keyword]'
|
||||
import { getAiSummary } from '@/lib/plugins/aiSummary'
|
||||
import BLOG from '@/blog.config'
|
||||
import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
|
||||
import { countWords } from '@/lib/plugins/wordCount'
|
||||
|
||||
/**
|
||||
* 获取文章的关联推荐文章列表,目前根据标签关联性筛选
|
||||
@@ -88,3 +97,87 @@ export function checkSlugHasMorThanTwoSlash(row) {
|
||||
!checkStartWithHttp(slug)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章摘要
|
||||
* @param props
|
||||
* @param pageContentText
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function getPageAISummary(props, pageContentText) {
|
||||
const aiSummaryAPI = siteConfig('AI_SUMMARY_API')
|
||||
if (aiSummaryAPI) {
|
||||
const post = props.post
|
||||
const cacheKey = `ai_summary_${post.id}`
|
||||
let aiSummary = await getDataFromCache(cacheKey)
|
||||
if (aiSummary) {
|
||||
props.post.aiSummary = aiSummary
|
||||
} else {
|
||||
const aiSummaryKey = siteConfig('AI_SUMMARY_KEY')
|
||||
const aiSummaryCacheTime = siteConfig('AI_SUMMARY_CACHE_TIME')
|
||||
const wordLimit = siteConfig('AI_SUMMARY_WORD_LIMIT', '1000')
|
||||
let content = ''
|
||||
for (let heading of post.toc) {
|
||||
content += heading.text + ' '
|
||||
}
|
||||
content += pageContentText
|
||||
const combinedText = post.title + ' ' + content
|
||||
const truncatedText = combinedText.slice(0, wordLimit)
|
||||
aiSummary = await getAiSummary(aiSummaryAPI, aiSummaryKey, truncatedText)
|
||||
await setDataToCache(cacheKey, aiSummary, aiSummaryCacheTime)
|
||||
props.post.aiSummary = aiSummary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文章数据
|
||||
* @param props
|
||||
* @param from
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function processPostData(props, from) {
|
||||
// 文章内容加载
|
||||
if (!props?.post?.blockMap) {
|
||||
props.post.blockMap = await getPostBlocks(props.post.id, from)
|
||||
}
|
||||
|
||||
if (props.post?.blockMap?.block) {
|
||||
// 目录默认加载
|
||||
props.post.content = Object.keys(props.post.blockMap.block).filter(
|
||||
key => props.post.blockMap.block[key]?.value?.parent_id === props.post.id
|
||||
)
|
||||
props.post.toc = getPageTableOfContents(props.post, props.post.blockMap)
|
||||
const pageContentText = getPageContentText(props.post, props.post.blockMap)
|
||||
const { wordCount, readTime } = countWords(pageContentText)
|
||||
props.post.wordCount = wordCount
|
||||
props.post.readTime = readTime
|
||||
await getPageAISummary(props, pageContentText)
|
||||
}
|
||||
|
||||
// 生成全文索引 && JSON.parse(BLOG.ALGOLIA_RECREATE_DATA)
|
||||
if (BLOG.ALGOLIA_APP_ID) {
|
||||
uploadDataToAlgolia(props?.post)
|
||||
}
|
||||
|
||||
// 推荐关联文章处理
|
||||
const allPosts = props.allPages?.filter(
|
||||
page => page.type === 'Post' && page.status === 'Published'
|
||||
)
|
||||
if (allPosts && allPosts.length > 0) {
|
||||
const index = allPosts.indexOf(props.post)
|
||||
props.prev = allPosts.slice(index - 1, index)[0] ?? allPosts.slice(-1)[0]
|
||||
props.next = allPosts.slice(index + 1, index + 2)[0] ?? allPosts[0]
|
||||
props.recommendPosts = getRecommendPost(
|
||||
props.post,
|
||||
allPosts,
|
||||
siteConfig('POST_RECOMMEND_COUNT')
|
||||
)
|
||||
} else {
|
||||
props.prev = null
|
||||
props.next = null
|
||||
props.recommendPosts = []
|
||||
}
|
||||
|
||||
delete props.allPages
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user