Compare commits

..

10 Commits

Author SHA1 Message Date
YoVinchen 0cf9627e8b fix(skill): correct skill doc URL branch and path resolution
Use the actual branch returned by download_repo instead of the
configured branch, fixing 404s when repos default to master but
the URL was hardcoded to main. Also switch URL format from /tree/
to /blob/ and always point to the SKILL.md file.

Closes farion1231/cc-switch#968
2026-02-09 11:25:22 +08:00
makoMako 5502c74a79 feat: rename Qwen Coder preset to Bailian (#965)
- replace Qwen Coder preset with Bailian for Claude and OpenCode
- add Bailian icon asset and metadata mapping
- remove Bailian preset default model values
- rename Claude preset Kimi k2 to Kimi
2026-02-08 23:32:40 +08:00
myjustify fa7c9514cc feat(env): add Volta package manager path detection (#969)
Add ~/.volta/bin to CLI version scanning paths to support tools
installed via Volta (claude, codex, gemini, etc.).
2026-02-08 23:30:53 +08:00
Dex Miller d82027f107 feat(pricing): add claude-opus-4-6 and gpt-5.3-codex models, use incremental seeding (#943)
- Add claude-opus-4-6-20260206 pricing (same as opus-4-5)
- Add gpt-5.3-codex series pricing (same as gpt-5.2-codex)
- Change seed_model_pricing to INSERT OR IGNORE for incremental upsert
- Remove count==0 guard in ensure_model_pricing_seeded so new models
  are appended on every startup without overwriting user customizations
2026-02-07 11:06:48 +08:00
Jason ded9980fbf fix(provider): remove /v1 suffix from AIGoCode OpenCode base URL 2026-02-06 22:39:07 +08:00
Jason 3d91c381d9 fix(provider): unify AIGoCode API base URLs to https://api.aigocode.com 2026-02-06 22:39:07 +08:00
funnytime b8538a6996 feat: circular reveal animation for theme switching (#905) 2026-02-06 22:14:29 +08:00
Dex Miller 87b80c66b2 feat(usage): enhance dashboard with auto-refresh control and robust formatting (#942)
* style: format code and apply clippy lint fixes

* feat(usage): enhance dashboard with auto-refresh control and robust formatting

- Add configurable auto-refresh interval toggle (off/5s/10s/30s/60s) to usage dashboard
- Extract shared format utilities (fmtUsd, fmtInt, parseFiniteNumber, getLocaleFromLanguage)
- Refactor request log time filtering to rolling vs fixed mode with validation
- Use stable serializable query keys instead of filter objects
- Handle NaN/Infinity safely in number formatting across all usage components
- Use RFC 3339 date format in backend trend data
2026-02-06 22:00:33 +08:00
Dex Miller 14fa749ca9 fix(opencode): reject save when no models configured (#932)
Add validation to require at least one model before saving an OpenCode
provider. Shows a localized toast error when models are empty.
2026-02-06 16:03:39 +08:00
Dex Miller 95bc0e38df style: format code and apply clippy lint fixes (#941) 2026-02-06 16:02:57 +08:00
19 changed files with 257 additions and 71 deletions
+1
View File
@@ -324,6 +324,7 @@ fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
home.join(".local/bin"), // Native install (official recommended)
home.join(".npm-global/bin"),
home.join("n/bin"), // n version manager
home.join(".volta/bin"), // Volta package manager
];
#[cfg(target_os = "macos")]
+47 -10
View File
@@ -919,7 +919,16 @@ impl Database {
/// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致
fn seed_model_pricing(conn: &Connection) -> Result<(), AppError> {
let pricing_data = [
// Claude 4.5 系列 (Latest Models)
// Claude 4.6 系列
(
"claude-opus-4-6-20260206",
"Claude Opus 4.6",
"5",
"25",
"0.50",
"6.25",
),
// Claude 4.5 系列
(
"claude-opus-4-5-20251101",
"Claude Opus 4.5",
@@ -1025,6 +1034,40 @@ impl Database {
"0.175",
"0",
),
// GPT-5.3 Codex 系列
("gpt-5.3-codex", "GPT-5.3 Codex", "1.75", "14", "0.175", "0"),
(
"gpt-5.3-codex-low",
"GPT-5.3 Codex",
"1.75",
"14",
"0.175",
"0",
),
(
"gpt-5.3-codex-medium",
"GPT-5.3 Codex",
"1.75",
"14",
"0.175",
"0",
),
(
"gpt-5.3-codex-high",
"GPT-5.3 Codex",
"1.75",
"14",
"0.175",
"0",
),
(
"gpt-5.3-codex-xhigh",
"GPT-5.3 Codex",
"1.75",
"14",
"0.175",
"0",
),
// GPT-5.1 系列
("gpt-5.1", "GPT-5.1", "1.25", "10", "0.125", "0"),
("gpt-5.1-low", "GPT-5.1", "1.25", "10", "0.125", "0"),
@@ -1212,7 +1255,7 @@ impl Database {
for (model_id, display_name, input, output, cache_read, cache_creation) in pricing_data {
conn.execute(
"INSERT OR REPLACE INTO model_pricing (
"INSERT OR IGNORE INTO model_pricing (
model_id, display_name, input_cost_per_million, output_cost_per_million,
cache_read_cost_per_million, cache_creation_cost_per_million
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
@@ -1239,14 +1282,8 @@ impl Database {
}
fn ensure_model_pricing_seeded_on_conn(conn: &Connection) -> Result<(), AppError> {
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM model_pricing", [], |row| row.get(0))
.map_err(|e| AppError::Database(format!("统计模型定价数据失败: {e}")))?;
if count == 0 {
Self::seed_model_pricing(conn)?;
}
Ok(())
// 每次启动都执行 INSERT OR IGNORE,增量追加新模型,已有数据不覆盖
Self::seed_model_pricing(conn)
}
// --- 辅助方法 ---
+104 -29
View File
@@ -174,6 +174,29 @@ impl SkillService {
Self
}
/// 构建 Skill 文档 URL(指向仓库中的 SKILL.md 文件)
fn build_skill_doc_url(owner: &str, repo: &str, branch: &str, doc_path: &str) -> String {
format!("https://github.com/{owner}/{repo}/blob/{branch}/{doc_path}")
}
/// 从旧 readme_url 中提取仓库内文档路径,兼容 `blob`/`tree` 两种格式
fn extract_doc_path_from_url(url: &str) -> Option<String> {
let marker = if url.contains("/blob/") {
"/blob/"
} else if url.contains("/tree/") {
"/tree/"
} else {
return None;
};
let (_, tail) = url.split_once(marker)?;
let (_, path) = tail.split_once('/')?;
if path.is_empty() {
return None;
}
Some(path.to_string())
}
// ========== 路径管理 ==========
/// 获取 SSOT 目录(~/.cc-switch/skills/
@@ -298,6 +321,8 @@ impl SkillService {
let dest = ssot_dir.join(&install_name);
let mut repo_branch = skill.repo_branch.clone();
// 如果已存在则跳过下载
if !dest.exists() {
let repo = SkillRepo {
@@ -308,7 +333,7 @@ impl SkillService {
};
// 下载仓库
let temp_dir = timeout(
let (temp_dir, used_branch) = timeout(
std::time::Duration::from_secs(60),
self.download_repo(&repo),
)
@@ -324,6 +349,7 @@ impl SkillService {
Some("checkNetwork"),
))
})??;
repo_branch = used_branch;
// 复制到 SSOT
let source = temp_dir.join(&skill.directory);
@@ -338,8 +364,39 @@ impl SkillService {
Self::copy_dir_recursive(&source, &dest)?;
let _ = fs::remove_dir_all(&temp_dir);
// 使用实际下载成功的分支,避免 readme_url / repo_branch 与真实分支不一致。
if repo_branch != skill.repo_branch {
log::info!(
"Skill {}/{} 分支自动回退: {} -> {}",
skill.repo_owner,
skill.repo_name,
skill.repo_branch,
repo_branch
);
}
}
let doc_path = skill
.readme_url
.as_deref()
.and_then(Self::extract_doc_path_from_url)
.map(|path| {
if path.ends_with("/SKILL.md") || path == "SKILL.md" {
path
} else {
format!("{}/SKILL.md", path.trim_end_matches('/'))
}
})
.unwrap_or_else(|| format!("{}/SKILL.md", skill.directory.trim_end_matches('/')));
let readme_url = Some(Self::build_skill_doc_url(
&skill.repo_owner,
&skill.repo_name,
&repo_branch,
&doc_path,
));
// 创建 InstalledSkill 记录
let installed_skill = InstalledSkill {
id: skill.key.clone(),
@@ -352,8 +409,8 @@ impl SkillService {
directory: install_name.clone(),
repo_owner: Some(skill.repo_owner.clone()),
repo_name: Some(skill.repo_name.clone()),
repo_branch: Some(skill.repo_branch.clone()),
readme_url: skill.readme_url.clone(),
repo_branch: Some(repo_branch),
readme_url,
apps: SkillApps::only(current_app),
installed_at: chrono::Utc::now().timestamp(),
};
@@ -862,24 +919,26 @@ impl SkillService {
/// 从仓库获取技能列表
async fn fetch_repo_skills(&self, repo: &SkillRepo) -> Result<Vec<DiscoverableSkill>> {
let temp_dir = timeout(std::time::Duration::from_secs(60), self.download_repo(repo))
.await
.map_err(|_| {
anyhow!(format_skill_error(
"DOWNLOAD_TIMEOUT",
&[
("owner", &repo.owner),
("name", &repo.name),
("timeout", "60")
],
Some("checkNetwork"),
))
})??;
let (temp_dir, resolved_branch) =
timeout(std::time::Duration::from_secs(60), self.download_repo(repo))
.await
.map_err(|_| {
anyhow!(format_skill_error(
"DOWNLOAD_TIMEOUT",
&[
("owner", &repo.owner),
("name", &repo.name),
("timeout", "60")
],
Some("checkNetwork"),
))
})??;
let mut skills = Vec::new();
let scan_dir = temp_dir.clone();
self.scan_dir_recursive(&scan_dir, &scan_dir, repo, &mut skills)?;
let mut resolved_repo = repo.clone();
resolved_repo.branch = resolved_branch;
self.scan_dir_recursive(&scan_dir, &scan_dir, &resolved_repo, &mut skills)?;
let _ = fs::remove_dir_all(&temp_dir);
@@ -907,7 +966,15 @@ impl SkillService {
.to_string()
};
if let Ok(skill) = self.build_skill_from_metadata(&skill_md, &directory, repo) {
let doc_path = skill_md
.strip_prefix(base_dir)
.unwrap_or(skill_md.as_path())
.to_string_lossy()
.replace('\\', "/");
if let Ok(skill) =
self.build_skill_from_metadata(&skill_md, &directory, &doc_path, repo)
{
skills.push(skill);
}
@@ -931,6 +998,7 @@ impl SkillService {
&self,
skill_md: &Path,
directory: &str,
doc_path: &str,
repo: &SkillRepo,
) -> Result<DiscoverableSkill> {
let meta = self.parse_skill_metadata(skill_md)?;
@@ -940,9 +1008,11 @@ impl SkillService {
name: meta.name.unwrap_or_else(|| directory.to_string()),
description: meta.description.unwrap_or_default(),
directory: directory.to_string(),
readme_url: Some(format!(
"https://github.com/{}/{}/tree/{}/{}",
repo.owner, repo.name, repo.branch, directory
readme_url: Some(Self::build_skill_doc_url(
&repo.owner,
&repo.name,
&repo.branch,
doc_path,
)),
repo_owner: repo.owner.clone(),
repo_name: repo.name.clone(),
@@ -994,16 +1064,21 @@ impl SkillService {
}
/// 下载仓库
async fn download_repo(&self, repo: &SkillRepo) -> Result<PathBuf> {
async fn download_repo(&self, repo: &SkillRepo) -> Result<(PathBuf, String)> {
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path().to_path_buf();
let _ = temp_dir.keep();
let branches = if repo.branch.is_empty() {
vec!["main", "master"]
} else {
vec![repo.branch.as_str(), "main", "master"]
};
let mut branches = Vec::new();
if !repo.branch.is_empty() {
branches.push(repo.branch.as_str());
}
if !branches.contains(&"main") {
branches.push("main");
}
if !branches.contains(&"master") {
branches.push("master");
}
let mut last_error = None;
for branch in branches {
@@ -1014,7 +1089,7 @@ impl SkillService {
match self.download_and_extract(&url, &temp_path).await {
Ok(_) => {
return Ok(temp_path);
return Ok((temp_path, branch.to_string()));
}
Err(e) => {
last_error = Some(e);
+3 -3
View File
@@ -7,13 +7,13 @@ export function ModeToggle() {
const { theme, setTheme } = useTheme();
const { t } = useTranslation();
const toggleTheme = () => {
const toggleTheme = (event: React.MouseEvent) => {
// 如果当前是 dark 或 system(且系统是暗色),切换到 light
// 否则切换到 dark
if (theme === "dark") {
setTheme("light");
setTheme("light", event);
} else {
setTheme("dark");
setTheme("dark", event);
}
};
@@ -746,7 +746,7 @@ export function ProviderForm({
return;
}
// OpenCode: validate provider key
// OpenCode: validate provider key and models
if (appId === "opencode") {
const keyPattern = /^[a-z0-9]+(-[a-z0-9]+)*$/;
if (!opencodeProviderKey.trim()) {
@@ -761,6 +761,11 @@ export function ProviderForm({
toast.error(t("opencode.providerKeyDuplicate"));
return;
}
// Validate that at least one model is configured
if (Object.keys(opencodeModels).length === 0) {
toast.error(t("opencode.modelsRequired"));
return;
}
}
// 非官方供应商必填校验:端点和 API Key
+4 -4
View File
@@ -19,21 +19,21 @@ export function ThemeSettings() {
<div className="inline-flex gap-1 rounded-md border border-border-default bg-background p-1">
<ThemeButton
active={theme === "light"}
onClick={() => setTheme("light")}
onClick={(e) => setTheme("light", e)}
icon={Sun}
>
{t("settings.themeLight")}
</ThemeButton>
<ThemeButton
active={theme === "dark"}
onClick={() => setTheme("dark")}
onClick={(e) => setTheme("dark", e)}
icon={Moon}
>
{t("settings.themeDark")}
</ThemeButton>
<ThemeButton
active={theme === "system"}
onClick={() => setTheme("system")}
onClick={(e) => setTheme("system", e)}
icon={Monitor}
>
{t("settings.themeSystem")}
@@ -45,7 +45,7 @@ export function ThemeSettings() {
interface ThemeButtonProps {
active: boolean;
onClick: () => void;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
icon: React.ComponentType<{ className?: string }>;
children: React.ReactNode;
}
+25 -3
View File
@@ -17,7 +17,7 @@ interface ThemeProviderProps {
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: Theme) => void;
setTheme: (theme: Theme, event?: React.MouseEvent) => void;
}
const ThemeProviderContext = createContext<ThemeContextValue | undefined>(
@@ -146,8 +146,30 @@ export function ThemeProvider({
const value = useMemo<ThemeContextValue>(
() => ({
theme,
setTheme: (nextTheme: Theme) => {
setThemeState(nextTheme);
setTheme: (nextTheme: Theme, event?: React.MouseEvent) => {
// Skip if same theme
if (nextTheme === theme) return;
// Set transition origin coordinates from click event
const x = event?.clientX ?? window.innerWidth / 2;
const y = event?.clientY ?? window.innerHeight / 2;
document.documentElement.style.setProperty(
"--theme-transition-x",
`${x}px`,
);
document.documentElement.style.setProperty(
"--theme-transition-y",
`${y}px`,
);
// Use View Transitions API if available, otherwise fall back to instant change
if (document.startViewTransition) {
document.startViewTransition(() => {
setThemeState(nextTheme);
});
} else {
setThemeState(nextTheme);
}
},
}),
[theme],
+5 -9
View File
@@ -125,24 +125,20 @@ export const providerPresets: ProviderPreset[] = [
iconColor: "#0F62FE",
},
{
name: "Qwen Coder",
name: "Bailian",
websiteUrl: "https://bailian.console.aliyun.com",
settingsConfig: {
env: {
ANTHROPIC_BASE_URL: "https://dashscope.aliyuncs.com/apps/anthropic",
ANTHROPIC_AUTH_TOKEN: "",
ANTHROPIC_MODEL: "qwen3-max",
ANTHROPIC_DEFAULT_HAIKU_MODEL: "qwen3-max",
ANTHROPIC_DEFAULT_SONNET_MODEL: "qwen3-max",
ANTHROPIC_DEFAULT_OPUS_MODEL: "qwen3-max",
},
},
category: "cn_official",
icon: "qwen",
iconColor: "#FF6A00",
icon: "bailian",
iconColor: "#624AFF",
},
{
name: "Kimi k2",
name: "Kimi",
websiteUrl: "https://platform.moonshot.cn/console",
settingsConfig: {
env: {
@@ -438,7 +434,7 @@ export const providerPresets: ProviderPreset[] = [
apiKeyUrl: "https://aigocode.com/invite/CC-SWITCH",
settingsConfig: {
env: {
ANTHROPIC_BASE_URL: "https://api.aigocode.com/api",
ANTHROPIC_BASE_URL: "https://api.aigocode.com",
ANTHROPIC_AUTH_TOKEN: "",
},
},
+1 -1
View File
@@ -183,7 +183,7 @@ requires_openai_auth = true`,
auth: generateThirdPartyAuth(""),
config: generateThirdPartyConfig(
"aigocode",
"https://api.aigocode.com/openai",
"https://api.aigocode.com",
"gpt-5.2",
),
endpointCandidates: ["https://api.aigocode.com"],
+3 -3
View File
@@ -102,17 +102,17 @@ export const geminiProviderPresets: GeminiProviderPreset[] = [
apiKeyUrl: "https://aigocode.com/invite/CC-SWITCH",
settingsConfig: {
env: {
GOOGLE_GEMINI_BASE_URL: "https://api.aigocode.com/gemini",
GOOGLE_GEMINI_BASE_URL: "https://api.aigocode.com",
GEMINI_MODEL: "gemini-3-pro",
},
},
baseURL: "https://api.aigocode.com/gemini",
baseURL: "https://api.aigocode.com",
model: "gemini-3-pro",
description: "AIGoCode",
category: "third_party",
isPartner: true,
partnerPromotionKey: "aigocode",
endpointCandidates: ["https://api.aigocode.com/gemini"],
endpointCandidates: ["https://api.aigocode.com"],
icon: "aigocode",
iconColor: "#5B7FFF",
},
+1
View File
@@ -10,6 +10,7 @@ const iconMappings = {
zhipu: { icon: "zhipu", iconColor: "#0F62FE" },
glm: { icon: "zhipu", iconColor: "#0F62FE" },
qwen: { icon: "qwen", iconColor: "#FF6A00" },
bailian: { icon: "bailian", iconColor: "#624AFF" },
alibaba: { icon: "alibaba", iconColor: "#FF6A00" },
aliyun: { icon: "alibaba", iconColor: "#FF6A00" },
kimi: { icon: "kimi", iconColor: "#6366F1" },
+6 -8
View File
@@ -137,23 +137,21 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
},
},
{
name: "Qwen Coder",
name: "Bailian",
websiteUrl: "https://bailian.console.aliyun.com",
apiKeyUrl: "https://bailian.console.aliyun.com/#/api-key",
settingsConfig: {
npm: "@ai-sdk/openai-compatible",
name: "Qwen Coder",
name: "Bailian",
options: {
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
apiKey: "",
},
models: {
"qwen3-max": { name: "Qwen3 Max" },
},
models: {},
},
category: "cn_official",
icon: "qwen",
iconColor: "#FF6A00",
icon: "bailian",
iconColor: "#624AFF",
templateValues: {
baseURL: {
label: "Base URL",
@@ -651,7 +649,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
npm: "@ai-sdk/anthropic",
name: "AIGoCode",
options: {
baseURL: "https://api.aigocode.com/v1",
baseURL: "https://api.aigocode.com",
apiKey: "",
},
models: {
+1
View File
@@ -633,6 +633,7 @@
"modelId": "Model ID",
"modelName": "Display Name",
"noModels": "No models configured",
"modelsRequired": "Please add at least one model",
"providerKey": "Provider Key",
"providerKeyPlaceholder": "my-provider",
"providerKeyHint": "Unique identifier in config file. Cannot be changed after creation. Use lowercase letters, numbers, and hyphens only.",
+1
View File
@@ -633,6 +633,7 @@
"modelId": "モデル ID",
"modelName": "表示名",
"noModels": "モデルが設定されていません",
"modelsRequired": "モデルを少なくとも1つ追加してください",
"providerKey": "プロバイダーキー",
"providerKeyPlaceholder": "my-provider",
"providerKeyHint": "設定ファイルの一意の識別子。作成後は変更できません。小文字、数字、ハイフンのみ使用できます。",
+1
View File
@@ -633,6 +633,7 @@
"modelId": "模型 ID",
"modelName": "显示名称",
"noModels": "暂无模型配置",
"modelsRequired": "请至少添加一个模型配置",
"providerKey": "供应商标识",
"providerKeyPlaceholder": "my-provider",
"providerKeyHint": "配置文件中的唯一标识符,创建后无法修改,只能使用小写字母、数字和连字符",
+1
View File
@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>BaiLian</title><path d="M6.336 8.919v6.162l5.335-3.083L6.337 8.92z" fill-opacity=".4"></path><path d="M21.394 5.288s-.006-.006-.01-.006L17.01 2.754 6.336 8.92l5.335 3.082 9.701-5.6.016-.01a.635.635 0 00.006-1.1v-.003z" fill-opacity=".8"></path><path d="M21.71 12.465a.62.62 0 00-.316.085s-.006 0-.009.003l-4.375 2.528 5.05 2.915h.006a2.06 2.06 0 00.28-1.04v-3.855a.637.637 0 00-.636-.636z"></path><path d="M22.06 17.996l-5.05-2.915L6.34 21.242l4.27 2.465s.016.006.022.012a2.102 2.102 0 002.093 0c.006-.003.016-.006.022-.012l8.538-4.93c.003 0 .006-.003.01-.006.321-.183.589-.45.775-.772h-.006l-.004-.003z" fill-opacity=".8"></path><path d="M11.672 11.998l-5.336 3.083-1.444.832-3.605 2.083H1.28c.173.303.416.555.709.738l.078.044.016.01.02.012 4.232 2.442 10.671-6.161-5.335-3.082z"></path><path d="M12.74.29c-.1-.06-.208-.107-.315-.148-.02-.006-.038-.016-.057-.022a2.121 2.121 0 00-.7-.12c-.233 0-.457.038-.668.11l-.031.01a2.196 2.196 0 00-.372.17L2.068 5.222s-.003 0-.006.003c-.324.183-.592.451-.781.773h.006l5.049 2.918L17.01 2.758 12.74.29z" fill-opacity=".6"></path><path d="M1.287 6.001H1.28A2.06 2.06 0 001 7.041v9.915c0 .378.1.735.28 1.043h.007l5.049-2.918V8.919l-5.05-2.918z" fill-opacity=".3"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -60,6 +60,7 @@ export const icons: Record<string, string> = {
catcoder: `<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>KwaiKAT</title><path d="M20.42 19.311h3.418V1l-6.781 4.177-6.778-4.111.026 7.868h3.418l-.026-2.222 3.42 2.035 3.303-2.035v12.6z"></path><path d="M3.064 10.734c2.784-2.07 6.942-2.394 9.941.907l.01.01.01.013 9.16 12.24h-3.84l-7.69-10.217c-1.63-1.737-3.891-1.689-5.515-.638-1.624 1.05-2.563 3.073-1.548 5.28 1.494 3.246 6.152 3.275 7.725.108l.032-.064 2.02 2.629c-2.98 3.968-9.329 3.926-12.165-.552-2.395-3.78-.926-7.645 1.86-9.716z"></path></svg>`,
mcp: `<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>ModelContextProtocol</title><path d="M15.688 2.343a2.588 2.588 0 00-3.61 0l-9.626 9.44a.863.863 0 01-1.203 0 .823.823 0 010-1.18l9.626-9.44a4.313 4.313 0 016.016 0 4.116 4.116 0 011.204 3.54 4.3 4.3 0 013.609 1.18l.05.05a4.115 4.115 0 010 5.9l-8.706 8.537a.274.274 0 000 .393l1.788 1.754a.823.823 0 010 1.18.863.863 0 01-1.203 0l-1.788-1.753a1.92 1.92 0 010-2.754l8.706-8.538a2.47 2.47 0 000-3.54l-.05-.049a2.588 2.588 0 00-3.607-.003l-7.172 7.034-.002.002-.098.097a.863.863 0 01-1.204 0 .823.823 0 010-1.18l7.273-7.133a2.47 2.47 0 00-.003-3.537z"></path><path d="M14.485 4.703a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a4.115 4.115 0 000 5.9 4.314 4.314 0 006.016 0l7.12-6.982a.823.823 0 000-1.18.863.863 0 00-1.204 0l-7.119 6.982a2.588 2.588 0 01-3.61 0 2.47 2.47 0 010-3.54l7.12-6.982z"></path></svg>`,
nvidia: `<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Nvidia</title><path d="M10.212 8.976V7.62c.127-.01.256-.017.388-.021 3.596-.117 5.957 3.184 5.957 3.184s-2.548 3.647-5.282 3.647a3.227 3.227 0 01-1.063-.175v-4.109c1.4.174 1.681.812 2.523 2.258l1.873-1.627a4.905 4.905 0 00-3.67-1.846 6.594 6.594 0 00-.729.044m0-4.476v2.025c.13-.01.259-.019.388-.024 5.002-.174 8.261 4.226 8.261 4.226s-3.743 4.69-7.643 4.69c-.338 0-.675-.031-1.007-.092v1.25c.278.038.558.057.838.057 3.629 0 6.253-1.91 8.794-4.169.421.347 2.146 1.193 2.501 1.564-2.416 2.083-8.048 3.763-11.24 3.763-.308 0-.603-.02-.894-.048V19.5H24v-15H10.21zm0 9.756v1.068c-3.356-.616-4.287-4.21-4.287-4.21a7.173 7.173 0 014.287-2.138v1.172h-.005a3.182 3.182 0 00-2.502 1.178s.615 2.276 2.507 2.931m-5.961-3.3c1.436-1.935 3.604-3.148 5.961-3.336V6.523C5.81 6.887 2 10.723 2 10.723s2.158 6.427 8.21 7.015v-1.166C5.77 16 4.25 10.958 4.25 10.958h-.002z" fill="#74B71B" fill-rule="nonzero"></path></svg>`,
bailian: `<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>BaiLian</title><path d="M6.336 8.919v6.162l5.335-3.083L6.337 8.92z" fill-opacity=".4"></path><path d="M21.394 5.288s-.006-.006-.01-.006L17.01 2.754 6.336 8.92l5.335 3.082 9.701-5.6.016-.01a.635.635 0 00.006-1.1v-.003z" fill-opacity=".8"></path><path d="M21.71 12.465a.62.62 0 00-.316.085s-.006 0-.009.003l-4.375 2.528 5.05 2.915h.006a2.06 2.06 0 00.28-1.04v-3.855a.637.637 0 00-.636-.636z"></path><path d="M22.06 17.996l-5.05-2.915L6.34 21.242l4.27 2.465s.016.006.022.012a2.102 2.102 0 002.093 0c.006-.003.016-.006.022-.012l8.538-4.93c.003 0 .006-.003.01-.006.321-.183.589-.45.775-.772h-.006l-.004-.003z" fill-opacity=".8"></path><path d="M11.672 11.998l-5.336 3.083-1.444.832-3.605 2.083H1.28c.173.303.416.555.709.738l.078.044.016.01.02.012 4.232 2.442 10.671-6.161-5.335-3.082z"></path><path d="M12.74.29c-.1-.06-.208-.107-.315-.148-.02-.006-.038-.016-.057-.022a2.121 2.121 0 00-.7-.12c-.233 0-.457.038-.668.11l-.031.01a2.196 2.196 0 00-.372.17L2.068 5.222s-.003 0-.006.003c-.324.183-.592.451-.781.773h.006l5.049 2.918L17.01 2.758 12.74.29z" fill-opacity=".6"></path><path d="M1.287 6.001H1.28A2.06 2.06 0 001 7.041v9.915c0 .378.1.735.28 1.043h.007l5.049-2.918V8.919l-5.05-2.918z" fill-opacity=".3"></path></svg>`,
};
export const iconList = Object.keys(icons);
+7
View File
@@ -44,6 +44,13 @@ export const iconMetadata: Record<string, IconMetadata> = {
keywords: ["ernie", "wenxin"],
defaultColor: "#2932E1",
},
bailian: {
name: "bailian",
displayName: "Bailian",
category: "ai-provider",
keywords: ["bailian", "dashscope", "aliyun", "alibaba"],
defaultColor: "#624AFF",
},
bytedance: {
name: "bytedance",
displayName: "bytedance",
+39
View File
@@ -215,3 +215,42 @@ input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear {
display: none;
}
/* Theme transition animation using View Transitions API */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
/* Old snapshot stays behind, new snapshot animates on top */
::view-transition-old(root) {
z-index: 1;
}
::view-transition-new(root) {
z-index: 9999;
}
/* Circular expand animation from click position */
@keyframes theme-circle-expand {
from {
clip-path: circle(0% at var(--theme-transition-x, 50%) var(--theme-transition-y, 50%));
}
to {
clip-path: circle(150% at var(--theme-transition-x, 50%) var(--theme-transition-y, 50%));
}
}
/* Apply animation to new snapshot - works for both light and dark transitions */
::view-transition-new(root) {
animation: theme-circle-expand 0.4s ease-out;
}
/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
::view-transition-new(root) {
animation: none;
}
}