next主题使用Gemini进行ai文章总结

今天是清明节假期,昨晚22:50上床,一觉睡到上午10:30左右(学生党是这样的)

上午给老mac换了硅脂,清了灰,然后决定休息会。

吃完饭翻了翻immmmm.com,翻到了一篇文章:

利用 Gemini Pro 为文章添加摘要

再一看,发现Google的Gemini Pro的api调用居然是免费的!For free!!!

我很少很少能见到这么高质量的LLM的api免费,于是顿时起了兴趣,火速开始折腾。
(踩了巨他妈多!!!!的!!!!坑!!!!!!!!)

按照原文。

第一步

获取 Gemini Pro 的 API 密钥:略

《略》…………………

打开bing搜索,启动小魔法,进入google,一分钟顺利搞定,拿到一个api key

第二步

Netlify 部署 API proxy
解决部分地区不可用问题。项目地址:https://github.com/antergone/palm-netlify-proxy

部署之后复制链接,如:https://lucky-beignet-12345.netlify.app

easy,过。

第三步

改为 OpenAI 接口格式,项目地址:https://github.com/zuisong/gemini-openai-proxy

打开 main_cloudflare-workers.mjs 并复制代码到 Cloudflare Workers 中。(当然,也可以其他平台。但,只有 CF 方便手动修改)

修改以下 3 处地方:

[可选] 自用设置,第 24 行,添加上自己的主域名,:origin: “https://immmmm.com",
第 1419 行,改为 Netlify 网址:var BASE_URL = “https://lucky-beignet-12345.netlify.app"
第 2106 行,写上 Gemini Pro 的 Key:const apiKey = “YourGeminiProKey”;

WTF!!打开https://github.com/zuisong/gemini-openai-proxy,显示这个文件一共就571行,哪里来的1419行和2106行?!!!!

只好硬着头皮上。

command+F进行搜索,关键字“var BASE_URL”,搜到1/1,正好是个网址,改成第二步的netlify网址

接着关键字“const apiKey”,淦喔,没有让我填GeminiProKey的地方

难道是这个文件被更新过了?immmmm的文章发布于2024.1.30,抬头一看,该文件于3天前更新

好家伙,原来是版本不对,查看history,找到一个2024.2.2的版本,果然有2100多行,依次找到,全部改掉,over

第四步

添加前端代码
https://github.com/lmm214/immmmm/blob/master/themes/hello-friend/layouts/partials/gemini.html

什么鬼玩意??给了个html,我怎么添加??看看评论区的大佬貌似都很轻松的搞好了??

按照一贯的操作,在next主题里随便找了个post-meta.njk把html内容插进去

hexo s本地启动博客,果然出现一个生成摘要的小按钮,我按

…没反应

还是没反应

漫长的debug

此时的我还不知道我即将迎接什么

妈蛋,启动F12,报错

1
2
3
ReferenceError: Cannot access 'postTile' before initialization
at geminiAI (18578.html:321:24)
at HTMLDivElement.onclick (18578.html:303:43)

我最开始认为是html代码不能直接扔进njk文件,所以让gpt4写了个njk模版的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<div class="post-ai" onclick="geminiAI()">
<img alt="Static Badge" src="https://api-shields.edui.fun/badge/Gemini-文章摘要-blue.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im0yMS42NCAzLjY0bC0xLjI4LTEuMjhhMS4yMSAxLjIxIDAgMCAwLTEuNzIgMEwyLjM2IDE4LjY0YTEuMjEgMS4yMSAwIDAgMCAwIDEuNzJsMS4yOCAxLjI4YTEuMiAxLjIgMCAwIDAgMS43MiAwTDIxLjY0IDUuMzZhMS4yIDEuMiAwIDAgMCAwLTEuNzJNMTQgN2wzIDNNNSA2djRtMTQgNHY0TTEwIDJ2Mk03IDhIM20xOCA4aC00TTExIDNIOSIvPjwvc3ZnPg==">
</div>
<style>
@keyframes spin { from {transform: rotate(0deg);}to {transform: rotate(360deg);} }
.post-ai-result svg{animation: spin 1s infinite linear;}
</style>
<script>
let postAI = document.querySelector(".post-ai")
let postTile = document.querySelector(".post-title a").textContent
async function geminiAI() {
postAI.insertAdjacentHTML('afterend', '<div class="post-ai-result"><svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 1 1-6.219-8.56"/></svg></div>');
postAI.classList.add("noclick")
let GeminiFetch = "{{ geminiAPI }}"
try {
let postAIResult = document.querySelector(".post-ai-result")
let input = document.querySelector(".post-content").textContent
let inputHanzi = input.replace(/\n/g, '').replace(/[ ]+/g, ' ').replace(/<pre>[\s\S]*?<\/pre>/g, '').substring(0, 30000)
let toAI = `文章标题:${postTile},具体内容:${inputHanzi}`
const res = await fetch(GeminiFetch, {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
model: 'gemini-pro',
messages: [
{ role: 'system', content: `{% raw %}You are a highly skilled AI trained in language comprehension and summarization. I would like you to read the text delimited by triple quotes and summarize it into a concise abstract paragraph. Aim to retain the most important points, providing a coherent and readable summary that could help a person understand the main points of the discussion without needing to read the entire text. Please avoid unnecessary details or tangential points.
Only give me the output and nothing else. Do not wrap responses in quotes. Respond in the Chinese language.{% endraw %}` },
{ role: 'user', content: toAI }
],
temperature: 0.7,
stream: true
})
})
const reader = res.body.getReader()
while(true) {
const {value, done} = await reader.read()
if (done) {
break
}
const text = new TextDecoder().decode(value)
const match = text.match(/DONE/)
if(!match){
const textJson = JSON.parse(text.substring(5))
const resData = textJson.choices[0].delta.content
if(resData.length > 0){
//console.log(textJson)
postAIResult.textContent += resData
}
}
}
} catch (error) {
document.querySelector(".post-ai-result").remove();
console.log(error);
}
};
</script>

还是报错

重新看了报错,发现是没match到class。按F12找了一圈,发现对于next主题,titile叫做h1.post-title

改了代码,出现新报错

1
2
3
4
18578.html:303 
Uncaught ReferenceError: geminiAI is not defined
at HTMLDivElement.onclick (18578.html:303:43)
onclick @ 18578.html:303

靠,什么叫做未定义。发现chatgpt的抽象njk代码有问题

换回原来的html,出现新报错

1
2
3
SyntaxError: Unexpected token 'T', "TYPE html>"... is not valid JSON
at JSON.parse (<anonymous>)
at geminiAI (18578.html:347:31)

传出去的json有问题??在控制台一个一个查日志,看了一下发给api的东西。

输入document.querySelector("h1.post-title").textContent

输出'\n 记东方绿舟的一周\n '

不会是这玩意有问题吧🤔,加了个trim()方法,不报错了

出现新的阴间错误,api报了特么404

回到cloudflare调试api,get请求200,post请求404,妈蛋,这什么鬼

反复调试无果,觉得是这代码有问题,干脆不用2月2号的旧代码,去用新的三天前更新的代码

换上新代码果然post出200了。回到本地调试,本地报错

1
2
3
SyntaxError: Unexpected token 'o', "ot Found" is not valid JSON
at JSON.parse (<anonymous>)
at geminiAI (18578.html:347:31)

WTF?????????看一下18578.html:347:31,发现是

1
const textJson = JSON.parse(text.substring(5))

从第五个字符开始解析?改成0看一下完整的字符串

1
Page Not Found

我吃柠檬。

查api文档,没有提到。

跑到immmmm.com的教程那边,发现一个巨jb坑的地方!!!!教程没细说到底在本地前端html填什么api,人家api有专门的调用地址的!!!

看了一下我的api显示,要求在网址后面加上/v1/chat/completions/

加上了,显示调用成功,api回复:

1
2
3
4
5
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1712224375482,"model":"gemini-pro","choices":[{"delta":{"role":"assistant","content":"在上海,高一学生会被送到东方绿舟参加国防教育,体验真实的军事训练。学生们会学习拆枪、投掷手榴弹、单兵战术等技能。同时,他们也参加了军事拓展训练,如越过高墙和团队合作。此外,还组织了电影放映、扎营、水到渠成等活动。通过这一周的训练,学生们不仅增强了体能和国防意识,还加深了同学间的友谊。"},"finish_reason":null,"index":0}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1712224375482,"model":"gemini-pro","choices":[{"delta":{"role":"assistant","content":""},"finish_reason":"stop","index":0}]}

data: [DONE]

正常了。结果他妈的本地不显示!!!不显示!!!理论上,接到响应后,应该显示一段文字。但文字并没有显示在网页中??????

发现resData本来应该把api返回的数据存储,结果未定义。翻代码:

1
2
3
if (resData.length > 0) {
postAIResult.textContent += resData
}

F12调试,报错:

1
2
3
VM1279:1 Uncaught ReferenceError: resData is not defined
at <anonymous>:1:25

代码有问题,我恨你。但是这个变量可能不在全局作用域中。所以让gpt4改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
async function fetchAndLogResData() {
// 这里是您之前处理请求的代码
// ...
const reader = res.body.getReader();
while(true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const text = new TextDecoder().decode(value);
console.log('Received chunk:', text); // 输出每一部分返回的数据
const match = text.match(/DONE/);
if(!match){
try {
const textJson = JSON.parse(text.substring(0));
const resData = textJson.choices[0].delta.content;
console.log('resData:', resData); // 在控制台输出resData
if(resData.length > 0){
// 如果resData有内容,则添加到页面元素中
postAIResult.textContent += resData;
}
} catch (error) {
// 如果在解析JSON时出错,则在控制台输出错误信息
console.error('JSON parsing error:', error);
}
}
}
// ...
}

// 调用此函数,并在控制台输出结果
fetchAndLogResData();

又跑了一遍,确认了,就是抽象代码有问题特么解析不出来api返回的json。

让gpt4整个重写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script>
async function geminiAI() {
let postAIResult, postAI, postTile;
postAI = document.querySelector(".post-ai");
postTile = document.querySelector("h1.post-title").textContent;
postAI.insertAdjacentHTML('afterend', '<div class="post-ai-result"><svg xmlns="http://www.w3.org/2000/svg" width="1.5rem" height="1.5rem" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 1 1-6.219-8.56"/></svg></div>');
postAI.classList.add("noclick");
let GeminiFetch = "https://api-gemini.nbplus.eu.org/v1/chat/completions/";
try {
postAIResult = document.querySelector(".post-ai-result");
const res = await fetch(GeminiFetch, {
// ...fetch details
});
const reader = res.body.getReader();
let receivedChunks = ''; // Used to accumulate chunks of data

while(true) {
const {value, done} = await reader.read();
if (done) {
postAIResult.innerHTML += "<p>API stream completed.</p>";
break;
}
receivedChunks += new TextDecoder().decode(value);

try {
let responseEnd = receivedChunks.indexOf('\n');
while (responseEnd > -1) {
const response = JSON.parse(receivedChunks.substring(0, responseEnd));
receivedChunks = receivedChunks.substring(responseEnd + 1);
responseEnd = receivedChunks.indexOf('\n');
if (response.choices && response.choices[0] && response.choices[0].delta && response.choices[0].delta.content) {
const content = response.choices[0].delta.content;
if (content) {
postAIResult.insertAdjacentHTML('beforeend', `<p>${content}</p>`);
}
}
}
} catch (error) {
console.error('JSON parse error:', error);
}
}
} catch (error) {
if (postAIResult) postAIResult.remove();
console.error('API Error:', error);
} finally {
if (postAI) postAI.classList.remove("noclick");
}
}
</script>

妈蛋把我post标头给改了,重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<script>
async function geminiAI() {
let postAI = document.querySelector(".post-ai");
let postTile = document.querySelector("h1.post-title").textContent;
postAI.insertAdjacentHTML('afterend', '<div class="post-ai-result"><svg ...></svg></div>');
postAI.classList.add("noclick");

let GeminiFetch = "https://api-gemini.nbplus.eu.org/v1/chat/completions/";

try {
let postAIResult = document.querySelector(".post-ai-result");
let input = document.querySelector(".post-content").textContent;
input = input.replace(/\n/g, '').replace(/[ ]+/g, ' ').replace(/<pre>[\s\S]*?<\/pre>/g, '').substring(0, 30000);
let toAI = `文章标题:${postTile},具体内容:${input}`;
const res = await fetch(GeminiFetch, {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Your-Authorization-Token-Here'
},
method: 'POST',
body: JSON.stringify({
model: 'gemini-pro',
messages: [
{ role: 'system', content: `Your-system-role-message` },
{ role: 'user', content: toAI }
],
temperature: 0.7,
})
});

const data = await res.json();
if (data.choices && data.choices[0] && data.choices[0].message) {
postAIResult.textContent = data.choices[0].message.content;
}

} catch (error) {
console.error('API Error:', error);
if (postAIResult) postAIResult.remove();
} finally {
if (postAI) postAI.classList.remove("noclick");
}
}
</script>

我醉了,自己写post标头吧。

写完之后能解析出api的json并且正常显示了。但是国内网络显示不了。那我第二步是干嘛的?????????

能跑起来就不要管了,心累

后记

发现一个很搞笑的事情,把immmmm.com的前端html里面解析api返回内容的那部分里的stream方法删了就行了

奇异。

next主题如果把前端html扔进post-meta.njk里,Index的post-meta也会显示一个徽标,但是因为match不到对应的content,所以又会在F12报错

1
2
3
ReferenceError: Cannot access 'postTile' before initialization
at geminiAI (18578.html:321:24)
at HTMLDivElement.onclick (18578.html:303:43)

最后决定把这个玩意删了。immmmm的办法是在服务端定义好api key,但是抽象代码就是报错。因此我只能在本地定义api key,但这样所有人都能得到我的key。最终还是把它当作本地hexo s时候用的文章总结,不对外开放了….

心累。