前言
如标题所示,bun在前段时间发布了正式版,突发奇想用就用它配合tarui实现一个简单的模型管理工具。
搭建环境
第一步,安装bun
进入官网bun,根据提示复制命令即可安装bun,由于作者使用的windows系统,接下来都会以window终端作为演示
powershell -c "irm bun.sh/install.ps1 | iex"
or linux/macos
curl -fsSL https://bun.sh/install | bash
当然,安装过程中你可能需要一点小魔法,会有意想不到的速度哦
安装完成后使用
bun --version
检查版本
第二步,使用create-tauri-app创建项目
根据tauri给出的文档,使用以下命令开始创建项目(当然,你需要根据tauri的文档完成运行前的依赖安装,rust和vs依赖,这里就不做赘述了)
bun create tauri-app --beta
然后根据vite的提示选择需要的环境,这里依次选择
这时候项目基础就搭建的差不多了,接下来使用以下命令安装tailwindcss和fluent ui
bun add -D tailwindcss
bunx tailwindcss init
bun add @fluentui/react-components
接下来打开项目,使用bun install安装项目依赖,这时候项目就已经可以跑通了,你将会看到一个简单的窗口
第三步,对依赖完成文件修改
- 修改
tailwind.config.js文件
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
- 修改
main.ts将fluent的配置组件引入进来
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
<FluentProvider theme={webLightTheme}>
<App />
</FluentProvider>
修改代码,完成基本功能
第一步,修改rust代码,让我们可以调用ollama的功能
- 首先,在
src-tauri/src目录下创建一个新的rust文件,model.rs - 然后在里面添加如下代码
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中修改代码,将我们编写的函数注入进去
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,最后在列表中加入如下代码,就可以获得一个简易的模型列表了
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
bun add -D react-devtools
接下来使用bunx react-devtools,然后将react-devtools窗口的端口代码复制到index.html中便可以运行react-devtools,查看组件的详细信息了
但是 这样每次运行都需要打开两个命令窗口,太过于麻烦了。我们可以尝试换个方法使用它
- 首先,在项目的根目录下创建一个名为
start-script.js的脚本文件 - 然后在
start-script.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的,最后实在太懒了,就实现了一部分。