-
-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3091 from qixing-jk/feat-aisummary-wordcount
Feat aisummary wordcount
- Loading branch information
Showing
26 changed files
with
418 additions
and
395 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import styles from './AISummary.module.css' | ||
import { useEffect, useState } from 'react' | ||
import { useGlobal } from '@/lib/global' | ||
|
||
const AISummary = ({ aiSummary }) => { | ||
const { locale } = useGlobal() | ||
const [summary, setSummary] = useState(aiSummary) | ||
|
||
useEffect(() => { | ||
showAiSummaryAnimation(aiSummary, setSummary) | ||
}, []) | ||
|
||
return ( | ||
aiSummary && ( | ||
<div className={styles['post-ai']}> | ||
<div className={styles['ai-container']}> | ||
<div className={styles['ai-header']}> | ||
<div className={styles['ai-icon']}> | ||
<svg | ||
xmlns='http://www.w3.org/2000/svg' | ||
viewBox='0 0 24 24' | ||
width='24' | ||
height='24'> | ||
<path | ||
fill='#ffffff' | ||
d='M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8Z' | ||
/> | ||
</svg> | ||
</div> | ||
<div className={styles['ai-title']}>{locale.AI_SUMMARY.NAME}</div> | ||
<div className={styles['ai-tag']}>GPT</div> | ||
</div> | ||
<div className={styles['ai-content']}> | ||
<div className={styles['ai-explanation']}> | ||
{summary} | ||
{summary !== aiSummary && ( | ||
<span className={styles['blinking-cursor']}></span> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
) | ||
} | ||
|
||
const showAiSummaryAnimation = (rawSummary, setSummary) => { | ||
if (!rawSummary) return | ||
let currentIndex = 0 | ||
const typingDelay = 20 | ||
const punctuationDelayMultiplier = 6 | ||
let animationRunning = true | ||
let lastUpdateTime = performance.now() | ||
const animate = () => { | ||
if (currentIndex < rawSummary.length && animationRunning) { | ||
const currentTime = performance.now() | ||
const timeDiff = currentTime - lastUpdateTime | ||
|
||
const letter = rawSummary.slice(currentIndex, currentIndex + 1) | ||
const isPunctuation = /[,。!、?,.!?]/.test(letter) | ||
const delay = isPunctuation | ||
? typingDelay * punctuationDelayMultiplier | ||
: typingDelay | ||
|
||
if (timeDiff >= delay) { | ||
setSummary(rawSummary.slice(0, currentIndex + 1)) | ||
lastUpdateTime = currentTime | ||
currentIndex++ | ||
|
||
if (currentIndex < rawSummary.length) { | ||
setSummary(rawSummary.slice(0, currentIndex)) | ||
} else { | ||
setSummary(rawSummary) | ||
observer.disconnect() | ||
} | ||
} | ||
requestAnimationFrame(animate) | ||
} | ||
} | ||
animate(rawSummary) | ||
const observer = new IntersectionObserver( | ||
entries => { | ||
animationRunning = entries[0].isIntersecting | ||
if (animationRunning && currentIndex === 0) { | ||
setTimeout(() => { | ||
requestAnimationFrame(animate) | ||
}, 200) | ||
} | ||
}, | ||
{ threshold: 0 } | ||
) | ||
let post_ai = document.querySelector('.post-ai') | ||
if (post_ai) { | ||
observer.observe(post_ai) | ||
} | ||
} | ||
|
||
export default AISummary |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
.post-ai { | ||
font-family: 'Noto Sans SC', sans-serif; | ||
margin-bottom: 20px; | ||
} | ||
.ai-container { | ||
background: linear-gradient(135deg, #f9f9f9 0%, #f5f5f5 100%); | ||
border: 1px solid #e8e8e8; | ||
border-radius: 10px; | ||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||
overflow: hidden; | ||
} | ||
.ai-header { | ||
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); | ||
color: white; | ||
padding: 12px 20px; | ||
display: flex; | ||
align-items: center; | ||
} | ||
.ai-icon { | ||
margin-right: 10px; | ||
} | ||
.ai-title { | ||
font-size: 18px; | ||
font-weight: bold; | ||
flex-grow: 1; | ||
} | ||
.ai-tag { | ||
background-color: rgba(255, 255, 255, 0.2); | ||
padding: 3px 8px; | ||
border-radius: 12px; | ||
font-size: 12px; | ||
} | ||
.ai-content { | ||
padding: 20px; | ||
} | ||
.ai-explanation { | ||
font-size: 16px; | ||
line-height: 1.6; | ||
color: #333; | ||
} | ||
.blinking-cursor { | ||
display: inline-block; | ||
width: 2px; | ||
height: 20px; | ||
background-color: #333; | ||
animation: blink 0.7s infinite; | ||
margin-left: 5px; | ||
} | ||
@keyframes blink { | ||
0% { opacity: 0; } | ||
50% { opacity: 1; } | ||
100% { opacity: 0; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,23 @@ | ||
import { useGlobal } from '@/lib/global' | ||
import { useEffect } from 'react' | ||
|
||
/** | ||
* 字数统计 | ||
* @returns | ||
*/ | ||
export default function WordCount() { | ||
export default function WordCount({ wordCount, readTime }) { | ||
const { locale } = useGlobal() | ||
useEffect(() => { | ||
countWords() | ||
}) | ||
|
||
return <span id='wordCountWrapper' className='flex gap-3 font-light'> | ||
<span className='flex whitespace-nowrap items-center'> | ||
<i className='pl-1 pr-2 fas fa-file-word' /> | ||
<span id='wordCount'>0</span> | ||
</span> | ||
<span className='flex whitespace-nowrap items-center'> | ||
<i className='mr-1 fas fa-clock' /> | ||
<span></span> | ||
<span id='readTime'>0</span> {locale.COMMON.MINUTE} | ||
</span> | ||
return ( | ||
<span id='wordCountWrapper' className='flex gap-3 font-light'> | ||
<span className='flex whitespace-nowrap items-center'> | ||
<i className='pl-1 pr-2 fas fa-file-word' /> | ||
<span>{locale.COMMON.WORD_COUNT}</span> | ||
<span id='wordCount'>{wordCount}</span> | ||
</span> | ||
<span className='flex whitespace-nowrap items-center'> | ||
<i className='mr-1 fas fa-clock' /> | ||
<span>{locale.COMMON.READ_TIME}≈</span> | ||
<span id='readTime'>{readTime}</span> {locale.COMMON.MINUTE} | ||
</span> | ||
</span> | ||
} | ||
|
||
/** | ||
* 更新字数统计和阅读时间 | ||
*/ | ||
function countWords() { | ||
const articleText = deleteHtmlTag(document.querySelector('#article-wrapper #notion-article')?.innerHTML) | ||
const wordCount = fnGetCpmisWords(articleText) | ||
// 阅读速度 300-500每分钟 | ||
document.getElementById('wordCount').innerHTML = wordCount | ||
document.getElementById('readTime').innerHTML = Math.floor(wordCount / 400) + 1 | ||
const wordCountWrapper = document.getElementById('wordCountWrapper') | ||
wordCountWrapper.classList.remove('hidden') | ||
} | ||
|
||
// 去除html标签 | ||
function deleteHtmlTag(str) { | ||
if (!str) { | ||
return '' | ||
} | ||
str = str.replace(/<[^>]+>|&[^>]+;/g, '').trim()// 去掉所有的html标签和 之类的特殊符合 | ||
return str | ||
} | ||
|
||
// 用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 | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,5 +58,8 @@ export default { | |
POST: { | ||
BACK: '前のページに戻る', | ||
TOP: '上に戻る' | ||
}, | ||
AI_SUMMARY: { | ||
NAME: 'AIインテリジェントサマリー', | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,5 +53,8 @@ export default { | |
POST: { | ||
BACK: 'Geri', | ||
TOP: 'Yukarı' | ||
}, | ||
AI_SUMMARY: { | ||
NAME: 'Yapay Zeka Akıllı Özet', | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,5 +52,8 @@ export default { | |
POST: { | ||
BACK: '返回', | ||
TOP: '回到頂端' | ||
}, | ||
AI_SUMMARY: { | ||
NAME: 'AI智能摘要', | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,5 +52,8 @@ export default { | |
POST: { | ||
BACK: '返回', | ||
TOP: '回到頂端' | ||
}, | ||
AI_SUMMARY: { | ||
NAME: 'AI智能摘要', | ||
} | ||
} |
Oops, something went wrong.