Merge pull request #3091 from qixing-jk/feat-aisummary-wordcount

Feat aisummary wordcount
This commit is contained in:
tangly1024
2025-01-01 15:39:50 +08:00
committed by GitHub
26 changed files with 418 additions and 395 deletions

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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',
}
}

View File

@@ -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 dUtilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International (CC BY-NC-SA 4.0)',
COPYRIGHT_NOTICE:
'Attribution - Pas dUtilisation 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",
}
}

View File

@@ -58,5 +58,8 @@ export default {
POST: {
BACK: '前のページに戻る',
TOP: '上に戻る'
},
AI_SUMMARY: {
NAME: 'AIインテリジェントサマリー',
}
}

View File

@@ -53,5 +53,8 @@ export default {
POST: {
BACK: 'Geri',
TOP: 'Yukarı'
},
AI_SUMMARY: {
NAME: 'Yapay Zeka Akıllı Özet',
}
}

View File

@@ -87,5 +87,8 @@ export default {
SUBSCRIBE: '邮件订阅',
MSG: '订阅以获取每月更新的新闻和文章,直接发送至您的邮箱。',
EMAIL: '邮箱'
},
AI_SUMMARY: {
NAME: 'AI智能摘要',
}
}

View File

@@ -52,5 +52,8 @@ export default {
POST: {
BACK: '返回',
TOP: '回到頂端'
},
AI_SUMMARY: {
NAME: 'AI智能摘要',
}
}

View File

@@ -52,5 +52,8 @@ export default {
POST: {
BACK: '返回',
TOP: '回到頂端'
},
AI_SUMMARY: {
NAME: 'AI智能摘要',
}
}

32
lib/plugins/aiSummary.js Normal file
View 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
View 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
}

View File

@@ -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
}