Compare commits
2 Commits
master
...
262920b8b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
262920b8b3 | ||
|
|
87cba41d01 |
66
README.md
66
README.md
@@ -1,6 +1,6 @@
|
||||
# Flomo AI
|
||||
|
||||
一个 AI 文本优化工具,包含 Android 原版和 Rust Windows 桌面移植版。
|
||||
一个 AI 文本优化工具,支持 Android 原版和 Rust Windows 桌面版。
|
||||
|
||||
## 项目结构
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
│ │ │ └── com/example/flomo_ai/
|
||||
│ │ │ ├── MainActivity.kt # 主界面
|
||||
│ │ │ ├── SecondActivity.kt # 设置页面
|
||||
│ │ │ ├── kwt.kt # JWT 生成工具
|
||||
│ │ │ └── ui/theme/ # 主题管理
|
||||
│ │ └── res/ # 布局、颜色、图标等资源
|
||||
│ └── mumu-pytest/ # MuMu 模拟器自动化测试
|
||||
@@ -26,10 +25,7 @@
|
||||
│ │ └── llm_client.rs # LLM API 调用(OpenAI 兼容格式)
|
||||
│ ├── theme/
|
||||
│ │ └── mod.rs # 明暗主题管理
|
||||
│ ├── pages/
|
||||
│ │ ├── main_page.rs # 主界面组件
|
||||
│ │ └── settings_page.rs # 设置页组件
|
||||
│ └── widgets/ # 可复用 UI 组件
|
||||
│ └── pages/ # 页面组件
|
||||
├── .cargo/config.toml # MinGW 工具链配置
|
||||
└── Cargo.toml # 依赖声明
|
||||
```
|
||||
@@ -37,7 +33,8 @@
|
||||
## 功能特性
|
||||
|
||||
### 核心功能
|
||||
- **多模型配置**:支持添加、编辑、删除多个大模型配置,首页可快速切换
|
||||
- **多模型并发**:支持同时配置 3 个 LLM 模型,发送时并发调用所有启用的模型
|
||||
- **独立测试**:每个模型可单独测试连接状态
|
||||
- **LLM API 调用**:兼容 OpenAI 格式的 API,支持自定义 Base URL、API Key、Model
|
||||
- **自定义请求头**:可添加额外的 HTTP Header
|
||||
- **提示词管理**:添加、删除、编辑系统提示词,主界面下拉选择
|
||||
@@ -45,15 +42,18 @@
|
||||
- **主题切换**:浅色模式 / 深色模式 / 跟随系统
|
||||
- **配置持久化**:JSON 格式保存到用户配置目录
|
||||
- **复制结果**:一键复制优化结果到剪贴板
|
||||
- **笔记保存**:支持 Flomo、Notion、Joplin 等笔记 API(API Key 可选)
|
||||
|
||||
### 界面布局
|
||||
- **顶部标题栏**:显示 "AI优化" + 模型选择下拉框 + 配置按钮
|
||||
- **快速操作区**:四个 emoji 快捷按钮
|
||||
- **提示词选择器**:下拉菜单选择系统提示词,显示提示词内容预览
|
||||
- **输入区域**:多行文本输入,自动增长(3-10行限制)
|
||||
- **发送/停止**:发送按钮 + 生成中可停止
|
||||
- **结果展示区**:状态标签 + 可编辑结果文本 + 复制按钮 + 保存笔记按钮
|
||||
- **主界面**:
|
||||
- 顶部标题栏 + 配置按钮
|
||||
- 提示词选择器 + 快速操作按钮
|
||||
- 输入区域 + 发送按钮
|
||||
- **三栏结果显示**:水平排列 3 个模型的结果卡片
|
||||
- **配置页面**:
|
||||
- 3 个模型的独立配置(启用开关、名称、URL、Key、Model)
|
||||
- 每个模型独立的测试按钮
|
||||
- 主题设置
|
||||
- 提示词管理
|
||||
|
||||
## 构建说明
|
||||
|
||||
@@ -74,7 +74,9 @@ cd flomo-ai-desktop
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
编译产物:`target/release/flomo-ai.exe`(~4.4MB,无控制台窗口)
|
||||
或使用 `build.bat`(需先安装 MSYS2 + MinGW)。
|
||||
|
||||
编译产物:`target/release/flomo-ai.exe`(~4MB,无控制台窗口)
|
||||
|
||||
### 编译配置
|
||||
- `.cargo/config.toml`:指定 MinGW 链接器和 `-mwindows` 参数
|
||||
@@ -83,8 +85,8 @@ cargo build --release
|
||||
### 依赖
|
||||
| 依赖 | 用途 |
|
||||
|------|------|
|
||||
| `egui` + `eframe` | GUI 框架(纯 Rust,无 C 依赖) |
|
||||
| `reqwest` (blocking + rustls-tls) | HTTP 客户端(纯 Rust TLS) |
|
||||
| `egui` + `eframe` (glow) | GUI 框架(纯 Rust,无 C 依赖) |
|
||||
| `reqwest` (blocking + native-tls) | HTTP 客户端 |
|
||||
| `serde` + `serde_json` | 序列化 / 配置持久化 |
|
||||
| `dirs` | 获取用户配置目录路径 |
|
||||
| `arboard` | 剪贴板操作 |
|
||||
@@ -95,10 +97,30 @@ cargo build --release
|
||||
|
||||
```json
|
||||
{
|
||||
"llm_config": {
|
||||
"base_url": "https://api.openai.com/v1",
|
||||
"api_key": "",
|
||||
"model": "gpt-4o"
|
||||
"llm_configs": {
|
||||
"models": [
|
||||
{
|
||||
"enabled": true,
|
||||
"name": "模型1",
|
||||
"base_url": "https://api.openai.com/v1",
|
||||
"api_key": "",
|
||||
"model": "gpt-4o"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "模型2",
|
||||
"base_url": "",
|
||||
"api_key": "",
|
||||
"model": ""
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"name": "模型3",
|
||||
"base_url": "",
|
||||
"api_key": "",
|
||||
"model": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"header_configs": [],
|
||||
"prompt_configs": [
|
||||
@@ -116,4 +138,4 @@ cargo build --release
|
||||
|
||||
## Android 版本
|
||||
|
||||
Android 版本使用 Gradle 构建,包含完整的 Kotlin 源代码和资源文件。详见 `flomo-ai/` 目录下的 `README.md`。
|
||||
Android 版本使用 Gradle 构建,包含完整的 Kotlin 源代码和资源文件。详见 `flomo-ai/` 目录下的 `README.md`。
|
||||
@@ -288,79 +288,78 @@ impl FlomoAiApp {
|
||||
ui.add_space(6.0);
|
||||
|
||||
let available_width = ui.available_width();
|
||||
let column_width = (available_width - 10.0) / 3.0;
|
||||
let column_width = (available_width - 16.0) / 3.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_height(200.0);
|
||||
for (i, display) in self.model_displays.iter().enumerate() {
|
||||
if i > 0 {
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(8.0);
|
||||
}
|
||||
|
||||
ui.set_min_width(column_width);
|
||||
ui.set_max_width(column_width);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.set_width(column_width);
|
||||
|
||||
let bg_color = if display.enabled {
|
||||
ui.style().visuals.widgets.inactive.bg_fill
|
||||
} else {
|
||||
egui::Color32::from_rgb(240, 240, 240)
|
||||
};
|
||||
|
||||
egui::Frame::none()
|
||||
.fill(bg_color)
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(200, 200, 210)))
|
||||
.rounding(6.0)
|
||||
.inner_margin(egui::Margin::same(8.0))
|
||||
.show(ui, |ui| {
|
||||
let status_color = match &display.status {
|
||||
ModelStatus::Waiting => egui::Color32::GRAY,
|
||||
ModelStatus::Loading => egui::Color32::from_rgb(255, 165, 0),
|
||||
ModelStatus::Completed => egui::Color32::from_rgb(0, 180, 0),
|
||||
ModelStatus::Error(_) => egui::Color32::RED,
|
||||
};
|
||||
|
||||
let enabled_dot = if display.enabled { "●" } else { "○" };
|
||||
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, display.name))
|
||||
.size(12.0).strong()
|
||||
.color(if display.enabled { egui::Color32::from_rgb(100, 100, 255) } else { egui::Color32::GRAY }));
|
||||
|
||||
ui.label(egui::RichText::new(&display.model).size(10.0).color(egui::Color32::GRAY));
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
||||
ui.add_space(4.0);
|
||||
|
||||
if display.result.is_empty() {
|
||||
ui.label(egui::RichText::new("等待结果...").size(11.0).color(egui::Color32::GRAY));
|
||||
} else {
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 120.0],
|
||||
egui::TextEdit::multiline(&mut display.result.clone())
|
||||
.desired_rows(5)
|
||||
.frame(false),
|
||||
);
|
||||
egui::Frame::none()
|
||||
.fill(ui.style().visuals.widgets.inactive.bg_fill)
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::from_rgb(180, 180, 200)))
|
||||
.rounding(8.0)
|
||||
.inner_margin(egui::Margin::same(10.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_min_width(column_width - 20.0);
|
||||
|
||||
let enabled_color = if display.enabled {
|
||||
egui::Color32::from_rgb(80, 80, 220)
|
||||
} else {
|
||||
egui::Color32::GRAY
|
||||
};
|
||||
|
||||
let enabled_dot = if display.enabled { "●" } else { "○" };
|
||||
ui.label(egui::RichText::new(format!("{} {}", enabled_dot, display.name))
|
||||
.size(13.0).strong().color(enabled_color));
|
||||
|
||||
ui.label(egui::RichText::new(&display.model).size(10.0).color(egui::Color32::GRAY));
|
||||
|
||||
ui.add_space(6.0);
|
||||
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
let status_color = match &display.status {
|
||||
ModelStatus::Waiting => egui::Color32::GRAY,
|
||||
ModelStatus::Loading => egui::Color32::from_rgb(255, 140, 0),
|
||||
ModelStatus::Completed => egui::Color32::from_rgb(0, 160, 0),
|
||||
ModelStatus::Error(_) => egui::Color32::RED,
|
||||
};
|
||||
|
||||
ui.label(egui::RichText::new(match &display.status {
|
||||
ModelStatus::Waiting => "● 就绪",
|
||||
ModelStatus::Loading => "◐ 生成中...",
|
||||
ModelStatus::Completed => "✓ 完成",
|
||||
ModelStatus::Error(_) => "✗ 错误",
|
||||
}).size(10.0).color(status_color));
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
if display.result.is_empty() {
|
||||
ui.label(egui::RichText::new("等待结果...").size(11.0).color(egui::Color32::GRAY));
|
||||
} else {
|
||||
let mut result_text = display.result.clone();
|
||||
ui.add_sized(
|
||||
[ui.available_width(), 120.0],
|
||||
egui::TextEdit::multiline(&mut result_text)
|
||||
.desired_rows(5)
|
||||
.frame(false),
|
||||
);
|
||||
}
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button("复制").clicked() && !display.result.is_empty() {
|
||||
self.copy_to_clipboard(&display.result);
|
||||
}
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.add_sized([ui.available_width(), 1.0], egui::Separator::default());
|
||||
ui.add_space(4.0);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(egui::RichText::new(match &display.status {
|
||||
ModelStatus::Waiting => "就绪",
|
||||
ModelStatus::Loading => "生成中...",
|
||||
ModelStatus::Completed => "完成",
|
||||
ModelStatus::Error(_) => "错误",
|
||||
}).size(10.0).color(status_color));
|
||||
|
||||
if ui.small_button("复制").clicked() && !display.result.is_empty() {
|
||||
self.copy_to_clipboard(&display.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user