forked from 27hrs/dsh-long-skin
Use host native Switch for reasoning binding
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
export interface AppearanceSnapshot {
|
||||
enabled: boolean;
|
||||
bindEffort: boolean;
|
||||
}
|
||||
|
||||
export interface AppearanceScope {
|
||||
getSnapshot(): AppearanceSnapshot;
|
||||
subscribe(listener: () => void): () => void;
|
||||
setBindEffort(enabled: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface AppearancePresenter {
|
||||
@@ -15,8 +13,6 @@ export interface AppearancePresenter {
|
||||
|
||||
const NATIVE_APPEARANCE_ROW = '[class*="_cubeRow"]';
|
||||
const LIANG_APPEARANCE_BUTTON = "liang-appearance-choice";
|
||||
const LIANG_BINDING_CONTROL = "liang-appearance-binding";
|
||||
const LIANG_BINDING_INPUT = "liang-appearance-binding__input";
|
||||
|
||||
function hasClassSuffix(node: Element, suffix: string) {
|
||||
return [...node.classList].some((name) => name.endsWith(suffix));
|
||||
@@ -47,10 +43,7 @@ interface MountedAppearance {
|
||||
row: HTMLElement;
|
||||
group: HTMLElement;
|
||||
button: HTMLButtonElement;
|
||||
binding: HTMLLabelElement;
|
||||
bindingInput: HTMLInputElement;
|
||||
handleCustomClick: () => void;
|
||||
handleBindingChange: () => void;
|
||||
}
|
||||
|
||||
export function installLiangAppearanceButton(
|
||||
@@ -70,9 +63,7 @@ export function installLiangAppearanceButton(
|
||||
nativeClickHandlers.clear();
|
||||
if (mounted === null) return;
|
||||
mounted.button.removeEventListener("click", mounted.handleCustomClick);
|
||||
mounted.bindingInput.removeEventListener("change", mounted.handleBindingChange);
|
||||
mounted.button.remove();
|
||||
mounted.binding.remove();
|
||||
mounted = null;
|
||||
};
|
||||
|
||||
@@ -110,46 +101,7 @@ export function installLiangAppearanceButton(
|
||||
const handleCustomClick = () => { void choose(true); };
|
||||
button.addEventListener("click", handleCustomClick);
|
||||
|
||||
const binding = document.createElement("label");
|
||||
binding.className = LIANG_BINDING_CONTROL;
|
||||
binding.dataset.plugin = packageId;
|
||||
|
||||
const bindingCopy = document.createElement("span");
|
||||
bindingCopy.className = "liang-appearance-binding__copy";
|
||||
const bindingLabel = document.createElement("span");
|
||||
bindingLabel.className = "liang-appearance-binding__label";
|
||||
const bindingText = english ? "Bind slider to reasoning level" : "滑动变祖绑定思考等级";
|
||||
bindingLabel.textContent = bindingText;
|
||||
|
||||
const bindingDescription = document.createElement("span");
|
||||
bindingDescription.className = "liang-appearance-binding__description";
|
||||
bindingDescription.id = "liang-appearance-binding-description";
|
||||
bindingDescription.textContent = english
|
||||
? "When off, the slider does not change the reasoning level."
|
||||
: "关闭之后滑块不联动思考等级";
|
||||
bindingCopy.append(bindingLabel, bindingDescription);
|
||||
|
||||
const bindingInput = document.createElement("input");
|
||||
bindingInput.className = LIANG_BINDING_INPUT;
|
||||
bindingInput.type = "checkbox";
|
||||
bindingInput.setAttribute("role", "switch");
|
||||
bindingInput.setAttribute("aria-label", bindingText);
|
||||
bindingInput.setAttribute("aria-describedby", bindingDescription.id);
|
||||
const updateBinding = async () => {
|
||||
if (disposed || pending) return;
|
||||
try {
|
||||
await scope.setBindEffort(bindingInput.checked);
|
||||
} catch {
|
||||
// Restore the persisted selection if writing the preference fails.
|
||||
} finally {
|
||||
if (!disposed) sync();
|
||||
}
|
||||
};
|
||||
const handleBindingChange = () => { void updateBinding(); };
|
||||
bindingInput.addEventListener("change", handleBindingChange);
|
||||
binding.append(bindingCopy, bindingInput);
|
||||
|
||||
return { row, group, button, binding, bindingInput, handleCustomClick, handleBindingChange };
|
||||
return { row, group, button, handleCustomClick };
|
||||
};
|
||||
|
||||
const sync = () => {
|
||||
@@ -163,20 +115,12 @@ export function installLiangAppearanceButton(
|
||||
unmount();
|
||||
}
|
||||
mounted ??= createMount(target.row, target.group);
|
||||
const { row, group, button, binding, bindingInput } = mounted;
|
||||
const { row, button } = mounted;
|
||||
if (button.parentElement !== row) row.append(button);
|
||||
|
||||
const snapshot = scope.getSnapshot();
|
||||
button.disabled = pending;
|
||||
button.setAttribute("aria-pressed", String(snapshot.enabled));
|
||||
if (snapshot.enabled) {
|
||||
if (binding.parentElement !== group) group.append(binding);
|
||||
bindingInput.checked = snapshot.bindEffort;
|
||||
bindingInput.setAttribute("aria-checked", String(snapshot.bindEffort));
|
||||
bindingInput.disabled = pending;
|
||||
} else {
|
||||
binding.remove();
|
||||
}
|
||||
|
||||
for (const [nativeButton, handler] of nativeClickHandlers) {
|
||||
if (target.nativeButtons.includes(nativeButton)) continue;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useState,
|
||||
useSyncExternalStore,
|
||||
} from "react";
|
||||
import { Switch } from "@deepseek-ai/dsh-client-ui-primitives";
|
||||
import type { Context as ClientContext } from "@deepseek-ai/cordis";
|
||||
import type { SnapshotStore } from "@deepseek-ai/dsh-client-store";
|
||||
import type {} from "@deepseek-ai/dsh-client-ui-conversation/client";
|
||||
@@ -110,11 +111,8 @@ interface SliderProps {
|
||||
scope: PreferenceStore;
|
||||
}
|
||||
|
||||
interface SettingsRowProps {
|
||||
interface EffortBindingRowProps {
|
||||
scope: PreferenceStore;
|
||||
presenter: SkinPresenter;
|
||||
theme: ThemeService;
|
||||
subscribeTheme: (listener: () => void) => () => void;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
@@ -455,104 +453,27 @@ function LiangEffortSlider({ directory, load, select, presenter, scope }: Slider
|
||||
);
|
||||
}
|
||||
|
||||
type AppearanceChoice = NativeThemeId | "system" | "liang";
|
||||
|
||||
const APPEARANCE_CHOICES: readonly { id: AppearanceChoice; labelKey: string }[] = [
|
||||
{ id: "light", labelKey: "appearance.light" },
|
||||
{ id: "dark", labelKey: "appearance.dark" },
|
||||
{ id: "system", labelKey: "appearance.system" },
|
||||
{ id: "liang", labelKey: "appearance.liang" },
|
||||
];
|
||||
|
||||
function NativeAppearanceIcon({ id }: { id: Exclude<AppearanceChoice, "liang"> }) {
|
||||
if (id === "light") {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M11.3496 8C11.3496 6.14985 9.85015 4.65039 8 4.65039C6.14985 4.65039 4.65039 6.14985 4.65039 8C4.65039 9.85015 6.14985 11.3496 8 11.3496C9.85015 11.3496 11.3496 9.85015 11.3496 8ZM12.6504 8C12.6504 10.5681 10.5681 12.6504 8 12.6504C5.43188 12.6504 3.34961 10.5681 3.34961 8C3.34961 5.43188 5.43188 3.34961 8 3.34961C10.5681 3.34961 12.6504 5.43188 12.6504 8Z" fill="currentColor" />
|
||||
<path d="M8.65039 0.5V2.5H7.34961V0.5H8.65039Z" fill="currentColor" />
|
||||
<path d="M8.65039 13.5V15.5H7.34961V13.5H8.65039Z" fill="currentColor" />
|
||||
<path d="M3.15808 2.24035L4.57229 3.65456L3.6525 4.57435L2.23829 3.16014L3.15808 2.24035Z" fill="currentColor" />
|
||||
<path d="M12.3505 11.4327L13.7647 12.8469L12.8449 13.7667L11.4307 12.3525L12.3505 11.4327Z" fill="currentColor" />
|
||||
<path d="M2.24537 12.8469L3.65958 11.4327L4.57937 12.3525L3.16516 13.7667L2.24537 12.8469Z" fill="currentColor" />
|
||||
<path d="M11.4377 3.65455L12.852 2.24033L13.7718 3.16012L12.3575 4.57434L11.4377 3.65455Z" fill="currentColor" />
|
||||
<path d="M0.5 7.35461H2.5V8.6554H0.5L0.5 7.35461Z" fill="currentColor" />
|
||||
<path d="M13.5 7.35461H15.5V8.6554H13.5V7.35461Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
if (id === "dark") {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M13.2764 9.52324C12.5607 9.97754 11.7177 10.242 10.7812 10.242C8.11386 10.2419 5.95042 8.07997 5.9502 5.41289C5.9502 4.48128 6.21453 3.61071 6.67188 2.87285C4.30332 3.4658 2.54992 5.60845 2.5498 8.16093C2.5498 11.1712 4.99103 13.6102 8 13.6102C10.5383 13.6102 12.6709 11.8724 13.2764 9.52324ZM7.05078 5.41289C7.051 7.47224 8.72116 9.1423 10.7812 9.14238C11.9248 9.14238 12.887 8.63397 13.5781 7.8084C13.7266 7.63106 13.9701 7.56547 14.1875 7.64433C14.4049 7.72329 14.5497 7.9297 14.5498 8.16093C14.5498 11.7766 11.6161 14.7098 8 14.7098C4.38402 14.7098 1.4502 11.7792 1.4502 8.16093C1.45033 4.54322 4.3812 1.61015 8 1.61015C8.23027 1.61015 8.43585 1.75352 8.51562 1.96953C8.59536 2.18554 8.53241 2.42829 8.35742 2.57793C7.55573 3.26311 7.05078 4.27876 7.05078 5.41289Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M12.1665 13.5811V14.7803H3.66651V13.5811H12.1665Z" fill="currentColor" />
|
||||
<path d="M13.4453 7.02379C13.4453 6.04702 13.4452 5.3616 13.3887 4.83434C13.3333 4.31828 13.2302 4.02378 13.0723 3.80309C12.9446 3.62475 12.7877 3.46883 12.6094 3.34117C12.3887 3.18328 12.0942 3.08007 11.5781 3.02477C11.0508 2.96829 10.3655 2.96715 9.38867 2.96715H6.61035C5.63359 2.96715 4.94816 2.96827 4.4209 3.02477C3.90486 3.0801 3.61034 3.18321 3.38965 3.34117C3.21143 3.46878 3.05534 3.62487 2.92774 3.80309C2.76977 4.02377 2.66667 4.3183 2.61133 4.83434C2.55483 5.3616 2.55371 6.04702 2.55371 7.02379C2.55371 8.0006 2.55485 8.68596 2.61133 9.21324C2.66663 9.72936 2.76983 10.0238 2.92774 10.2445C3.0554 10.4228 3.21131 10.5797 3.38965 10.7074C3.61034 10.8654 3.90484 10.9685 4.4209 11.0238C4.94816 11.0803 5.63359 11.0804 6.61035 11.0804H9.38867C10.3654 11.0804 11.0508 11.0803 11.5781 11.0238C12.0941 10.9685 12.3887 10.8652 12.6094 10.7074C12.7877 10.5797 12.9446 10.4229 13.0723 10.2445C13.2301 10.0238 13.3334 9.72927 13.3887 9.21324C13.4452 8.68596 13.4453 8.00058 13.4453 7.02379ZM14.6455 7.02379C14.6455 7.97428 14.646 8.73509 14.5811 9.34117C14.5149 9.95828 14.3756 10.4858 14.0479 10.9437C13.8436 11.229 13.5938 11.4788 13.3086 11.683C12.8507 12.0108 12.3232 12.15 11.7061 12.2162C11.1 12.2811 10.3391 12.2806 9.38867 12.2806H6.61035C5.66018 12.2806 4.89991 12.2811 4.29395 12.2162C3.67684 12.15 3.14935 12.0108 2.69141 11.683C2.40613 11.4788 2.15639 11.229 1.95215 10.9437C1.62436 10.4858 1.4841 9.95828 1.41797 9.34117C1.35305 8.73511 1.35449 7.97424 1.35449 7.02379C1.35449 6.07366 1.35308 5.31333 1.41797 4.70738C1.4841 4.09028 1.62436 3.56279 1.95215 3.10485C2.15638 2.81956 2.40613 2.56982 2.69141 2.36559C3.14935 2.03779 3.67684 1.89753 4.29395 1.83141C4.8999 1.76652 5.66022 1.76793 6.61035 1.76793H9.38867C10.3391 1.76793 11.1 1.76649 11.7061 1.83141C12.3232 1.89753 12.8507 2.03779 13.3086 2.36559C13.5939 2.56982 13.8436 2.81957 14.0479 3.10485C14.3756 3.56279 14.5149 4.09028 14.5811 4.70738C14.646 5.31335 14.6455 6.07362 14.6455 7.02379Z" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function AppearanceSkinRow({ scope, presenter, theme, subscribeTheme, t }: SettingsRowProps) {
|
||||
function EffortBindingRow({ scope, t }: EffortBindingRowProps) {
|
||||
const snapshot = useSyncExternalStore(
|
||||
(listener) => scope.subscribe(listener),
|
||||
() => scope.getSnapshot(),
|
||||
);
|
||||
const preference = useSyncExternalStore(
|
||||
subscribeTheme,
|
||||
() => theme.getTheme().preference,
|
||||
);
|
||||
const selected = snapshot.enabled
|
||||
? "liang"
|
||||
: preference === "light" || preference === "dark" || preference === "system"
|
||||
? preference
|
||||
: "system";
|
||||
const [pending, setPending] = useState(false);
|
||||
|
||||
const choose = async (next: AppearanceChoice) => {
|
||||
if (next === selected || pending) return;
|
||||
setPending(true);
|
||||
try {
|
||||
if (next === "liang") {
|
||||
await presenter.choose(true);
|
||||
} else {
|
||||
// Disable the custom skin first. This is intentionally unconditional:
|
||||
// it makes the native choice the only writer after this click, even if
|
||||
// the external preference store is one render behind.
|
||||
await presenter.choose(false);
|
||||
theme.setTheme(next);
|
||||
}
|
||||
} finally {
|
||||
setPending(false);
|
||||
}
|
||||
};
|
||||
if (!snapshot.enabled) return null;
|
||||
|
||||
return (
|
||||
<div className="liang-settings-row" data-plugin={PACKAGE_ID}>
|
||||
<span className="liang-settings-row__title">{t("appearance.title")}</span>
|
||||
<div className="liang-settings-row__choices">
|
||||
{APPEARANCE_CHOICES.map(({ id, labelKey }) => (
|
||||
<button
|
||||
className={`liang-settings-row__choice${id === "liang" ? " liang-settings-row__choice--liang" : ""}`}
|
||||
type="button"
|
||||
aria-pressed={selected === id}
|
||||
disabled={pending}
|
||||
onClick={() => void choose(id)}
|
||||
key={id}
|
||||
>
|
||||
{id === "liang" ? (
|
||||
<span className="liang-settings-row__liang-icon" aria-hidden="true">◈</span>
|
||||
) : (
|
||||
<NativeAppearanceIcon id={id} />
|
||||
)}
|
||||
{t(labelKey)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="liang-appearance-binding" data-plugin={PACKAGE_ID}>
|
||||
<span>{t("effort.binding")}</span>
|
||||
<Switch
|
||||
checked={snapshot.bindEffort}
|
||||
disabled={pending}
|
||||
label={t("effort.binding")}
|
||||
onChange={(next) => {
|
||||
if (pending) return;
|
||||
setPending(true);
|
||||
void scope.setBindEffort(next).catch(() => undefined).finally(() => setPending(false));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -632,22 +553,18 @@ export function apply(ctx: ClientContext) {
|
||||
);
|
||||
ctx.effect(
|
||||
() => ctx.locale.register(LOCALE_NAMESPACE, {
|
||||
zh: {
|
||||
"appearance.title": "外观",
|
||||
"appearance.light": "浅色",
|
||||
"appearance.dark": "深色",
|
||||
"appearance.system": "跟随系统",
|
||||
"appearance.liang": "滑动变祖",
|
||||
},
|
||||
en: {
|
||||
"appearance.title": "Appearance",
|
||||
"appearance.light": "Light",
|
||||
"appearance.dark": "Dark",
|
||||
"appearance.system": "System",
|
||||
"appearance.liang": "Slider",
|
||||
},
|
||||
zh: { "effort.binding": "滑动变祖绑定思考等级" },
|
||||
en: { "effort.binding": "Bind slider to reasoning level" },
|
||||
}), "liang-intensity-skin: settings locale");
|
||||
|
||||
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
||||
name: "settings.general.item",
|
||||
id: "liang-effort-binding",
|
||||
order: 10.5,
|
||||
locale: LOCALE_NAMESPACE,
|
||||
inject: () => ({ scope }),
|
||||
}, EffortBindingRow));
|
||||
|
||||
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
||||
name: "conversation.input.right",
|
||||
id: "liang-intensity-control",
|
||||
|
||||
@@ -497,90 +497,14 @@ body[data-liang-skin="on"] .liang-effort-control__range::-webkit-slider-thumb {
|
||||
}
|
||||
|
||||
.liang-appearance-binding {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 28px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 4px 0 0;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font: inherit;
|
||||
gap: 24px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 0.5px solid var(--dsw-alias-border-l2);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.liang-appearance-binding__label {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.liang-appearance-binding__copy {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.liang-appearance-binding__description {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input {
|
||||
position: relative;
|
||||
flex: 0 0 36px;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 999px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-module-platform));
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input::before {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
background: var(--dsw-alias-label-primary);
|
||||
box-shadow: 0 1px 3px rgb(0 0 0 / 18%);
|
||||
transition: transform 160ms ease, background-color 160ms ease;
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input:checked {
|
||||
border-color: var(--dsw-static-neutral-bluish-400);
|
||||
background: var(--dsw-static-neutral-bluish-400);
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input:checked::before {
|
||||
background: var(--dsw-alias-bg-layer-1, #fff);
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
.liang-appearance-binding__input:disabled {
|
||||
cursor: progress;
|
||||
opacity: 0.7;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
|
||||
Reference in New Issue
Block a user