Skip to content

Commit 733c302

Browse files
committed
feat(api): #4 add Google Gemini
1 parent a5725f6 commit 733c302

File tree

7 files changed

+275
-45
lines changed

7 files changed

+275
-45
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Wins桌面端请移步至[发布页](https://github.com/Unagi-cq/THChatUI/releas
4848
- 讯飞星火
4949
- 智谱AI
5050
- OpenAI(包括类OpenAI式服务)
51-
- 火山方舟(DeepSeek R1)
51+
- Google AI(Gemini)
5252
- 系统主题切换:支持浅色、深色、毛玻璃三种主题,支持自定义背景图片
5353
- 响应式设计:支持PC、移动端
5454
- 多模态:
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* @fileoverview Google AI平台的HTTP调用。
3+
*/
4+
import { fetchEventSource } from "@microsoft/fetch-event-source";
5+
import store from '../../store';
6+
7+
const API_URLS = {
8+
llm: "/google/remote/v1beta/openai/chat/completions",
9+
vim: "/google/remote/v1beta/openai/chat/completions",
10+
igm: "" // 预留绘图模型
11+
};
12+
13+
/**
14+
* 调用Google AI平台的接口
15+
* @param {string} prompt - 用户输入的问题
16+
* @param {Array} history - 历史对话消息
17+
* @param {Array} files - 文件列表 图片
18+
* @param {AbortController} controller - 控制请求的取消
19+
* @param {Function} onopen - 连接成功回调
20+
* @param {Function} onmessage - 接收消息回调
21+
* @param {Function} onclose - 连接关闭回调
22+
* @param {Function} onerror - 错误处理回调
23+
*/
24+
export async function fetchAPI({
25+
prompt,
26+
history,
27+
files,
28+
controller,
29+
onopen,
30+
onmessage,
31+
onclose,
32+
onerror
33+
}) {
34+
const { setting } = store.state;
35+
const { model_config, web_search_enabled } = setting;
36+
const { version, type, can_web_search } = model_config;
37+
const api_key = setting.model_list[setting.platform].api_key_config.api_key;
38+
39+
const url = API_URLS[type] || API_URLS.llm;
40+
const is_search = (can_web_search !== undefined && can_web_search) && web_search_enabled;
41+
42+
const requestConfig = {
43+
method: "POST",
44+
headers: {
45+
"Authorization": `Bearer ${api_key}`,
46+
'Content-Type': 'application/json',
47+
'Accept': 'text/event-stream'
48+
},
49+
body: JSON.stringify(preProcess(version, prompt, history, type, files, is_search)),
50+
signal: controller.signal,
51+
onopen,
52+
onmessage,
53+
onclose,
54+
onerror,
55+
openWhenHidden: true
56+
};
57+
58+
return await fetchEventSource(url, requestConfig);
59+
}
60+
61+
/**
62+
* 构建请求体
63+
* @param {string} model_version - 模型版本标识
64+
* @param {string} prompt - 用户当前的输入内容
65+
* @param {Array} history - 历史对话记录数组
66+
* @param {string} type - 模型类型标识
67+
* @param {Array} files - 上传的文件数组,主要包含图片的base64数据
68+
* @param {boolean} is_search - 是否启用网络搜索功能
69+
*/
70+
function preProcess(model_version, prompt, history, type, files, is_search) {
71+
let body = {};
72+
switch (type) {
73+
// 文本输入格式
74+
case "llm":
75+
body = {
76+
model: model_version,
77+
messages: buildLLMMessage(prompt, history),
78+
reasoning_effort: "low",
79+
stream: true
80+
}
81+
break;
82+
// 图片输入格式
83+
case "vim":
84+
body = {
85+
model: model_version,
86+
messages: buildVIMMessage(prompt, history, files),
87+
reasoning_effort: "low",
88+
stream: true
89+
}
90+
break;
91+
}
92+
return body;
93+
}
94+
95+
/**
96+
* 构建LLM文本对话消息
97+
* @param {string} prompt - 用户当前的输入内容
98+
* @param {Array} history - 历史对话记录数组
99+
*/
100+
function buildLLMMessage(prompt, history) {
101+
function getHistory(history) {
102+
const array = [];
103+
// 排除最后一条 history,因为是本次刚发的消息
104+
for (let i = 0; i < history.length - 1; i++) {
105+
const chat = history[i];
106+
array.push({
107+
"role": "user",
108+
"content": chat.query
109+
});
110+
array.push({
111+
"role": "assistant",
112+
"content": chat.answer
113+
});
114+
}
115+
return array;
116+
}
117+
let arr = getHistory(history)
118+
arr.push({
119+
"role": "user",
120+
"content": prompt
121+
})
122+
return arr;
123+
}
124+
125+
126+
/**
127+
* 构建多模态消息
128+
* @param {string} prompt - 用户当前的输入内容
129+
* @param {Array} history - 历史对话记录数组
130+
* @param {Array} files - 上传的文件数组,主要包含图片的base64数据
131+
*/
132+
function buildVIMMessage(prompt, history, files) {
133+
function getHistory(history) {
134+
const array = [];
135+
// 排除最后一条 history,因为是本次刚发的消息
136+
for (let i = 0; i < history.length - 1; i++) {
137+
const chat = history[i];
138+
array.push({
139+
"role": "user",
140+
"content": chat.files && chat.files[0] && chat.files[0].base64 ?
141+
[{ "type": "image_url", "image_url": { "url": chat.files[0].base64 } }, { "text": chat.query }] :
142+
[{ "text": chat.query }]
143+
});
144+
array.push({
145+
"role": "assistant",
146+
"content": [{ "text": chat.answer }]
147+
});
148+
}
149+
return array;
150+
}
151+
let arr = getHistory(history);
152+
let content = [];
153+
154+
if (files && files.length > 0 && files[0].base64) {
155+
content.push({
156+
"type": "image_url",
157+
"image_url": {
158+
"url": files[0].base64
159+
}
160+
});
161+
}
162+
163+
content.push({
164+
"type": "text",
165+
"text": prompt
166+
});
167+
168+
arr.push({
169+
"role": "user",
170+
"content": content
171+
});
172+
173+
return arr;
174+
}
175+
176+
export function postProcess(event) {
177+
if (event.data === '[DONE]') {
178+
return { content: '' };
179+
}
180+
181+
const data = JSON.parse(event.data).choices[0];
182+
183+
if (data.finish_reason === "stop") {
184+
return { content: '' };
185+
}
186+
187+
if (data.delta.reasoning_content) {
188+
return { reasoning_content: data.delta.reasoning_content };
189+
} else {
190+
return { content: data.delta.content };
191+
}
192+
}

thchat-ui/src/api/config.js

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ module.exports = {
3636
}
3737
}
3838
},
39+
"Azure":
40+
{
41+
platform_name: "Azure",
42+
avatar: "azure",
43+
list: [],
44+
model_config: {
45+
form_items: [
46+
{ label: "模型类型", key: "type", options: ["llm"] },
47+
{ label: "模型调用名称", key: "version" },
48+
{ label: "模型调用地址", key: "endpoint" },
49+
{ label: "API-KEY", key: "api_key" }
50+
],
51+
rules: {
52+
type: [{ required: true, message: '请选择模型类型' }],
53+
version: [{ required: true, message: '请输入模型调用名称,模型的官方调用名称' }],
54+
endpoint: [{ required: true, message: '请输入模型调用地址' }],
55+
api_key: [{ required: true, message: '请输入API-KEY' }],
56+
}
57+
}
58+
},
3959
"Baidu_QianFan":
4060
{
4161
platform_name: "百度千帆",
@@ -63,26 +83,31 @@ module.exports = {
6383
}
6484
}
6585
},
66-
"SiliconFlow":
67-
{
68-
platform_name: "硅基流动",
69-
avatar: "siliconflow",
86+
"Google_AI": {
87+
platform_name: "谷歌AI",
88+
avatar: "google",
7089
list: [
71-
{ type: "llm", series: "deepseek", version: "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B" },
72-
{ type: "llm", series: "qwen", version: "Qwen/Qwen2-7B-Instruct" }
90+
{ type: "llm", series: "gemini", version: "gemini-2.5-pro-preview-03-25" },
91+
{ type: "llm", series: "gemini", version: "gemini-2.0-flash" },
92+
{ type: "llm", series: "gemini", version: "gemini-1.5-flash" },
93+
{ type: "llm", series: "gemini", version: "gemini-1.5-pro" },
94+
{ type: "vim", series: "gemini", version: "gemini-2.5-pro-preview-03-25" },
95+
{ type: "vim", series: "gemini", version: "gemini-2.0-flash" },
96+
{ type: "vim", series: "gemini", version: "gemini-1.5-flash" },
97+
{ type: "vim", series: "gemini", version: "gemini-1.5-pro" },
7398
],
7499
api_key_config: {
75100
api_key: "",
76-
apply_url: "https://cloud.siliconflow.cn/account/ak"
101+
apply_url: "https://aistudio.google.com/app/apikey"
77102
},
78103
model_config: {
79104
form_items: [
80-
{ label: "模型类型", key: "type", options: ["llm"] },
81-
{ label: "模型调用名称", key: "version" },
105+
{ label: "模型类型", key: "type", options: ["llm", "vim"] },
106+
{ label: "模型调用名称", key: "version" }
82107
],
83108
rules: {
84109
type: [{ required: true, message: '请选择模型类型' }],
85-
version: [{ required: true, message: '请输入模型调用名称,模型的官方调用名称' }],
110+
version: [{ required: true, message: '请输入模型调用名称,模型的官方调用名称' }]
86111
}
87112
}
88113
},
@@ -110,39 +135,21 @@ module.exports = {
110135
}
111136
}
112137
},
113-
"Xunfei_Spark":
114-
{
115-
platform_name: "讯飞星火",
116-
avatar: "spark",
117-
list: [
118-
{ type: "llm", series: "spark", version: "spark lite" }
119-
]
120-
},
121-
"Zhipu_BigModel":
138+
"SiliconFlow":
122139
{
123-
platform_name: "智谱AI",
124-
avatar: "zhipu",
140+
platform_name: "硅基流动",
141+
avatar: "siliconflow",
125142
list: [
126-
{ type: "llm", series: "zhipu", version: "glm-4-flash" },
127-
{ type: "llm", series: "zhipu", version: "glm-4-0520" },
128-
{ type: "llm", series: "zhipu", version: "glm-4-air" },
129-
{ type: "llm", series: "zhipu", version: "glm-4-plus" },
130-
{ type: "llm", series: "zhipu", version: "glm-4-long" },
131-
{ type: "llm", series: "zhipu", version: "glm-4-flashx" },
132-
{ type: "llm", series: "zhipu", version: "glm-4-airx" },
133-
{ type: "llm", series: "zhipu", version: "glm-4" },
134-
{ type: "vim", series: "zhipu", version: "glm-4v" },
135-
{ type: "igm", series: "zhipu", version: "cogview-3-flash" },
136-
{ type: "igm", series: "zhipu", version: "cogview-3" },
137-
{ type: "igm", series: "zhipu", version: "cogview-3-plus" },
143+
{ type: "llm", series: "deepseek", version: "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B" },
144+
{ type: "llm", series: "qwen", version: "Qwen/Qwen2-7B-Instruct" }
138145
],
139146
api_key_config: {
140147
api_key: "",
141-
apply_url: "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys"
148+
apply_url: "https://cloud.siliconflow.cn/account/ak"
142149
},
143150
model_config: {
144151
form_items: [
145-
{ label: "模型类型", key: "type", options: ["llm", "vim", "igm"] },
152+
{ label: "模型类型", key: "type", options: ["llm"] },
146153
{ label: "模型调用名称", key: "version" },
147154
],
148155
rules: {
@@ -178,23 +185,44 @@ module.exports = {
178185
}
179186
}
180187
},
181-
"Azure":
188+
"Xunfei_Spark":
182189
{
183-
platform_name: "Azure",
184-
avatar: "azure",
185-
list: [],
190+
platform_name: "讯飞星火",
191+
avatar: "spark",
192+
list: [
193+
{ type: "llm", series: "spark", version: "spark lite" }
194+
]
195+
},
196+
"Zhipu_BigModel":
197+
{
198+
platform_name: "智谱AI",
199+
avatar: "zhipu",
200+
list: [
201+
{ type: "llm", series: "zhipu", version: "glm-4-flash" },
202+
{ type: "llm", series: "zhipu", version: "glm-4-0520" },
203+
{ type: "llm", series: "zhipu", version: "glm-4-air" },
204+
{ type: "llm", series: "zhipu", version: "glm-4-plus" },
205+
{ type: "llm", series: "zhipu", version: "glm-4-long" },
206+
{ type: "llm", series: "zhipu", version: "glm-4-flashx" },
207+
{ type: "llm", series: "zhipu", version: "glm-4-airx" },
208+
{ type: "llm", series: "zhipu", version: "glm-4" },
209+
{ type: "vim", series: "zhipu", version: "glm-4v" },
210+
{ type: "igm", series: "zhipu", version: "cogview-3-flash" },
211+
{ type: "igm", series: "zhipu", version: "cogview-3" },
212+
{ type: "igm", series: "zhipu", version: "cogview-3-plus" },
213+
],
214+
api_key_config: {
215+
api_key: "",
216+
apply_url: "https://open.bigmodel.cn/usercenter/proj-mgmt/apikeys"
217+
},
186218
model_config: {
187219
form_items: [
188-
{ label: "模型类型", key: "type", options: ["llm"] },
220+
{ label: "模型类型", key: "type", options: ["llm", "vim", "igm"] },
189221
{ label: "模型调用名称", key: "version" },
190-
{ label: "模型调用地址", key: "endpoint" },
191-
{ label: "API-KEY", key: "api_key" }
192222
],
193223
rules: {
194224
type: [{ required: true, message: '请选择模型类型' }],
195225
version: [{ required: true, message: '请输入模型调用名称,模型的官方调用名称' }],
196-
endpoint: [{ required: true, message: '请输入模型调用地址' }],
197-
api_key: [{ required: true, message: '请输入API-KEY' }],
198226
}
199227
}
200228
},
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Loading

thchat-ui/src/views/about/index.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<ul>
3636
<li>[add] 新增了Electron打包成桌面应用功能</li>
3737
<li>[add] 新增了用户输入复制按钮</li>
38+
<li>[add] 新增了Google AI平台(Gemini) 要在本地用梯子才能连上Google服务器</li>
3839
<li>[fix] 解决了SSE请求无限报错重试bug</li>
3940
<li>[fix] 修复了多模态模型无法多轮对话bug</li>
4041
</ul>

thchat-ui/vue.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ module.exports = defineConfig({
2222
'^/ali/remote': ''
2323
},
2424
},
25+
'/google/remote': {
26+
target: 'https://generativelanguage.googleapis.com', // 代理的目标地址
27+
changeOrigin: true, // 是否需要改变源
28+
pathRewrite: {
29+
'^/google/remote': ''
30+
}
31+
},
2532
// 配置代理 本地使用
2633
'/local': {
2734
target: 'http://localhost:5000', // 代理的目标地址

0 commit comments

Comments
 (0)