Skip to content

Commit 8c005bd

Browse files
jcglqmoyxjcglqmoyx
andauthored
Add support for more LLM service providers (#2348)
Co-authored-by: jcglqmoyx <[email protected]>
1 parent 796987c commit 8c005bd

File tree

6 files changed

+152
-41
lines changed

6 files changed

+152
-41
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ package-lock.json
77
jsconfig.json
88
src/content_scripts/safari.js
99
*~
10+
.idea

README.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -611,28 +611,34 @@ There are several LLM providers integrated into Surfingkeys now, use `A` to call
611611
* Bedrock
612612
* DeepSeek
613613
* Gemini
614+
* Custom LLM provider (e.g.: SiliconFlow and OpenRouter; other OpenAI API compatible services should also work)
614615

615616
To use the feature, you need set up your credentials/API keys first, like
616617

617-
settings.defaultLLMProvider = "bedrock";
618-
settings.llm = {
619-
bedrock: {
620-
accessKeyId: '********************',
621-
secretAccessKey: '****************************************',
622-
// model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
623-
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
624-
},
625-
gemini: {
626-
apiKey: '***************************************',
627-
},
628-
ollama: {
629-
model: 'qwen2.5-coder:32b',
630-
},
631-
deepseek: {
632-
apiKey: '***********************************',
633-
model: 'deepseek-chat',
634-
}
635-
};
618+
settings.defaultLLMProvider = "bedrock";
619+
settings.llm = {
620+
bedrock: {
621+
accessKeyId: '********************',
622+
secretAccessKey: '****************************************',
623+
// model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
624+
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
625+
},
626+
gemini: {
627+
apiKey: '***************************************',
628+
},
629+
ollama: {
630+
model: 'qwen2.5-coder:32b',
631+
},
632+
deepseek: {
633+
apiKey: '***********************************',
634+
model: 'deepseek-chat',
635+
},
636+
custom: {
637+
serviceUrl: 'https://api.siliconflow.cn/v1/chat/completions',
638+
apiKey: '***********************************',
639+
model: 'deepseek-ai/DeepSeek-V3.1',
640+
}
641+
};
636642

637643
You can also use `A` in visual mode. Press `v` or `V` to enter visual mode, then `v` again to select the text you'd like to chat with AI about, then `A` to call out the LLM chat box. Now start to chat with AI about the selected text.
638644

README_CN.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -595,28 +595,34 @@ Surfingkeys默认使用[这个markdown分析器](https://github.com/chjj/marked)
595595
* Bedrock
596596
* DeepSeek
597597
* Gemini
598+
* 自定义模型(例如:SiliconFlow 和 OpenRouter, 其他和OpenAI API兼容的服务应该也可以)
598599

599600
使用之前,必须设置相应的密钥或者API key,比如
600601

601-
settings.defaultLLMProvider = "bedrock";
602-
settings.llm = {
603-
bedrock: {
604-
accessKeyId: '********************',
605-
secretAccessKey: '****************************************',
606-
// model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
607-
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
608-
},
609-
gemini: {
610-
apiKey: '***************************************',
611-
},
612-
ollama: {
613-
model: 'qwen2.5-coder:32b',
614-
},
615-
deepseek: {
616-
apiKey: '***********************************',
617-
model: 'deepseek-chat',
618-
}
619-
};
602+
settings.defaultLLMProvider = "bedrock";
603+
settings.llm = {
604+
bedrock: {
605+
accessKeyId: '********************',
606+
secretAccessKey: '****************************************',
607+
// model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
608+
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
609+
},
610+
gemini: {
611+
apiKey: '***************************************',
612+
},
613+
ollama: {
614+
model: 'qwen2.5-coder:32b',
615+
},
616+
deepseek: {
617+
apiKey: '***********************************',
618+
model: 'deepseek-chat',
619+
},
620+
custom: {
621+
serviceUrl: 'https://api.siliconflow.cn/v1/chat/completions',
622+
apiKey: '***********************************',
623+
model: 'deepseek-ai/DeepSeek-V3.1',
624+
}
625+
};
620626

621627
你也可以在Visual mode下使用大语言模型对话。按`v``V`进入Visual mode,再按`v`选中你关注的文本,最后`A`按调出对话窗口,开始和AI就选中文本进行探讨。
622628
另一个方式是使用Regional Hints mode选择需要与AI进行探讨的内容。按`L`选择一个区域,再按`l`调出对话窗口。

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@
3838
"@types/offscreencanvas": "^2019.6.4",
3939
"babel-plugin-module-resolver": "^4.1.0",
4040
"copy-webpack-plugin": "^9.0.1",
41-
"documentation": "^13.2.5",
41+
"documentation": "^14.0.3",
4242
"file-loader": "^6.2.0",
4343
"filemanager-webpack-plugin": "^8.0.0",
4444
"jest": "^27.3.1",
4545
"jest-image-snapshot": "^4.5.1",
4646
"npm-run-all": "^4.1.5",
47-
"puppeteer": "^10.2.0",
47+
"puppeteer": "^24.20.0",
4848
"strict-event-emitter-types": "^2.0.0",
4949
"string-replace-loader": "^3.0.3",
5050
"style-loader": "^3.2.1",
@@ -60,6 +60,7 @@
6060
"@pixi/constants": "^7.4.0",
6161
"@pixi/core": "^7.4.0",
6262
"@pixi/display": "^7.4.0",
63+
"@pixi/extensions": "^7.4.0",
6364
"@pixi/graphics": "^7.4.0",
6465
"@pixi/math": "^7.4.0",
6566
"@pixi/runner": "^7.4.0",
@@ -68,7 +69,6 @@
6869
"@pixi/ticker": "^7.4.0",
6970
"@pixi/unsafe-eval": "^7.4.0",
7071
"@pixi/utils": "^7.4.0",
71-
"@pixi/extensions": "^7.4.0",
7272
"ace-builds": "^1.4.12",
7373
"aws4fetch": "^1.0.20",
7474
"dompurify": "^3.2.4",

src/background/llm.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,101 @@ function gemini(req, opts) {
498498
}).catch(error => console.error('Error:', error));
499499
}
500500

501+
function custom(req, opts) {
502+
const decoder = new TextDecoder();
503+
const abortCtrl = new AbortController();
504+
505+
if (!custom.serviceUrl) {
506+
opts.onChunk('Please set service URL correctly.');
507+
opts.onComplete({});
508+
return;
509+
}
510+
if (!custom.apiKey) {
511+
opts.onChunk('Please set API key correctly.');
512+
opts.onComplete({});
513+
return;
514+
}
515+
if (!custom.model) {
516+
opts.onChunk('Please set model correctly.');
517+
opts.onComplete({});
518+
return;
519+
}
520+
521+
const transformMessages = msgs => msgs.map(m =>
522+
typeof m.content === 'string' ? m : { role: m.role, content: m.content[0].text }
523+
);
524+
525+
fetch(custom.serviceUrl, {
526+
method: 'POST',
527+
headers: {
528+
Authorization: `Bearer ${custom.apiKey}`,
529+
'Content-Type': 'application/json',
530+
},
531+
body: JSON.stringify({
532+
model: custom.model,
533+
stream: true,
534+
messages: transformMessages(req.messages),
535+
}),
536+
signal: abortCtrl.signal,
537+
})
538+
.then(resp => {
539+
const reader = resp.body.getReader();
540+
let contentBlock = { type: 'text', text: '' };
541+
542+
const readStream = () => {
543+
reader.read()
544+
.then(({ done, value }) => {
545+
if (done) {
546+
return;
547+
}
548+
const chunk = decoder.decode(value);
549+
try {
550+
const lines = chunk.trim().split('\n\n');
551+
const dataPat = /^data: /;
552+
for (const line of lines) {
553+
if (!dataPat.test(line)) {
554+
continue;
555+
}
556+
const data = line.replace(dataPat, '');
557+
if (data === '[DONE]') {
558+
opts.onComplete({ role: 'assistant', content: [contentBlock] });
559+
return;
560+
}
561+
const o = JSON.parse(data);
562+
if (o.choices?.[0]?.delta?.content) {
563+
const txt = o.choices[0].delta.content;
564+
opts.onChunk(txt);
565+
contentBlock.text += txt;
566+
}
567+
}
568+
} catch (e) {
569+
console.error('Error parsing chunk:', e);
570+
}
571+
572+
readStream();
573+
})
574+
.catch(err => {
575+
if (err.name !== 'AbortError') {
576+
console.error('Stream error:', err);
577+
}
578+
});
579+
};
580+
581+
readStream();
582+
})
583+
.catch(err => {
584+
if (err.name !== 'AbortError') {
585+
console.error('Fetch error:', err);
586+
}
587+
});
588+
589+
return () => abortCtrl.abort();
590+
}
591+
501592
export default {
502593
bedrock,
503594
deepseek,
504595
gemini,
505596
ollama,
597+
custom,
506598
}

src/background/start.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,12 @@ function start(browser) {
13241324
llmClients.bedrock.init(llmConf.bedrock);
13251325
delete message.settings.llm.bedrock;
13261326
}
1327+
if (llmConf.custom && llmConf.custom.serviceUrl && llmConf.custom.apiKey && llmConf.custom.model) {
1328+
llmClients.custom.serviceUrl = llmConf.custom.serviceUrl;
1329+
llmClients.custom.apiKey = llmConf.custom.apiKey;
1330+
llmClients.custom.model = llmConf.custom.model;
1331+
delete message.settings.llm.custom;
1332+
}
13271333
} else {
13281334
if (message.settings.showAdvanced && isMV3) {
13291335
if (isUserScriptsAvailable()) {

0 commit comments

Comments
 (0)