Tauri 和 Electron 都是用于开发跨平台桌面应用程序的工具,因为最近使用ChatGPT在国内环境的确不够友好,又没有一个比较轻量简洁的工具可用,如是想自己造个轻量点的轮子,力争做到代码轻量,界面美观简洁,打出的安装包小,请求ChatGPT速度快,会话安全,不被封号。实现玩之后的效果是这样:项目地址在 这里 https://github.com/bravekingzhang/tauri-chat-box
那么,既然决定要搞一个轻量版本的桌面端App,势必就需要做一些选型对比,目前开发桌面端大家可能比较熟悉的跨平台方案是Electron,但实际上,近年也出现了一个Tauri,隐隐有取Electron而代之的趋势,我们不妨来看看两者的优劣势,前期做好选型,在动手开始实施也不迟。
以下是 Tauri 和 Electron 的优劣势对比:
因此,我们可以看出,Tauri 的轻量、安全、可定制、跨平台支持是其优势,虽然Rust学习曲线陡峭,但是我们实现的ChatGPT聊天工具基本上不太涉及与操作系统的过多交互,充其量,我们的会话保存可以在Rust上实现,存储在sqlite 中,我保证这部分代码非常易懂,只需要了解最最基础的Rust语法就可以明白。
如下图所示,我们可以在这个对话App中直接问ChatGPT相关问题,有代码着色功能,在设置中:可以配置
"tauri": {
"allowlist": {
"all": true,
"http": {
"scope": ["http://**", "https://**"]
},
"shell": {
"all": false,
"open": true
}
},
这样的方式,发现没有跨域。支持流式响应的主要逻辑就是
fetch(url, {
headers,
method: "POST",
body: JSON.stringify(data),
}).then((response) => {
if (response.ok) {
const reader = response.body!.getReader();
pump(reader);
}
});
let finalResult = "";
function pump(reader: any) {
reader.read().then(({ value, done }: ReadResult) => {
if (done) {
return;
}
const text = decoder.decode(value);
const lines = text.split("\n");
for (const line of lines) {
if (line.length > 0) {
//去掉前面的 data: 和后面的换行符
const json = line.substring(6).trim();
if (json === "[DONE]") {
onResponse(finalResult);
break;
}
const result: openai.CreateChatCompletionDeltaResponse =
JSON.parse(json);
finalResult += result.choices[0]?.delta.content || "";
onData(finalResult);
}
}
pump(reader);
});
}
} catch (err: any) {
onError(new Error(err));
}
这样做的主要目的就是为了可以快速看到响应,而不是等ChatGPT全部生成完毕才看到回答结果,ChatGPT就是一个生成式大预言模型,这个机制决定了他的答案是一部分一部分生成的,如果不选择流式响应,需要等待很久才可以看到完整答复。
会话记录存储在sqlite当中,做么做的目的主要是先拿Rust练练手,得到的好处是性能会比较好点,会话存储读取速度都比较快,实现细节在src-tauri/src/session.rs 文件中,其实代码不难理解。你会发现无非就是SQL语句的Rust封装而已,增删改查逻辑做完之后,就可以通过main.rs 中的 #[tauri::command],暴露给到js去调用了。
fn main() {
let db = Database::new("database.db").expect("Unable to create database connection");
let app_state = AppState::new(Mutex::new(db));
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
greet,
create_session,
get_all_sessions,
delete_session,
update_session,
add_message,
delete_message,
get_all_messages
])
.manage(app_state)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
这里是暴露的一些方法。在js中调用,我使用了repository封装去交互,尽量不要让页面耦合这些逻辑。可以看看src/repository/session.ts里面的实现。如添加一个新的会话
// 添加一个新会话
async createSession(name: string): Promise<Session> {
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
const result: Session = await invoke("create_session", {
name,
});
return result;
}
可以通过DB Browser for SQLite工具来查看数据,也可以在里面写sql去查询。
界面整体UI使用vuetifyjs实现的。
https://vuetifyjs.com/en/ 是一个基于 Vue.js 的 Material Design 组件框架,它提供了一系列高质量、易用的 UI 组件,包括按钮、卡片、表格、表单、图标等,可以帮助开发者快速构建出美观、高效的 Web 应用程序。关键是在移动设备上也有很好的兼容性。
与其他的组件框架相比,Vuetify.js 更加注重对 Material Design 概念的实现,通过在组件之间保持一致的视觉设计和交互方式,让 Vue.js 开发者可以更加方便地使用 Material Design 风格的UI组件。同时,Vuetify.js 还提供了丰富的主题定制选项,可以根据自己的需求进行风格定制。
Material Design 是我选择这个组件库的主要原因,太省心了,基本上拼凑一下就可以完成一个页面,而我们只需要关注业务逻辑部分。