style(providers): mark additive default providers in blue

The default provider in OpenCode, OpenClaw, Hermes and Kimi used a gray
card and dot. Use the same blue as an in-use switch-mode card instead,
and drop the now-unused neutral card tone.
This commit is contained in:
Jason committed 2026-10-23 11:15:35 +08:00
1 parent d6a2324196
commit 7cc3141cae
6 files changed
+14 -16

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@ Kimi Code 是独立应用,应用 ID 为 `kimi`。接入面向当前的 [Moonsh
1. 在「应用」页安装 Kimi Code,或先按[官方说明](https://moonshotai.github.io/kimi-code/en/guides/getting-started)安装。CC Switch 使用官方独立程序安装器,自带运行时,不依赖系统 Node 版本;已有客户端升级使用 `kimi upgrade --yes`,静态兜底重跑官方安装器。手动使用 npm 包 `@moonshot-ai/kimi-code` 时需满足其 Node 要求,2.3.0 要求 Node ≥22.19.0,Node 22.12.0 会因缺少 `createZstdDecompress` 无法启动。
2. 在侧栏选择 **Kimi Code**。已有原生供应商和模型会自动出现在列表中。
3. 使用编程套餐时,在「授权中心」的 **Kimi Code** 分组中添加账号,完成授权后自动添加模型;「添加供应商」的「账号登录 → Kimi Code 官方登录」也可进入该分组。支持同时登录多个官方账号。使用 API Key 时,和 Pi 等应用一样,从「模型厂商」「第三方平台」「云服务商」「本地」分组中选择具体供应商,或使用各组共用的「自定义配置」。预设保留厂商的套餐、地区版本、模型参数、请求头和获取 API Key 的链接,并转换为 Kimi 原生字段。「模型厂商」中的 **Kimi** 包含开放平台、编程套餐及国内/海外版本。每个模型必须填写模型 ID 和正整数的上下文长度。
4. 和 Pi、Hermes 一样,卡片分「已添加」「未添加」两组。点击「添加」只将供应商及模型写入 Kimi 配置、不改变当前模型,之后可在 Kimi Code 的 `/model` 中直接选用;多个供应商可以同时添加。已添加卡片「更多」菜单中的「设为默认」会选择该供应商的模型,当前供应商以灰色「默认」标识,与 OpenCode 一致。已有当前模型属于目标供应商时保留该模型,否则选择目标供应商的第一个原生模型;需要切换到其他具体模型时,可在 Kimi Code 内使用 `/model`。没有默认模型时,首次添加会选择该供应商的第一个模型。
4. 和 Pi、Hermes 一样,卡片分「已添加」「未添加」两组。点击「添加」只将供应商及模型写入 Kimi 配置、不改变当前模型,之后可在 Kimi Code 的 `/model` 中直接选用;多个供应商可以同时添加。已添加卡片「更多」菜单中的「设为默认」会选择该供应商的模型,当前供应商以蓝色「默认」标识,与 OpenCode 一致。已有当前模型属于目标供应商时保留该模型,否则选择目标供应商的第一个原生模型;需要切换到其他具体模型时,可在 Kimi Code 内使用 `/model`。没有默认模型时,首次添加会选择该供应商的第一个模型。
5. 已添加卡片上的「移除」会从原生配置移除供应商和所属模型(卡片回到「未添加」),并保留 CC Switch 中的档案。正在使用的供应商、主模型或子代理模型池仍引用的模型不能移除,需要先在 Kimi Code 中调整选择。仍在原生配置中的官方 OAuth 节点禁止通过供应商页编辑、删除和移除,账号的添加、重新登录和删除统一在授权中心操作;原生节点已被 Kimi 移除时,可删除 CC Switch 中留下的档案,独立账号的本机凭据随之删除。
## 配置与能力
+1 -2
View File
@@ -67,12 +67,11 @@ interface ProviderCardProps {
dragHandleProps?: DragHandleProps;
}
/** 当前那张:模式色边框 + 淡底;共存式「已添加」是中性的。 */
/** 当前那张:模式色边框 + 淡底;共存式应用的默认那家同直连色。 */
const TONE_CLASS: Record<CardTone, string> = {
direct: "border-direct-border bg-direct-soft",
route: "border-route-border bg-route-soft",
stack: "border-stack-border bg-stack-soft",
neutral: "border-border-strong bg-subtle",
};
const CHIP_CLASS: Record<CardChip["tone"], string> = {
@@ -47,7 +47,6 @@ const DOT: Record<CardTone | "muted", string> = {
direct: "bg-direct",
route: "bg-route",
stack: "bg-stack",
neutral: "bg-fg-2",
muted: "bg-fg-3",
};
+8 -8
View File
@@ -13,7 +13,7 @@ import {
* 供应商卡片怎么画(v7):当前那张的模式色、状态文字或按钮、徽标。由列表按应用和正在查看的
* 模式算好交给卡片,卡片本身不再判断模式。
*/
export type CardTone = "direct" | "route" | "stack" | "neutral";
export type CardTone = "direct" | "route" | "stack";
export type ChipTone =
| "outline"
@@ -58,7 +58,7 @@ export interface CardButton {
}
export interface CardPresentation {
/** 当前那张:模式色边框 + 淡底;neutral(灰底)只给共存式应用的默认那家(OpenClaw 默认、Hermes 当前) */
/** 当前那张:模式色边框 + 淡底;共存式应用的默认那家(OpenClaw / OpenCode / Kimi 默认、Hermes 当前)也用 direct */
tone?: CardTone;
/** 主操作位换成状态文字(使用中 / 路由中 / 当前默认 / 已添加…) */
status?: { label: string; dot: CardTone | "muted" };
@@ -763,9 +763,9 @@ export function buildAdditiveSections(input: AdditiveInput): ProviderSection[] {
(live ? added : available).push({
provider: p,
presentation: {
tone: current ? "neutral" : undefined,
tone: current ? "direct" : undefined,
status: current
? { label: t("providerCard.status.default"), dot: "muted" }
? { label: t("providerCard.status.default"), dot: "direct" }
: undefined,
chips,
buttons,
@@ -858,7 +858,7 @@ export function buildAdditiveSections(input: AdditiveInput): ProviderSection[] {
continue;
}
// 已添加:白底、不写「● 已添加」(分组标题和「移除」已经说清楚);灰底只给默认那一家
// 已添加:白底、不写「● 已添加」(分组标题和「移除」已经说清楚);蓝底只给默认那一家
const buttons: CardButton[] = [];
const menuItems: CardMenuItem[] = [];
let status: CardPresentation["status"];
@@ -869,7 +869,7 @@ export function buildAdditiveSections(input: AdditiveInput): ProviderSection[] {
if (app === "openclaw" || app === "opencode") {
isDefault = input.defaultModel?.providerId === p.id;
if (isDefault) {
status = { label: t("providerCard.status.default"), dot: "muted" };
status = { label: t("providerCard.status.default"), dot: "direct" };
chips.push({
key: "defaultModel",
label: t("providerCard.chip.defaultModel", {
@@ -912,7 +912,7 @@ export function buildAdditiveSections(input: AdditiveInput): ProviderSection[] {
if (app === "hermes") {
if (p.id === input.hermesCurrentId) {
isDefault = true;
status = { label: t("providerCard.status.current"), dot: "muted" };
status = { label: t("providerCard.status.current"), dot: "direct" };
removeReason = t("providerCard.reason.currentCannotRemove");
} else {
buttons.push({
@@ -934,7 +934,7 @@ export function buildAdditiveSections(input: AdditiveInput): ProviderSection[] {
added.push({
provider: p,
presentation: {
tone: isDefault ? "neutral" : undefined,
tone: isDefault ? "direct" : undefined,
status,
chips,
dim: managed,
+1 -1
View File
@@ -442,7 +442,7 @@ describe("ProviderList Component", () => {
expect(screen.getByTestId("status-current")).toHaveTextContent(
"providerCard.status.default",
);
expect(lastProps("current")?.presentation.tone).toBe("neutral");
expect(lastProps("current")?.presentation.tone).toBe("direct");
expect(lastProps("current")?.isCurrent).toBe(true);
expect(screen.queryByTestId("switch-current")).not.toBeInTheDocument();
expect(
@@ -535,7 +535,7 @@ describe("buildAdditiveSections", () => {
});
expect(item(sections, "relay")).toMatchObject({
tone: "neutral",
tone: "direct",
status: { label: "providerCard.status.default" },
});
expect(item(sections, "multi").tone).toBeUndefined();
@@ -574,7 +574,7 @@ describe("buildAdditiveSections", () => {
});
expect(item(sections, "relay")).toMatchObject({
tone: "neutral",
tone: "direct",
status: { label: "providerCard.status.default" },
});
expect(
@@ -641,7 +641,7 @@ describe("buildAdditiveSections", () => {
expect(button(sections, "relay", "remove").disabledReason).toBe(
"providerCard.reason.currentCannotRemove",
);
expect(item(sections, "relay").tone).toBe("neutral");
expect(item(sections, "relay").tone).toBe("direct");
expect(item(sections, "managed").tone).toBeUndefined();
expect(item(sections, "managed")).toMatchObject({
dim: true,