前言
如标题所示,bun在前段时间发布了正式版,突发奇想用就用它配合tarui实现一个简单的模型管理工具。
搭建环境
第一步,安装bun
进入官网bun,根据提示复制命令即可安装bun,由于作者使用的windows系统,接下来都会以window终端作为演示
shell代码解读复制代码powershell -c "irm bun.sh/install.ps1 | iex"
or linux/macos
bash代码解读复制代码curl -fsSL https://bun.sh/install | bash
当然,安装过程中你可能需要一点小魔法,会有意想不到的速度哦
安装完成后使用
shell代码解读复制代码bun --version
检查版本
第二步,使用create-tauri-app创建项目
根据tauri给出的文档,使用以下命令开始创建项目(当然,你需要根据tauri的文档完成运行前的依赖安装,rust和vs依赖,这里就不做赘述了)
shell代码解读复制代码bun create tauri-app --beta
然后根据vite的提示选择需要的环境,这里依次选择
这时候项目基础就搭建的差不多了,接下来使用以下命令安装tailwindcss和fluent ui
shell代码解读复制代码bun add -D tailwindcss bunx tailwindcss init bun add @fluentui/react-components
接下来打开项目,使用bun install安装项目依赖,这时候项目就已经可以跑通了,你将会看到一个简单的窗口
第三步,对依赖完成文件修改
- 修改
tailwind.config.js文件
js代码解读复制代码/** @type {import(''tailwindcss'').Config} */ export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], }
- 修改
main.ts将fluent的配置组件引入进来
ts代码解读复制代码import { FluentProvider, webLightTheme } from "@fluentui/react-components"; <FluentProvider theme={webLightTheme}> <App /> </FluentProvider>
修改代码,完成基本功能
第一步,修改rust代码,让我们可以调用ollama的功能
- 首先,在
src-tauri/src目录下创建一个新的rust文件,model.rs - 然后在里面添加如下代码
rust代码解读复制代码use serde::Serialize; use std::process::{Command, Output}; fn execute_command(cmd: &str) -> Output { let mut command = Command::new("cmd"); command .arg("/c").arg(cmd); command .output().expect("failed to execute command") } #[derive(Serialize, Debug)] pub struct Model { name: String, version: String, id: String, size: String, modified: String, } #[tauri::command] pub fn get_models() -> Vec<Model> { let output = execute_command("ollama list"); let stdout = String::from_utf8_lossy(&output.stdout); let mut models: Vec<Model> = vec![]; for line in stdout.lines().skip(1) { // 模型信息分隔 let parts: Vec<&str> = line.split(''\t'').map(|s| s.trim()).collect(); if parts.len() == 5 { // 拆分模型名称和版本 let model_name_parts = parts[0].split('':'').collect::<Vec<&str>>(); // 构造模型对象 let model = Model { name: model_name_parts[0].to_string(), version: model_name_parts[1].to_string(), id: parts[1].to_string(), size: parts[2].to_string(), modified: parts[3].to_string(), }; models.push(model); } } models } #[tauri::command] pub fn run_model(model_name: &str) { let output = execute_command(&format!("ollama run {}", model_name)); format!("{:#?}", output); } pub fn pull_model(model_name: &str) { let output = execute_command(&format!("ollama pull {}", model_name)); format!("{:#?}", output); } // 更新模型 #[tauri::command] pub fn update_model(model_name: &str) { pull_model(model_name); } // 获取模型许可 #[tauri::command] pub fn get_license(model_name: &str) -> String { let output = execute_command(&format!("ollama show {} --license", model_name)); String::from_utf8_lossy(&output.stdout).to_string() } #[tauri::command] pub fn delete_model(model_name: &str) { let output = execute_command(&format!("ollama rm {}", model_name)); format!("{:#?}", output); } // 检查 Ollama #[tauri::command] pub fn check_ollama() { let output = execute_command("ollama --version"); format!("{:#?}", output); }
- 然后在
main.rs中修改代码,将我们编写的函数注入进去
rust代码解读复制代码use crate::models::{check_ollama, delete_model, get_license, get_models, run_model, update_model}; mod models; fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ get_models, run_model, update_model, get_license, check_ollama, delete_model ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }
- 接下来修改前端代码,在
src中新建文件夹components,接着在components目录下新建文件ModelList.tsx,最后在列表中加入如下代码,就可以获得一个简易的模型列表了
tsx代码解读复制代码import * as React from "react"; import { invoke } from "@tauri-apps/api/tauri"; import { Button, Card, CardHeader, Subtitle1, Caption1, Tooltip, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem, CardFooter, } from "@fluentui/react-components"; import { MoreHorizontal20Regular } from "@fluentui/react-icons"; type Models = { name: String; id: String; size: String; modified: String; version: String; }; type ModelList = { updateTime: number; models: Models[]; }; const REFRESH_INTERVAL = 1000 * 60 * 60 * 24; const isOld = (date: number) => { if (date == null) { return true; } const nowTime = new Date().getTime(); return nowTime - date > REFRESH_INTERVAL; } export default function ModelList() { const [models, setModels] = React.useState<Models[]>([]); async function getModels() { const localModel: ModelList = JSON.parse(localStorage.getItem("modelList") || "{}"); // 对模型列表做出本地缓存 if (!localModel || isOld(localModel.updateTime)) { const data: Models[] = await invoke("get_models"); setModels(data); localStorage.setItem("modellist", JSON.stringify({ updateTime: new Date().getTime(), models: data })); } else { setModels(localModel.models); } } return ( <section className="select-none px-[4%] py-8 w-full h-full"> <section className="flex gap-4 items-center"> <Tooltip content="click to get models" relationship="label" positioning={"after"} showDelay={50} hideDelay={50}> <Button appearance="outline" onClick={getModels}> get Models </Button> </Tooltip> {/* <Subtitle2>click to get models</Subtitle2> */} </section> <section className="grid gap-4 mt-4"> {models?.map(model => ( <Card key={model.id + ""}> <CardHeader header={<Subtitle1>{model.name}</Subtitle1>} description={<Caption1>Size: {model.size}</Caption1>} action={ <Menu> <MenuTrigger disableButtonEnhancement> <Button appearance="transparent" aria-label="More options" icon={<MoreHorizontal20Regular />} /> </MenuTrigger> <MenuPopover> <MenuList> <MenuItem onClick={() => console.log("update")}>update</MenuItem> <MenuItem>delete</MenuItem> <MenuItem>license</MenuItem> </MenuList> </MenuPopover> </Menu> } /> <section className="flex gap-4"> <span>version: {model.version}</span> <span>update: {model.modified}</span> </section> <CardFooter> <Button appearance="subtle">Run Model</Button> </CardFooter> </Card> ))} </section> </section> ); }
调试
虽然webview提供的的devtools已经可以满足大部分调试了,但是我们怎么可以放过react-devtools这个强大的react项目调试工具呢
首先,我们需要先安装react-devtools
shell代码解读复制代码bun add -D react-devtools
接下来使用bunx react-devtools,然后将react-devtools窗口的端口代码复制到index.html中便可以运行react-devtools,查看组件的详细信息了
但是 这样每次运行都需要打开两个命令窗口,太过于麻烦了。我们可以尝试换个方法使用它
- 首先,在项目的根目录下创建一个名为
start-script.js的脚本文件 - 然后在
start-script.js中添加如下代码
js代码解读复制代码const { exec } = require(''child_process''); // 执行 react-devtools const devtoolsProcess = exec(''bun react-devtools''); // 执行 tauri dev const tauriProcess = exec(''tauri dev''); // 捕获错误 devtoolsProcess.on(''error'', (error) => { console.error(''Error running react-devtools:'', error); }); tauriProcess.on(''error'', (error) => { console.error(''Error running tauri dev:'', error); });
- 接下来在
package.json文件的scripts下添加一行代码"tauri:rd": "bun start-script.js" - 最后使用
bun run tauri:rd便可以启动两个窗口啦
结语
这时候我们的代码就基本上差不多了,关于其它功能暂时就不一一列出了,大家可以发挥自己的想象实现它。
这个文章的主要目的是实现一个简单的小工具,顺带学习一下标题列出的这些技术栈。
到这里就结束了,github链接就不放了,本来想做成chat的,最后实在太懒了,就实现了一部分。