mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-25 11:28:46 +08:00
Compare commits
1 Commits
feat/tabby
...
style/code
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a052af0060 |
@@ -755,7 +755,7 @@ fn launch_macos_open_app(
|
|||||||
|
|
||||||
let output = cmd
|
let output = cmd
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| format!("启动 {} 失败: {e}", app_name))?;
|
.map_err(|e| format!("启动 {app_name} 失败: {e}"))?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ mod plugin;
|
|||||||
mod prompt;
|
mod prompt;
|
||||||
mod provider;
|
mod provider;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
mod settings;
|
|
||||||
mod session_manager;
|
mod session_manager;
|
||||||
|
mod settings;
|
||||||
pub mod skill;
|
pub mod skill;
|
||||||
mod stream_check;
|
mod stream_check;
|
||||||
mod usage;
|
mod usage;
|
||||||
@@ -30,8 +30,8 @@ pub use plugin::*;
|
|||||||
pub use prompt::*;
|
pub use prompt::*;
|
||||||
pub use provider::*;
|
pub use provider::*;
|
||||||
pub use proxy::*;
|
pub use proxy::*;
|
||||||
pub use settings::*;
|
|
||||||
pub use session_manager::*;
|
pub use session_manager::*;
|
||||||
|
pub use settings::*;
|
||||||
pub use skill::*;
|
pub use skill::*;
|
||||||
pub use stream_check::*;
|
pub use stream_check::*;
|
||||||
pub use usage::*;
|
pub use usage::*;
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ mod prompt_files;
|
|||||||
mod provider;
|
mod provider;
|
||||||
mod provider_defaults;
|
mod provider_defaults;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
mod session_manager;
|
|
||||||
mod services;
|
mod services;
|
||||||
|
mod session_manager;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod store;
|
mod store;
|
||||||
mod tray;
|
mod tray;
|
||||||
|
|||||||
@@ -55,17 +55,12 @@ pub fn load_messages(path: &Path) -> Result<Vec<SessionMessage>, String> {
|
|||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
let content = message
|
let content = message.get("content").map(extract_text).unwrap_or_default();
|
||||||
.get("content")
|
|
||||||
.map(extract_text)
|
|
||||||
.unwrap_or_default();
|
|
||||||
if content.trim().is_empty() {
|
if content.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ts = value
|
let ts = value.get("timestamp").and_then(parse_timestamp_to_ms);
|
||||||
.get("timestamp")
|
|
||||||
.and_then(parse_timestamp_to_ms);
|
|
||||||
|
|
||||||
messages.push(SessionMessage { role, content, ts });
|
messages.push(SessionMessage { role, content, ts });
|
||||||
}
|
}
|
||||||
@@ -127,10 +122,7 @@ fn parse_session(path: &Path) -> Option<SessionMeta> {
|
|||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let text = message
|
let text = message.get("content").map(extract_text).unwrap_or_default();
|
||||||
.get("content")
|
|
||||||
.map(extract_text)
|
|
||||||
.unwrap_or_default();
|
|
||||||
if text.trim().is_empty() {
|
if text.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::fs::File;
|
|||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde_json::Value;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::codex_config::get_codex_config_dir;
|
use crate::codex_config::get_codex_config_dir;
|
||||||
use crate::session_manager::{SessionMessage, SessionMeta};
|
use crate::session_manager::{SessionMessage, SessionMeta};
|
||||||
@@ -60,17 +60,12 @@ pub fn load_messages(path: &Path) -> Result<Vec<SessionMessage>, String> {
|
|||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
let content = payload
|
let content = payload.get("content").map(extract_text).unwrap_or_default();
|
||||||
.get("content")
|
|
||||||
.map(extract_text)
|
|
||||||
.unwrap_or_default();
|
|
||||||
if content.trim().is_empty() {
|
if content.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ts = value
|
let ts = value.get("timestamp").and_then(parse_timestamp_to_ms);
|
||||||
.get("timestamp")
|
|
||||||
.and_then(parse_timestamp_to_ms);
|
|
||||||
|
|
||||||
messages.push(SessionMessage { role, content, ts });
|
messages.push(SessionMessage { role, content, ts });
|
||||||
}
|
}
|
||||||
@@ -139,10 +134,7 @@ fn parse_session(path: &Path) -> Option<SessionMeta> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = payload
|
let text = payload.get("content").map(extract_text).unwrap_or_default();
|
||||||
.get("content")
|
|
||||||
.map(extract_text)
|
|
||||||
.unwrap_or_default();
|
|
||||||
if text.trim().is_empty() {
|
if text.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -174,10 +166,9 @@ fn parse_session(path: &Path) -> Option<SessionMeta> {
|
|||||||
|
|
||||||
fn infer_session_id_from_filename(path: &Path) -> Option<String> {
|
fn infer_session_id_from_filename(path: &Path) -> Option<String> {
|
||||||
let file_name = path.file_name()?.to_string_lossy();
|
let file_name = path.file_name()?.to_string_lossy();
|
||||||
let re = Regex::new(
|
let re =
|
||||||
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
|
Regex::new(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
||||||
)
|
.ok()?;
|
||||||
.ok()?;
|
|
||||||
re.find(&file_name).map(|mat| mat.as_str().to_string())
|
re.find(&file_name).map(|mat| mat.as_str().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub fn extract_text(content: &Value) -> String {
|
|||||||
Value::String(text) => text.to_string(),
|
Value::String(text) => text.to_string(),
|
||||||
Value::Array(items) => items
|
Value::Array(items) => items
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| extract_text_from_item(item))
|
.filter_map(extract_text_from_item)
|
||||||
.filter(|text| !text.trim().is_empty())
|
.filter(|text| !text.trim().is_empty())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
@@ -68,10 +68,10 @@ pub fn path_basename(value: &str) -> Option<String> {
|
|||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let normalized = trimmed.trim_end_matches(|c| c == '/' || c == '\\');
|
let normalized = trimmed.trim_end_matches(['/', '\\']);
|
||||||
let last = normalized
|
let last = normalized
|
||||||
.split(['/', '\\'])
|
.split(['/', '\\'])
|
||||||
.last()
|
.next_back()
|
||||||
.filter(|segment| !segment.is_empty())?;
|
.filter(|segment| !segment.is_empty())?;
|
||||||
Some(last.to_string())
|
Some(last.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ fn launch_macos_terminal(command: &str, cwd: Option<&str>) -> Result<(), String>
|
|||||||
let script = format!(
|
let script = format!(
|
||||||
r#"tell application "Terminal"
|
r#"tell application "Terminal"
|
||||||
activate
|
activate
|
||||||
do script "{}"
|
do script "{escaped}"
|
||||||
end tell"#,
|
end tell"#
|
||||||
escaped
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let status = Command::new("osascript")
|
let status = Command::new("osascript")
|
||||||
@@ -59,10 +58,9 @@ fn launch_iterm(command: &str, cwd: Option<&str>) -> Result<(), String> {
|
|||||||
activate
|
activate
|
||||||
create window with default profile
|
create window with default profile
|
||||||
tell current session of current window
|
tell current session of current window
|
||||||
write text "{}"
|
write text "{escaped}"
|
||||||
end tell
|
end tell
|
||||||
end tell"#,
|
end tell"#
|
||||||
escaped
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let status = Command::new("osascript")
|
let status = Command::new("osascript")
|
||||||
@@ -88,7 +86,7 @@ fn launch_ghostty(command: &str, cwd: Option<&str>) -> Result<(), String> {
|
|||||||
// Note: The user's error output didn't show the working dir arg failure, so we assume flag is okay or we stick to compatible ones.
|
// Note: The user's error output didn't show the working dir arg failure, so we assume flag is okay or we stick to compatible ones.
|
||||||
// Documentation says --working-directory is supported in CLI.
|
// Documentation says --working-directory is supported in CLI.
|
||||||
let work_dir_arg = if let Some(dir) = cwd {
|
let work_dir_arg = if let Some(dir) = cwd {
|
||||||
format!("--working-directory={}", dir)
|
format!("--working-directory={dir}")
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
@@ -251,7 +249,7 @@ fn build_shell_command(command: &str, cwd: Option<&str>) -> String {
|
|||||||
|
|
||||||
fn shell_escape(value: &str) -> String {
|
fn shell_escape(value: &str) -> String {
|
||||||
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
|
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
|
||||||
format!("\"{}\"", escaped)
|
format!("\"{escaped}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escape_osascript(value: &str) -> String {
|
fn escape_osascript(value: &str) -> String {
|
||||||
|
|||||||
44
src/App.tsx
44
src/App.tsx
@@ -175,7 +175,7 @@ function App() {
|
|||||||
// 当前应用代理实际使用的供应商 ID(从 active_targets 中获取)
|
// 当前应用代理实际使用的供应商 ID(从 active_targets 中获取)
|
||||||
const activeProviderId = useMemo(() => {
|
const activeProviderId = useMemo(() => {
|
||||||
const target = proxyStatus?.active_targets?.find(
|
const target = proxyStatus?.active_targets?.find(
|
||||||
(t) => t.app_type === activeApp
|
(t) => t.app_type === activeApp,
|
||||||
);
|
);
|
||||||
return target?.provider_id;
|
return target?.provider_id;
|
||||||
}, [proxyStatus?.active_targets, activeApp]);
|
}, [proxyStatus?.active_targets, activeApp]);
|
||||||
@@ -208,7 +208,7 @@ function App() {
|
|||||||
if (event.appType === activeApp) {
|
if (event.appType === activeApp) {
|
||||||
await refetch();
|
await refetch();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[App] Failed to subscribe provider switch event", error);
|
console.error("[App] Failed to subscribe provider switch event", error);
|
||||||
@@ -242,7 +242,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[App] Failed to subscribe universal-provider-synced event",
|
"[App] Failed to subscribe universal-provider-synced event",
|
||||||
error
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -270,7 +270,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[App] Failed to check environment conflicts on startup:",
|
"[App] Failed to check environment conflicts on startup:",
|
||||||
error
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -286,7 +286,7 @@ function App() {
|
|||||||
if (migrated) {
|
if (migrated) {
|
||||||
toast.success(
|
toast.success(
|
||||||
t("migration.success", { defaultValue: "配置迁移成功" }),
|
t("migration.success", { defaultValue: "配置迁移成功" }),
|
||||||
{ closeButton: true }
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -302,7 +302,7 @@ function App() {
|
|||||||
const checkSkillsMigration = async () => {
|
const checkSkillsMigration = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await invoke<{ count: number; error?: string } | null>(
|
const result = await invoke<{ count: number; error?: string } | null>(
|
||||||
"get_skills_migration_result"
|
"get_skills_migration_result",
|
||||||
);
|
);
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
toast.error(t("migration.skillsFailed"), {
|
toast.error(t("migration.skillsFailed"), {
|
||||||
@@ -336,10 +336,10 @@ function App() {
|
|||||||
// 合并新检测到的冲突
|
// 合并新检测到的冲突
|
||||||
setEnvConflicts((prev) => {
|
setEnvConflicts((prev) => {
|
||||||
const existingKeys = new Set(
|
const existingKeys = new Set(
|
||||||
prev.map((c) => `${c.varName}:${c.sourcePath}`)
|
prev.map((c) => `${c.varName}:${c.sourcePath}`),
|
||||||
);
|
);
|
||||||
const newConflicts = conflicts.filter(
|
const newConflicts = conflicts.filter(
|
||||||
(c) => !existingKeys.has(`${c.varName}:${c.sourcePath}`)
|
(c) => !existingKeys.has(`${c.varName}:${c.sourcePath}`),
|
||||||
);
|
);
|
||||||
return [...prev, ...newConflicts];
|
return [...prev, ...newConflicts];
|
||||||
});
|
});
|
||||||
@@ -351,7 +351,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[App] Failed to check environment conflicts on app switch:",
|
"[App] Failed to check environment conflicts on app switch:",
|
||||||
error
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -433,7 +433,7 @@ function App() {
|
|||||||
t("notifications.removeFromConfigSuccess", {
|
t("notifications.removeFromConfigSuccess", {
|
||||||
defaultValue: "已从配置移除",
|
defaultValue: "已从配置移除",
|
||||||
}),
|
}),
|
||||||
{ closeButton: true }
|
{ closeButton: true },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Delete from database
|
// Delete from database
|
||||||
@@ -445,7 +445,7 @@ function App() {
|
|||||||
// Generate a unique provider key for OpenCode duplication
|
// Generate a unique provider key for OpenCode duplication
|
||||||
const generateUniqueOpencodeKey = (
|
const generateUniqueOpencodeKey = (
|
||||||
originalKey: string,
|
originalKey: string,
|
||||||
existingKeys: string[]
|
existingKeys: string[],
|
||||||
): string => {
|
): string => {
|
||||||
const baseKey = `${originalKey}-copy`;
|
const baseKey = `${originalKey}-copy`;
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ function App() {
|
|||||||
const existingKeys = Object.keys(providers);
|
const existingKeys = Object.keys(providers);
|
||||||
duplicatedProvider.providerKey = generateUniqueOpencodeKey(
|
duplicatedProvider.providerKey = generateUniqueOpencodeKey(
|
||||||
provider.id,
|
provider.id,
|
||||||
existingKeys
|
existingKeys,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ function App() {
|
|||||||
(p) =>
|
(p) =>
|
||||||
p.sortIndex !== undefined &&
|
p.sortIndex !== undefined &&
|
||||||
p.sortIndex >= newSortIndex! &&
|
p.sortIndex >= newSortIndex! &&
|
||||||
p.id !== provider.id
|
p.id !== provider.id,
|
||||||
)
|
)
|
||||||
.map((p) => ({
|
.map((p) => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
@@ -514,7 +514,7 @@ function App() {
|
|||||||
toast.error(
|
toast.error(
|
||||||
t("provider.sortUpdateFailed", {
|
t("provider.sortUpdateFailed", {
|
||||||
defaultValue: "排序更新失败",
|
defaultValue: "排序更新失败",
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
return; // 如果排序更新失败,不继续添加
|
return; // 如果排序更新失败,不继续添加
|
||||||
}
|
}
|
||||||
@@ -532,7 +532,7 @@ function App() {
|
|||||||
toast.success(
|
toast.success(
|
||||||
t("provider.terminalOpened", {
|
t("provider.terminalOpened", {
|
||||||
defaultValue: "终端已打开",
|
defaultValue: "终端已打开",
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[App] Failed to open terminal", error);
|
console.error("[App] Failed to open terminal", error);
|
||||||
@@ -540,7 +540,7 @@ function App() {
|
|||||||
toast.error(
|
toast.error(
|
||||||
t("provider.terminalOpenFailed", {
|
t("provider.terminalOpenFailed", {
|
||||||
defaultValue: "打开终端失败",
|
defaultValue: "打开终端失败",
|
||||||
}) + (errorMessage ? `: ${errorMessage}` : "")
|
}) + (errorMessage ? `: ${errorMessage}` : ""),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -721,7 +721,7 @@ function App() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
"[App] Failed to re-check conflicts after deletion:",
|
"[App] Failed to re-check conflicts after deletion:",
|
||||||
error
|
error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -755,7 +755,9 @@ function App() {
|
|||||||
size="icon"
|
size="icon"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setCurrentView(
|
setCurrentView(
|
||||||
currentView === "skillsDiscovery" ? "skills" : "providers"
|
currentView === "skillsDiscovery"
|
||||||
|
? "skills"
|
||||||
|
: "providers",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="mr-2 rounded-lg"
|
className="mr-2 rounded-lg"
|
||||||
@@ -788,7 +790,7 @@ function App() {
|
|||||||
"text-xl font-semibold transition-colors",
|
"text-xl font-semibold transition-colors",
|
||||||
isProxyRunning && isCurrentAppTakeoverActive
|
isProxyRunning && isCurrentAppTakeoverActive
|
||||||
? "text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300"
|
? "text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-300"
|
||||||
: "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
|
: "text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
CC Switch
|
CC Switch
|
||||||
@@ -934,7 +936,7 @@ function App() {
|
|||||||
"transition-all duration-300 ease-in-out overflow-hidden",
|
"transition-all duration-300 ease-in-out overflow-hidden",
|
||||||
isCurrentAppTakeoverActive
|
isCurrentAppTakeoverActive
|
||||||
? "opacity-100 max-w-[100px] scale-100"
|
? "opacity-100 max-w-[100px] scale-100"
|
||||||
: "opacity-0 max-w-0 scale-75 pointer-events-none"
|
: "opacity-0 max-w-0 scale-75 pointer-events-none",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<FailoverToggle activeApp={activeApp} />
|
<FailoverToggle activeApp={activeApp} />
|
||||||
@@ -962,7 +964,7 @@ function App() {
|
|||||||
"transition-all duration-200 ease-in-out overflow-hidden",
|
"transition-all duration-200 ease-in-out overflow-hidden",
|
||||||
hasSkillsSupport
|
hasSkillsSupport
|
||||||
? "opacity-100 w-8 scale-100 px-2"
|
? "opacity-100 w-8 scale-100 px-2"
|
||||||
: "opacity-0 w-0 scale-75 pointer-events-none px-0 -ml-1"
|
: "opacity-0 w-0 scale-75 pointer-events-none px-0 -ml-1",
|
||||||
)}
|
)}
|
||||||
title={t("skills.manage")}
|
title={t("skills.manage")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ interface AppCountBarProps {
|
|||||||
counts: Record<AppId, number>;
|
counts: Record<AppId, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppCountBar: React.FC<AppCountBarProps> = ({ totalLabel, counts }) => {
|
export const AppCountBar: React.FC<AppCountBarProps> = ({
|
||||||
|
totalLabel,
|
||||||
|
counts,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex-shrink-0 py-4 glass rounded-xl border border-white/10 mb-4 px-6 flex items-center justify-between gap-4">
|
<div className="flex-shrink-0 py-4 glass rounded-xl border border-white/10 mb-4 px-6 flex items-center justify-between gap-4">
|
||||||
<Badge variant="outline" className="bg-background/50 h-7 px-3">
|
<Badge variant="outline" className="bg-background/50 h-7 px-3">
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ interface AppToggleGroupProps {
|
|||||||
onToggle: (app: AppId, enabled: boolean) => void;
|
onToggle: (app: AppId, enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppToggleGroup: React.FC<AppToggleGroupProps> = ({ apps, onToggle }) => {
|
export const AppToggleGroup: React.FC<AppToggleGroupProps> = ({
|
||||||
|
apps,
|
||||||
|
onToggle,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5 flex-shrink-0">
|
<div className="flex items-center gap-1.5 flex-shrink-0">
|
||||||
{APP_IDS.map((app) => {
|
{APP_IDS.map((app) => {
|
||||||
@@ -25,16 +28,17 @@ export const AppToggleGroup: React.FC<AppToggleGroupProps> = ({ apps, onToggle }
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => onToggle(app, !enabled)}
|
onClick={() => onToggle(app, !enabled)}
|
||||||
className={`w-7 h-7 rounded-lg flex items-center justify-center transition-all ${
|
className={`w-7 h-7 rounded-lg flex items-center justify-center transition-all ${
|
||||||
enabled
|
enabled ? activeClass : "opacity-35 hover:opacity-70"
|
||||||
? activeClass
|
|
||||||
: "opacity-35 hover:opacity-70"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</button>
|
</button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="bottom">
|
<TooltipContent side="bottom">
|
||||||
<p>{label}{enabled ? " ✓" : ""}</p>
|
<p>
|
||||||
|
{label}
|
||||||
|
{enabled ? " ✓" : ""}
|
||||||
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ interface ListItemRowProps {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ListItemRow: React.FC<ListItemRowProps> = ({ isLast, children }) => {
|
export const ListItemRow: React.FC<ListItemRowProps> = ({
|
||||||
|
isLast,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`group flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors ${
|
className={`group flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors ${
|
||||||
|
|||||||
@@ -245,7 +245,9 @@ const UnifiedMcpListItem: React.FC<UnifiedMcpListItemProps> = ({
|
|||||||
<ListItemRow isLast={isLast}>
|
<ListItemRow isLast={isLast}>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="font-medium text-sm text-foreground truncate">{name}</span>
|
<span className="font-medium text-sm text-foreground truncate">
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
{docsUrl && (
|
{docsUrl && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -258,7 +260,10 @@ const UnifiedMcpListItem: React.FC<UnifiedMcpListItemProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-xs text-muted-foreground truncate" title={description}>
|
<p
|
||||||
|
className="text-xs text-muted-foreground truncate"
|
||||||
|
title={description}
|
||||||
|
>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function SessionItem({
|
|||||||
"w-full text-left rounded-lg px-3 py-2.5 transition-all group",
|
"w-full text-left rounded-lg px-3 py-2.5 transition-all group",
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-primary/10 border border-primary/30"
|
? "bg-primary/10 border border-primary/30"
|
||||||
: "hover:bg-muted/60 border border-transparent"
|
: "hover:bg-muted/60 border border-transparent",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
@@ -62,7 +62,7 @@ export function SessionItem({
|
|||||||
<ChevronRight
|
<ChevronRight
|
||||||
className={cn(
|
className={cn(
|
||||||
"size-4 text-muted-foreground/50 shrink-0 transition-transform",
|
"size-4 text-muted-foreground/50 shrink-0 transition-transform",
|
||||||
isSelected && "text-primary rotate-90"
|
isSelected && "text-primary rotate-90",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function SessionManagerPage() {
|
|||||||
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
const messagesEndRef = useRef<HTMLDivElement | null>(null);
|
||||||
const messageRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
const messageRefs = useRef<Map<number, HTMLDivElement>>(new Map());
|
||||||
const [activeMessageIndex, setActiveMessageIndex] = useState<number | null>(
|
const [activeMessageIndex, setActiveMessageIndex] = useState<number | null>(
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
const [tocDialogOpen, setTocDialogOpen] = useState(false);
|
const [tocDialogOpen, setTocDialogOpen] = useState(false);
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
@@ -83,7 +83,7 @@ export function SessionManagerPage() {
|
|||||||
}
|
}
|
||||||
const exists = selectedKey
|
const exists = selectedKey
|
||||||
? filteredSessions.some(
|
? filteredSessions.some(
|
||||||
(session) => getSessionKey(session) === selectedKey
|
(session) => getSessionKey(session) === selectedKey,
|
||||||
)
|
)
|
||||||
: false;
|
: false;
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
@@ -95,7 +95,7 @@ export function SessionManagerPage() {
|
|||||||
if (!selectedKey) return null;
|
if (!selectedKey) return null;
|
||||||
return (
|
return (
|
||||||
filteredSessions.find(
|
filteredSessions.find(
|
||||||
(session) => getSessionKey(session) === selectedKey
|
(session) => getSessionKey(session) === selectedKey,
|
||||||
) || null
|
) || null
|
||||||
);
|
);
|
||||||
}, [filteredSessions, selectedKey]);
|
}, [filteredSessions, selectedKey]);
|
||||||
@@ -103,7 +103,7 @@ export function SessionManagerPage() {
|
|||||||
const { data: messages = [], isLoading: isLoadingMessages } =
|
const { data: messages = [], isLoading: isLoadingMessages } =
|
||||||
useSessionMessagesQuery(
|
useSessionMessagesQuery(
|
||||||
selectedSession?.providerId,
|
selectedSession?.providerId,
|
||||||
selectedSession?.sourcePath
|
selectedSession?.sourcePath,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 提取用户消息用于目录
|
// 提取用户消息用于目录
|
||||||
@@ -147,7 +147,7 @@ export function SessionManagerPage() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
extractErrorMessage(error) ||
|
extractErrorMessage(error) ||
|
||||||
t("common.error", { defaultValue: "Copy failed" })
|
t("common.error", { defaultValue: "Copy failed" }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -158,7 +158,7 @@ export function SessionManagerPage() {
|
|||||||
if (!isMac()) {
|
if (!isMac()) {
|
||||||
await handleCopy(
|
await handleCopy(
|
||||||
selectedSession.resumeCommand,
|
selectedSession.resumeCommand,
|
||||||
t("sessionManager.resumeCommandCopied")
|
t("sessionManager.resumeCommandCopied"),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -240,14 +240,16 @@ export function SessionManagerPage() {
|
|||||||
setIsSearchOpen(true);
|
setIsSearchOpen(true);
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => searchInputRef.current?.focus(),
|
() => searchInputRef.current?.focus(),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Search className="size-3.5" />
|
<Search className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>{t("sessionManager.searchSessions")}</TooltipContent>
|
<TooltipContent>
|
||||||
|
{t("sessionManager.searchSessions")}
|
||||||
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
@@ -387,7 +389,7 @@ export function SessionManagerPage() {
|
|||||||
<span className="shrink-0">
|
<span className="shrink-0">
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
icon={getProviderIconName(
|
icon={getProviderIconName(
|
||||||
selectedSession.providerId
|
selectedSession.providerId,
|
||||||
)}
|
)}
|
||||||
name={selectedSession.providerId}
|
name={selectedSession.providerId}
|
||||||
size={20}
|
size={20}
|
||||||
@@ -410,7 +412,7 @@ export function SessionManagerPage() {
|
|||||||
<span>
|
<span>
|
||||||
{formatTimestamp(
|
{formatTimestamp(
|
||||||
selectedSession.lastActiveAt ??
|
selectedSession.lastActiveAt ??
|
||||||
selectedSession.createdAt
|
selectedSession.createdAt,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -422,7 +424,7 @@ export function SessionManagerPage() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
void handleCopy(
|
void handleCopy(
|
||||||
selectedSession.projectDir!,
|
selectedSession.projectDir!,
|
||||||
t("sessionManager.projectDirCopied")
|
t("sessionManager.projectDirCopied"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
||||||
@@ -497,7 +499,7 @@ export function SessionManagerPage() {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
void handleCopy(
|
void handleCopy(
|
||||||
selectedSession.resumeCommand!,
|
selectedSession.resumeCommand!,
|
||||||
t("sessionManager.resumeCommandCopied")
|
t("sessionManager.resumeCommandCopied"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -559,7 +561,7 @@ export function SessionManagerPage() {
|
|||||||
content,
|
content,
|
||||||
t("sessionManager.messageCopied", {
|
t("sessionManager.messageCopied", {
|
||||||
defaultValue: "已复制消息内容",
|
defaultValue: "已复制消息内容",
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function SessionMessageItem({
|
|||||||
: message.role.toLowerCase() === "assistant"
|
: message.role.toLowerCase() === "assistant"
|
||||||
? "bg-blue-500/5 border-blue-500/20 mr-8"
|
? "bg-blue-500/5 border-blue-500/20 mr-8"
|
||||||
: "bg-muted/40 border-border/60",
|
: "bg-muted/40 border-border/60",
|
||||||
isActive && "ring-2 ring-primary ring-offset-2"
|
isActive && "ring-2 ring-primary ring-offset-2",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function SessionTocSidebar({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-full text-left px-2 py-1.5 rounded text-xs transition-colors",
|
"w-full text-left px-2 py-1.5 rounded text-xs transition-colors",
|
||||||
"hover:bg-muted/80 text-muted-foreground hover:text-foreground",
|
"hover:bg-muted/80 text-muted-foreground hover:text-foreground",
|
||||||
"flex items-start gap-2"
|
"flex items-start gap-2",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="shrink-0 w-4 h-4 rounded-full bg-primary/10 text-primary text-[10px] flex items-center justify-center font-medium">
|
<span className="shrink-0 w-4 h-4 rounded-full bg-primary/10 text-primary text-[10px] flex items-center justify-center font-medium">
|
||||||
@@ -118,7 +118,7 @@ export function SessionTocDialog({
|
|||||||
"w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all",
|
"w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all",
|
||||||
"hover:bg-primary/10 text-foreground",
|
"hover:bg-primary/10 text-foreground",
|
||||||
"flex items-start gap-3",
|
"flex items-start gap-3",
|
||||||
"focus:outline-none focus:ring-2 focus:ring-primary focus:ring-inset"
|
"focus:outline-none focus:ring-2 focus:ring-primary focus:ring-inset",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="shrink-0 w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs flex items-center justify-center font-semibold">
|
<span className="shrink-0 w-6 h-6 rounded-full bg-primary text-primary-foreground text-xs flex items-center justify-center font-semibold">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const formatTimestamp = (value?: number) => {
|
|||||||
|
|
||||||
export const formatRelativeTime = (
|
export const formatRelativeTime = (
|
||||||
value: number | undefined,
|
value: number | undefined,
|
||||||
t: (key: string, options?: Record<string, unknown>) => string
|
t: (key: string, options?: Record<string, unknown>) => string,
|
||||||
) => {
|
) => {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -37,7 +37,7 @@ export const formatRelativeTime = (
|
|||||||
|
|
||||||
export const getProviderLabel = (
|
export const getProviderLabel = (
|
||||||
providerId: string,
|
providerId: string,
|
||||||
t: (key: string) => string
|
t: (key: string) => string,
|
||||||
) => {
|
) => {
|
||||||
const key = `apps.${providerId}`;
|
const key = `apps.${providerId}`;
|
||||||
const translated = t(key);
|
const translated = t(key);
|
||||||
@@ -60,10 +60,7 @@ export const getRoleTone = (role: string) => {
|
|||||||
return "text-muted-foreground";
|
return "text-muted-foreground";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRoleLabel = (
|
export const getRoleLabel = (role: string, t: (key: string) => string) => {
|
||||||
role: string,
|
|
||||||
t: (key: string) => string
|
|
||||||
) => {
|
|
||||||
const normalized = role.toLowerCase();
|
const normalized = role.toLowerCase();
|
||||||
if (normalized === "assistant") return "AI";
|
if (normalized === "assistant") return "AI";
|
||||||
if (normalized === "user") return t("sessionManager.roleUser");
|
if (normalized === "user") return t("sessionManager.roleUser");
|
||||||
|
|||||||
@@ -63,11 +63,7 @@ const UnifiedSkillsPanel = React.forwardRef<
|
|||||||
return counts;
|
return counts;
|
||||||
}, [skills]);
|
}, [skills]);
|
||||||
|
|
||||||
const handleToggleApp = async (
|
const handleToggleApp = async (id: string, app: AppId, enabled: boolean) => {
|
||||||
id: string,
|
|
||||||
app: AppId,
|
|
||||||
enabled: boolean,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
await toggleAppMutation.mutateAsync({ id, app, enabled });
|
await toggleAppMutation.mutateAsync({ id, app, enabled });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -257,7 +253,9 @@ const InstalledSkillListItem: React.FC<InstalledSkillListItemProps> = ({
|
|||||||
<ListItemRow isLast={isLast}>
|
<ListItemRow isLast={isLast}>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="font-medium text-sm text-foreground truncate">{skill.name}</span>
|
<span className="font-medium text-sm text-foreground truncate">
|
||||||
|
{skill.name}
|
||||||
|
</span>
|
||||||
{skill.readmeUrl && (
|
{skill.readmeUrl && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -267,10 +265,15 @@ const InstalledSkillListItem: React.FC<InstalledSkillListItemProps> = ({
|
|||||||
<ExternalLink size={12} />
|
<ExternalLink size={12} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-muted-foreground/50 flex-shrink-0">{sourceLabel}</span>
|
<span className="text-xs text-muted-foreground/50 flex-shrink-0">
|
||||||
|
{sourceLabel}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{skill.description && (
|
{skill.description && (
|
||||||
<p className="text-xs text-muted-foreground truncate" title={skill.description}>
|
<p
|
||||||
|
className="text-xs text-muted-foreground truncate"
|
||||||
|
title={skill.description}
|
||||||
|
>
|
||||||
{skill.description}
|
{skill.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const ScrollBar = React.forwardRef<
|
|||||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,25 +16,40 @@ export const APP_ICON_MAP: Record<AppId, AppConfig> = {
|
|||||||
claude: {
|
claude: {
|
||||||
label: "Claude",
|
label: "Claude",
|
||||||
icon: <ClaudeIcon size={14} />,
|
icon: <ClaudeIcon size={14} />,
|
||||||
activeClass: "bg-orange-500/10 ring-1 ring-orange-500/20 hover:bg-orange-500/20 text-orange-600 dark:text-orange-400",
|
activeClass:
|
||||||
badgeClass: "bg-orange-500/10 text-orange-700 dark:text-orange-300 hover:bg-orange-500/20 border-0 gap-1.5",
|
"bg-orange-500/10 ring-1 ring-orange-500/20 hover:bg-orange-500/20 text-orange-600 dark:text-orange-400",
|
||||||
|
badgeClass:
|
||||||
|
"bg-orange-500/10 text-orange-700 dark:text-orange-300 hover:bg-orange-500/20 border-0 gap-1.5",
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
label: "Codex",
|
label: "Codex",
|
||||||
icon: <CodexIcon size={14} />,
|
icon: <CodexIcon size={14} />,
|
||||||
activeClass: "bg-green-500/10 ring-1 ring-green-500/20 hover:bg-green-500/20 text-green-600 dark:text-green-400",
|
activeClass:
|
||||||
badgeClass: "bg-green-500/10 text-green-700 dark:text-green-300 hover:bg-green-500/20 border-0 gap-1.5",
|
"bg-green-500/10 ring-1 ring-green-500/20 hover:bg-green-500/20 text-green-600 dark:text-green-400",
|
||||||
|
badgeClass:
|
||||||
|
"bg-green-500/10 text-green-700 dark:text-green-300 hover:bg-green-500/20 border-0 gap-1.5",
|
||||||
},
|
},
|
||||||
gemini: {
|
gemini: {
|
||||||
label: "Gemini",
|
label: "Gemini",
|
||||||
icon: <GeminiIcon size={14} />,
|
icon: <GeminiIcon size={14} />,
|
||||||
activeClass: "bg-blue-500/10 ring-1 ring-blue-500/20 hover:bg-blue-500/20 text-blue-600 dark:text-blue-400",
|
activeClass:
|
||||||
badgeClass: "bg-blue-500/10 text-blue-700 dark:text-blue-300 hover:bg-blue-500/20 border-0 gap-1.5",
|
"bg-blue-500/10 ring-1 ring-blue-500/20 hover:bg-blue-500/20 text-blue-600 dark:text-blue-400",
|
||||||
|
badgeClass:
|
||||||
|
"bg-blue-500/10 text-blue-700 dark:text-blue-300 hover:bg-blue-500/20 border-0 gap-1.5",
|
||||||
},
|
},
|
||||||
opencode: {
|
opencode: {
|
||||||
label: "OpenCode",
|
label: "OpenCode",
|
||||||
icon: <ProviderIcon icon="opencode" name="OpenCode" size={14} showFallback={false} />,
|
icon: (
|
||||||
activeClass: "bg-indigo-500/10 ring-1 ring-indigo-500/20 hover:bg-indigo-500/20 text-indigo-600 dark:text-indigo-400",
|
<ProviderIcon
|
||||||
badgeClass: "bg-indigo-500/10 text-indigo-700 dark:text-indigo-300 hover:bg-indigo-500/20 border-0 gap-1.5",
|
icon="opencode"
|
||||||
|
name="OpenCode"
|
||||||
|
size={14}
|
||||||
|
showFallback={false}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
activeClass:
|
||||||
|
"bg-indigo-500/10 ring-1 ring-indigo-500/20 hover:bg-indigo-500/20 text-indigo-600 dark:text-indigo-400",
|
||||||
|
badgeClass:
|
||||||
|
"bg-indigo-500/10 text-indigo-700 dark:text-indigo-300 hover:bg-indigo-500/20 border-0 gap-1.5",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function useSessionSearch({
|
|||||||
.filter(
|
.filter(
|
||||||
(session) =>
|
(session) =>
|
||||||
session &&
|
session &&
|
||||||
(providerFilter === "all" || session.providerId === providerFilter)
|
(providerFilter === "all" || session.providerId === providerFilter),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 按时间排序
|
// 按时间排序
|
||||||
@@ -127,7 +127,7 @@ export function useSessionSearch({
|
|||||||
return bTs - aTs;
|
return bTs - aTs;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[sessions, providerFilter]
|
[sessions, providerFilter],
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMemo(() => ({ search, isIndexing }), [search, isIndexing]);
|
return useMemo(() => ({ search, isIndexing }), [search, isIndexing]);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const sessionsApi = {
|
|||||||
|
|
||||||
async getMessages(
|
async getMessages(
|
||||||
providerId: string,
|
providerId: string,
|
||||||
sourcePath: string
|
sourcePath: string,
|
||||||
): Promise<SessionMessage[]> {
|
): Promise<SessionMessage[]> {
|
||||||
return await invoke("get_session_messages", { providerId, sourcePath });
|
return await invoke("get_session_messages", { providerId, sourcePath });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ export const skillsApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/** 切换 Skill 的应用启用状态 */
|
/** 切换 Skill 的应用启用状态 */
|
||||||
async toggleApp(
|
async toggleApp(id: string, app: AppId, enabled: boolean): Promise<boolean> {
|
||||||
id: string,
|
|
||||||
app: AppId,
|
|
||||||
enabled: boolean,
|
|
||||||
): Promise<boolean> {
|
|
||||||
return await invoke("toggle_skill_app", { id, app, enabled });
|
return await invoke("toggle_skill_app", { id, app, enabled });
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,10 +129,7 @@ export const skillsApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/** 卸载技能(兼容旧 API) */
|
/** 卸载技能(兼容旧 API) */
|
||||||
async uninstall(
|
async uninstall(directory: string, app: AppId = "claude"): Promise<boolean> {
|
||||||
directory: string,
|
|
||||||
app: AppId = "claude",
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (app === "claude") {
|
if (app === "claude") {
|
||||||
return await invoke("uninstall_skill", { directory });
|
return await invoke("uninstall_skill", { directory });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user