Skip to content

Commit ad00ca4

Browse files
committed
feat: 优化UI体验和添加帮助说明 v0.7.2
- 移除冗余的 OpenAI/Claude 自定义 Tab 和可用模型 Tab - 合并启动/停止服务按钮为单个切换按钮 - 添加可折叠的 HelpTip 组件 - 为凭证池、配置切换、API Server、MCP、Prompts、Skills 页面添加帮助说明 - 在关于页面添加使用说明 Q&A - 优化日志页面,添加回到顶部按钮 - 优化 Provider 切换提示,显示在框内 - 重命名 AI Clients 为配置切换,说明更清晰
1 parent b819295 commit ad00ca4

18 files changed

Lines changed: 509 additions & 243 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "proxycast",
33
"private": true,
4-
"version": "0.7.0",
4+
"version": "0.7.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "proxycast"
3-
version = "0.7.0"
3+
version = "0.7.2"
44
description = "AI API Proxy Desktop App"
55
authors = ["you"]
66
edition = "2021"

src-tauri/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ async fn set_default_provider(
211211

212212
let mut s = state.write().await;
213213
s.config.default_provider = provider.clone();
214+
215+
// 同时更新运行中服务器的 default_provider_ref
216+
{
217+
let mut dp = s.default_provider_ref.write().await;
218+
*dp = provider.clone();
219+
}
220+
214221
config::save_config(&s.config).map_err(|e| e.to_string())?;
215222
logs.write()
216223
.await

src-tauri/src/server.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub struct ServerState {
5959
pub qwen_provider: QwenProvider,
6060
pub openai_custom_provider: OpenAICustomProvider,
6161
pub claude_custom_provider: ClaudeCustomProvider,
62+
pub default_provider_ref: Arc<RwLock<String>>,
6263
shutdown_tx: Option<oneshot::Sender<()>>,
6364
}
6465

@@ -69,6 +70,7 @@ impl ServerState {
6970
let qwen = QwenProvider::new();
7071
let openai_custom = OpenAICustomProvider::new();
7172
let claude_custom = ClaudeCustomProvider::new();
73+
let default_provider_ref = Arc::new(RwLock::new(config.default_provider.clone()));
7274

7375
Self {
7476
config,
@@ -80,6 +82,7 @@ impl ServerState {
8082
qwen_provider: qwen,
8183
openai_custom_provider: openai_custom,
8284
claude_custom_provider: claude_custom,
85+
default_provider_ref,
8386
shutdown_tx: None,
8487
}
8588
}
@@ -111,6 +114,7 @@ impl ServerState {
111114
let host = self.config.server.host.clone();
112115
let port = self.config.server.port;
113116
let api_key = self.config.server.api_key.clone();
117+
let default_provider_ref = self.default_provider_ref.clone();
114118

115119
// 重新加载凭证
116120
let _ = self.kiro_provider.load_credentials().await;
@@ -121,6 +125,7 @@ impl ServerState {
121125
&host,
122126
port,
123127
&api_key,
128+
default_provider_ref,
124129
kiro,
125130
logs,
126131
rx,
@@ -163,6 +168,7 @@ impl Clone for KiroProvider {
163168
struct AppState {
164169
api_key: String,
165170
base_url: String,
171+
default_provider: Arc<RwLock<String>>,
166172
kiro: Arc<RwLock<KiroProvider>>,
167173
logs: Arc<RwLock<LogStore>>,
168174
kiro_refresh_lock: Arc<tokio::sync::Mutex<()>>,
@@ -177,6 +183,7 @@ async fn run_server(
177183
host: &str,
178184
port: u16,
179185
api_key: &str,
186+
default_provider: Arc<RwLock<String>>,
180187
kiro: KiroProvider,
181188
logs: Arc<RwLock<LogStore>>,
182189
shutdown: oneshot::Receiver<()>,
@@ -188,6 +195,7 @@ async fn run_server(
188195
let state = AppState {
189196
api_key: api_key.to_string(),
190197
base_url,
198+
default_provider,
191199
kiro: Arc::new(RwLock::new(kiro)),
192200
logs,
193201
kiro_refresh_lock: Arc::new(tokio::sync::Mutex::new(())),
@@ -311,6 +319,42 @@ async fn chat_completions(
311319
),
312320
);
313321

322+
// 获取当前默认 provider
323+
let default_provider = state.default_provider.read().await.clone();
324+
325+
// 尝试从凭证池中选择凭证
326+
let credential = match &state.db {
327+
Some(db) => state
328+
.pool_service
329+
.select_credential(db, &default_provider, Some(&request.model))
330+
.ok()
331+
.flatten(),
332+
None => None,
333+
};
334+
335+
// 如果找到凭证池中的凭证,使用它
336+
if let Some(cred) = credential {
337+
state.logs.write().await.add(
338+
"info",
339+
&format!(
340+
"[ROUTE] Using pool credential: type={} name={:?} uuid={}",
341+
cred.provider_type,
342+
cred.name,
343+
&cred.uuid[..8]
344+
),
345+
);
346+
return call_provider_openai(&state, &cred, &request).await;
347+
}
348+
349+
// 回退到旧的单凭证模式
350+
state.logs.write().await.add(
351+
"debug",
352+
&format!(
353+
"[ROUTE] No pool credential found for '{}', using legacy mode",
354+
default_provider
355+
),
356+
);
357+
314358
// 检查是否需要刷新 token(无 token 或即将过期)
315359
{
316360
let _guard = state.kiro_refresh_lock.lock().await;
@@ -595,6 +639,45 @@ async fn anthropic_messages(
595639
);
596640
}
597641

642+
// 获取当前默认 provider
643+
let default_provider = state.default_provider.read().await.clone();
644+
645+
// 尝试从凭证池中选择凭证
646+
let credential = match &state.db {
647+
Some(db) => {
648+
// 根据 default_provider 配置选择凭证
649+
state
650+
.pool_service
651+
.select_credential(db, &default_provider, Some(&request.model))
652+
.ok()
653+
.flatten()
654+
}
655+
None => None,
656+
};
657+
658+
// 如果找到凭证池中的凭证,使用它
659+
if let Some(cred) = credential {
660+
state.logs.write().await.add(
661+
"info",
662+
&format!(
663+
"[ROUTE] Using pool credential: type={} name={:?} uuid={}",
664+
cred.provider_type,
665+
cred.name,
666+
&cred.uuid[..8]
667+
),
668+
);
669+
return call_provider_anthropic(&state, &cred, &request).await;
670+
}
671+
672+
// 回退到旧的单凭证模式
673+
state.logs.write().await.add(
674+
"debug",
675+
&format!(
676+
"[ROUTE] No pool credential found for '{}', using legacy mode",
677+
default_provider
678+
),
679+
);
680+
598681
// 检查是否需要刷新 token(无 token 或即将过期)
599682
{
600683
let _guard = state.kiro_refresh_lock.lock().await;

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "ProxyCast",
4-
"version": "0.7.0",
4+
"version": "0.7.2",
55
"identifier": "com.proxycast.app",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/components/Dashboard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ export function Dashboard() {
144144
/>
145145
<QuickLinkCard
146146
icon={Monitor}
147-
title="AI Clients"
148-
description="配置 Claude Code/Codex/Gemini CLI"
147+
title="配置切换"
148+
description="一键切换 Claude Code/Codex/Gemini CLI 的 API 配置"
149149
status="info"
150150
statusText="管理 Provider 配置"
151151
/>

src/components/HelpTip.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState, ReactNode } from "react";
2+
import { ChevronDown, ChevronUp, HelpCircle } from "lucide-react";
3+
4+
interface HelpTipProps {
5+
title: string;
6+
children: ReactNode;
7+
defaultOpen?: boolean;
8+
variant?: "blue" | "amber" | "green";
9+
}
10+
11+
export function HelpTip({
12+
title,
13+
children,
14+
defaultOpen = false,
15+
variant = "blue",
16+
}: HelpTipProps) {
17+
const [isOpen, setIsOpen] = useState(defaultOpen);
18+
19+
const variantStyles = {
20+
blue: {
21+
border: "border-blue-200 dark:border-blue-900",
22+
bg: "bg-blue-50 dark:bg-blue-950/30",
23+
title: "text-blue-800 dark:text-blue-300",
24+
icon: "text-blue-600 dark:text-blue-400",
25+
},
26+
amber: {
27+
border: "border-amber-200 dark:border-amber-900",
28+
bg: "bg-amber-50 dark:bg-amber-950/30",
29+
title: "text-amber-800 dark:text-amber-300",
30+
icon: "text-amber-600 dark:text-amber-400",
31+
},
32+
green: {
33+
border: "border-green-200 dark:border-green-900",
34+
bg: "bg-green-50 dark:bg-green-950/30",
35+
title: "text-green-800 dark:text-green-300",
36+
icon: "text-green-600 dark:text-green-400",
37+
},
38+
};
39+
40+
const styles = variantStyles[variant];
41+
42+
return (
43+
<div className={`rounded-lg border ${styles.border} ${styles.bg} mb-2`}>
44+
<button
45+
onClick={() => setIsOpen(!isOpen)}
46+
className="flex w-full items-center justify-between p-3 text-left"
47+
>
48+
<div className="flex items-center gap-2">
49+
<HelpCircle className={`h-4 w-4 ${styles.icon}`} />
50+
<span className={`text-sm font-medium ${styles.title}`}>{title}</span>
51+
</div>
52+
{isOpen ? (
53+
<ChevronUp className={`h-4 w-4 ${styles.icon}`} />
54+
) : (
55+
<ChevronDown className={`h-4 w-4 ${styles.icon}`} />
56+
)}
57+
</button>
58+
{isOpen && <div className="px-3 pb-4 pt-1">{children}</div>}
59+
</div>
60+
);
61+
}

src/components/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface SidebarProps {
3030
const navItems = [
3131
{ id: "dashboard" as Page, label: "仪表盘", icon: LayoutDashboard },
3232
{ id: "provider-pool" as Page, label: "凭证池", icon: Database },
33-
{ id: "clients" as Page, label: "AI Clients", icon: Monitor },
33+
{ id: "clients" as Page, label: "配置切换", icon: Monitor },
3434
{ id: "api-server" as Page, label: "API Server", icon: Globe },
3535
{ id: "mcp" as Page, label: "MCP", icon: Plug },
3636
{ id: "prompts" as Page, label: "Prompts", icon: MessageSquare },

0 commit comments

Comments
 (0)