Compare commits
2 Commits
bdf2e499bc
...
46e8502359
| Author | SHA1 | Date | |
|---|---|---|---|
| 46e8502359 | |||
| fff3c4d952 |
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [1.3.0] - 2025-11-07
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Prompt file library (`src/components/PromptFilesManager.tsx`, `src/stores/promptFilesStore.ts`, `src/lib/api.ts`, `src-tauri/src/commands/prompt_files.rs`) with full CRUD, tagging/search, side-by-side preview/editor, CLAUDE.md import/export, and one-click apply/deactivate workflows.
|
||||||
|
- Cross-adapter API node manager (`src/components/NodeManager`, `src/lib/api.ts`, `src-tauri/src/utils/node_tester.rs`) that seeds default nodes, lets users persist custom endpoints, toggles availability, and runs single/bulk latency tests before wiring a relay station.
|
||||||
|
- Smart quick-start sessions (`src/App.tsx`, `src/components/WelcomePage.tsx`, `src/components/TabContent.tsx`, `src-tauri/src/commands/smart_sessions.rs`) so a single click spawns a ready-to-use Claude tab with toast/analytics feedback even when no project is selected yet.
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- Relay station workflow overhaul (`src/components/RelayStationManager.tsx`, `src/components/SortableStationItem.tsx`): drag-and-drop ordering, advanced toolbelt (DNS flush, JSON diff, hide/show details), raw source config backup editing, granular import/export progress, and tighter NodeSelector integration.
|
||||||
|
- Backend infrastructure hardening (`src-tauri/src/http_client.rs`, `src-tauri/src/utils/node_tester.rs`, `src-tauri/src/commands/*`, `src-tauri/src/utils/error.rs`): unified HTTP client presets, resilient node testing pipeline, richer error surfaces, and refactored usage/index storage to reduce duplication.
|
||||||
|
- Tab and onboarding UX (`src/App.tsx`, `src/components/WelcomePage.tsx`, `src/components/TabContent.tsx`, `src/components/Topbar.tsx`): deterministic tab creation, global events for smart sessions, better toasts, and hidden-detail toggles that keep the interface clean by default.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Addressed multiple Tauri packaging and startup regressions (notably in `src-tauri/src/main.rs`, `src-tauri/src/commands/claude.rs`, `src-tauri/src/commands/filesystem.rs`) so macOS/Windows/Linux bundles install cleanly.
|
||||||
|
- Resolved quick-start/new-session routing bugs and project-card navigation glitches by synchronizing welcome-page actions with the tab manager (`src/components/WelcomePage.tsx`, `src/components/TabContent.tsx`).
|
||||||
|
- Filled missing translations and eliminated noisy runtime warnings across locales/components (`src/locales/en/common.json`, `src/locales/zh/common.json`, `src/components/ClaudeCodeSession.tsx`, `src/components/WelcomePage.tsx`).
|
||||||
|
- Patched long-text overflow & formatting regressions in prompts, station cards, and queues via new clamps/breakpoints (`src/components/PromptFilesManager.tsx`, `src/components/ClaudeCodeSession.tsx`, `src/components/SortableStationItem.tsx`, `src/components/SessionList.tsx`).
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "claudia",
|
"name": "claudia",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -718,7 +718,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "claudia"
|
name = "claudia"
|
||||||
version = "1.2.3"
|
version = "1.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "claudia"
|
name = "claudia"
|
||||||
version = "1.2.3"
|
version = "1.3.0"
|
||||||
description = "GUI app and Toolkit for Claude Code"
|
description = "GUI app and Toolkit for Claude Code"
|
||||||
authors = ["mufeedvh", "123vviekr"]
|
authors = ["mufeedvh", "123vviekr"]
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use std::path::PathBuf;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// 导入公共模块
|
// 导入公共模块
|
||||||
use crate::http_client;
|
|
||||||
use crate::types::node_test::NodeTestResult;
|
use crate::types::node_test::NodeTestResult;
|
||||||
use crate::utils::node_tester;
|
use crate::utils::node_tester;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tauri::command;
|
use tauri::command;
|
||||||
|
|
||||||
// 导入公共模块
|
// 导入公共模块
|
||||||
use crate::types::node_test::{NodeTestResult, TestStatus};
|
use crate::types::node_test::NodeTestResult;
|
||||||
use crate::utils::node_tester;
|
use crate::utils::node_tester;
|
||||||
|
|
||||||
/// PackyCode 节点类型
|
/// PackyCode 节点类型
|
||||||
@@ -115,6 +115,7 @@ pub fn get_all_nodes() -> Vec<PackycodeNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 测试单个节点速度(仅测试网络延时,不需要认证)
|
/// 测试单个节点速度(仅测试网络延时,不需要认证)
|
||||||
|
#[allow(dead_code)]
|
||||||
async fn test_node_speed(node: &PackycodeNode) -> NodeTestResult {
|
async fn test_node_speed(node: &PackycodeNode) -> NodeTestResult {
|
||||||
let url = format!("{}/", node.url.trim_end_matches('/'));
|
let url = format!("{}/", node.url.trim_end_matches('/'));
|
||||||
let mut result = node_tester::test_node_connectivity(&url, 3000).await;
|
let mut result = node_tester::test_node_connectivity(&url, 3000).await;
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ pub fn default_client() -> Result<Client> {
|
|||||||
///
|
///
|
||||||
/// let client = fast_client()?;
|
/// let client = fast_client()?;
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn fast_client() -> Result<Client> {
|
pub fn fast_client() -> Result<Client> {
|
||||||
create_client(
|
create_client(
|
||||||
ClientConfig::default()
|
ClientConfig::default()
|
||||||
@@ -161,6 +162,7 @@ pub fn secure_client() -> Result<Client> {
|
|||||||
/// - 接受无效证书: 否
|
/// - 接受无效证书: 否
|
||||||
/// - 使用代理: 是
|
/// - 使用代理: 是
|
||||||
/// - User-Agent: "Claudia/1.0"
|
/// - User-Agent: "Claudia/1.0"
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn long_timeout_client() -> Result<Client> {
|
pub fn long_timeout_client() -> Result<Client> {
|
||||||
create_client(ClientConfig::default().timeout(60))
|
create_client(ClientConfig::default().timeout(60))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ impl TestStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 判断测试是否失败
|
/// 判断测试是否失败
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn is_failure(&self) -> bool {
|
pub fn is_failure(&self) -> bool {
|
||||||
!self.is_success()
|
!self.is_success()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 转换为字符串
|
/// 转换为字符串
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
TestStatus::Success => "success",
|
TestStatus::Success => "success",
|
||||||
@@ -88,6 +90,7 @@ impl NodeTestResult {
|
|||||||
/// assert!(result.status.is_success());
|
/// assert!(result.status.is_success());
|
||||||
/// assert_eq!(result.response_time_ms, Some(150));
|
/// assert_eq!(result.response_time_ms, Some(150));
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn success(url: String, response_time: u64) -> Self {
|
pub fn success(url: String, response_time: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
node_id: None,
|
node_id: None,
|
||||||
@@ -180,24 +183,28 @@ impl NodeTestResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 设置节点 ID
|
/// 设置节点 ID
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn with_node_id(mut self, node_id: String) -> Self {
|
pub fn with_node_id(mut self, node_id: String) -> Self {
|
||||||
self.node_id = Some(node_id);
|
self.node_id = Some(node_id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置节点名称
|
/// 设置节点名称
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn with_node_name(mut self, node_name: String) -> Self {
|
pub fn with_node_name(mut self, node_name: String) -> Self {
|
||||||
self.node_name = Some(node_name);
|
self.node_name = Some(node_name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置元数据
|
/// 设置元数据
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
|
pub fn with_metadata(mut self, metadata: HashMap<String, serde_json::Value>) -> Self {
|
||||||
self.metadata = Some(metadata);
|
self.metadata = Some(metadata);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 添加单个元数据项
|
/// 添加单个元数据项
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
|
pub fn add_metadata(mut self, key: String, value: serde_json::Value) -> Self {
|
||||||
if self.metadata.is_none() {
|
if self.metadata.is_none() {
|
||||||
self.metadata = Some(HashMap::new());
|
self.metadata = Some(HashMap::new());
|
||||||
@@ -214,16 +221,19 @@ impl NodeTestResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 判断测试是否失败
|
/// 判断测试是否失败
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn is_failure(&self) -> bool {
|
pub fn is_failure(&self) -> bool {
|
||||||
self.status.is_failure()
|
self.status.is_failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取响应时间(如果有)
|
/// 获取响应时间(如果有)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn response_time(&self) -> Option<u64> {
|
pub fn response_time(&self) -> Option<u64> {
|
||||||
self.response_time_ms
|
self.response_time_ms
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取错误信息(如果有)
|
/// 获取错误信息(如果有)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn error(&self) -> Option<&str> {
|
pub fn error(&self) -> Option<&str> {
|
||||||
self.error_details.as_deref()
|
self.error_details.as_deref()
|
||||||
}
|
}
|
||||||
@@ -250,6 +260,7 @@ pub struct BatchTestSummary {
|
|||||||
|
|
||||||
impl BatchTestSummary {
|
impl BatchTestSummary {
|
||||||
/// 从测试结果列表生成统计摘要
|
/// 从测试结果列表生成统计摘要
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn from_results(results: &[NodeTestResult]) -> Self {
|
pub fn from_results(results: &[NodeTestResult]) -> Self {
|
||||||
let total = results.len();
|
let total = results.len();
|
||||||
let success = results.iter().filter(|r| r.is_success()).count();
|
let success = results.iter().filter(|r| r.is_success()).count();
|
||||||
@@ -286,6 +297,7 @@ impl BatchTestSummary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 获取成功率(百分比)
|
/// 获取成功率(百分比)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn success_rate(&self) -> f64 {
|
pub fn success_rate(&self) -> f64 {
|
||||||
if self.total == 0 {
|
if self.total == 0 {
|
||||||
0.0
|
0.0
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use anyhow::Result;
|
|||||||
/// to_string_error(some_operation())
|
/// to_string_error(some_operation())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn to_string_error<T>(result: Result<T>) -> Result<T, String> {
|
pub fn to_string_error<T>(result: Result<T>) -> Result<T, String> {
|
||||||
result.map_err(|e| e.to_string())
|
result.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
@@ -45,6 +46,7 @@ pub fn to_string_error<T>(result: Result<T>) -> Result<T, String> {
|
|||||||
/// )
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn to_string_error_ctx<T>(result: Result<T>, context: &str) -> Result<T, String> {
|
pub fn to_string_error_ctx<T>(result: Result<T>, context: &str) -> Result<T, String> {
|
||||||
result.map_err(|e| format!("{}: {}", context, e))
|
result.map_err(|e| format!("{}: {}", context, e))
|
||||||
}
|
}
|
||||||
@@ -63,6 +65,7 @@ pub fn to_string_error_ctx<T>(result: Result<T>, context: &str) -> Result<T, Str
|
|||||||
/// Ok("result".to_string())
|
/// Ok("result".to_string())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn db_error_to_string(e: rusqlite::Error) -> String {
|
pub fn db_error_to_string(e: rusqlite::Error) -> String {
|
||||||
match e {
|
match e {
|
||||||
rusqlite::Error::QueryReturnedNoRows => "查询未返回任何行".to_string(),
|
rusqlite::Error::QueryReturnedNoRows => "查询未返回任何行".to_string(),
|
||||||
@@ -100,6 +103,7 @@ pub fn db_error_to_string(e: rusqlite::Error) -> String {
|
|||||||
/// response.text().await.map_err(http_error_to_string)
|
/// response.text().await.map_err(http_error_to_string)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn http_error_to_string(e: reqwest::Error) -> String {
|
pub fn http_error_to_string(e: reqwest::Error) -> String {
|
||||||
if e.is_timeout() {
|
if e.is_timeout() {
|
||||||
format!("请求超时: {}", e)
|
format!("请求超时: {}", e)
|
||||||
@@ -122,11 +126,13 @@ pub fn http_error_to_string(e: reqwest::Error) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 将 serde_json::Error 转换为用户友好的错误消息
|
/// 将 serde_json::Error 转换为用户友好的错误消息
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn json_error_to_string(e: serde_json::Error) -> String {
|
pub fn json_error_to_string(e: serde_json::Error) -> String {
|
||||||
format!("JSON 解析错误: {}", e)
|
format!("JSON 解析错误: {}", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 将 std::io::Error 转换为用户友好的错误消息
|
/// 将 std::io::Error 转换为用户友好的错误消息
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn io_error_to_string(e: std::io::Error) -> String {
|
pub fn io_error_to_string(e: std::io::Error) -> String {
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
@@ -158,6 +164,7 @@ pub fn io_error_to_string(e: std::io::Error) -> String {
|
|||||||
/// let combined = combine_errors(&errors);
|
/// let combined = combine_errors(&errors);
|
||||||
/// // 输出: "发生 2 个错误: 错误 1: 连接失败; 错误 2: 超时"
|
/// // 输出: "发生 2 个错误: 错误 1: 连接失败; 错误 2: 超时"
|
||||||
/// ```
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn combine_errors(errors: &[String]) -> String {
|
pub fn combine_errors(errors: &[String]) -> String {
|
||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
"无错误".to_string()
|
"无错误".to_string()
|
||||||
@@ -169,11 +176,13 @@ pub fn combine_errors(errors: &[String]) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 创建带前缀的错误消息
|
/// 创建带前缀的错误消息
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn prefixed_error(prefix: &str, error: &str) -> String {
|
pub fn prefixed_error(prefix: &str, error: &str) -> String {
|
||||||
format!("{}: {}", prefix, error)
|
format!("{}: {}", prefix, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 为错误添加建议
|
/// 为错误添加建议
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn error_with_suggestion(error: &str, suggestion: &str) -> String {
|
pub fn error_with_suggestion(error: &str, suggestion: &str) -> String {
|
||||||
format!("{}。建议: {}", error, suggestion)
|
format!("{}。建议: {}", error, suggestion)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ pub async fn test_nodes_batch(urls: Vec<String>, timeout_ms: u64) -> Vec<NodeTes
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `urls` - 节点 URL 列表
|
/// * `urls` - 节点 URL 列表
|
||||||
/// * `timeout_ms` - 每个节点的超时时间(毫秒)
|
/// * `timeout_ms` - 每个节点的超时时间(毫秒)
|
||||||
|
#[allow(dead_code)]
|
||||||
pub async fn test_nodes_sequential(urls: Vec<String>, timeout_ms: u64) -> Vec<NodeTestResult> {
|
pub async fn test_nodes_sequential(urls: Vec<String>, timeout_ms: u64) -> Vec<NodeTestResult> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
|
|
||||||
@@ -168,11 +169,13 @@ pub fn find_fastest_node(results: &[NodeTestResult]) -> Option<&NodeTestResult>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 过滤成功的节点
|
/// 过滤成功的节点
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn filter_successful_nodes(results: &[NodeTestResult]) -> Vec<&NodeTestResult> {
|
pub fn filter_successful_nodes(results: &[NodeTestResult]) -> Vec<&NodeTestResult> {
|
||||||
results.iter().filter(|r| r.is_success()).collect()
|
results.iter().filter(|r| r.is_success()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 过滤失败的节点
|
/// 过滤失败的节点
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn filter_failed_nodes(results: &[NodeTestResult]) -> Vec<&NodeTestResult> {
|
pub fn filter_failed_nodes(results: &[NodeTestResult]) -> Vec<&NodeTestResult> {
|
||||||
results.iter().filter(|r| r.is_failure()).collect()
|
results.iter().filter(|r| r.is_failure()).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Claudia",
|
"productName": "Claudia",
|
||||||
"version": "1.2.3",
|
"version": "1.3.0",
|
||||||
"identifier": "claudia.asterisk.so",
|
"identifier": "claudia.asterisk.so",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user