diff --git a/README.md b/README.md index 166193c..b059987 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# dsh-plugin-model-enhancer +# dsh-plugin-model-enhancer (adapted for DSH 0.1.5-rc.2) -DeepSeek Harness (DSH) Model Settings Enhancer & Codex Selector Plugin (Compatible with DSH 0.1.5-rc.2). +DeepSeek Harness (DSH) Model Settings Enhancer Plugin (Adapted for DSH 0.1.5-rc.2). ## ✨ Features @@ -8,17 +8,13 @@ DeepSeek Harness (DSH) Model Settings Enhancer & Codex Selector Plugin (Compatib - Integrates model enhancement configuration via `settings.models.footer` and `settings.models.provider-card` (targeted at `llm-pi-ai`). - Supports models.dev auto-fill matching, prefilling missing capacities, image modalities (`input: ["text", "image"]`), and multi-level reasoning efforts (`minimal` / `low` / `medium` / `high` / `xhigh` / `max`). - Uses `ctx.remote.settings.mutate` with revision fence checking for safe, non-destructive configuration persistence. - - Preserves official file attachment and upload pipeline (`ui-attachment` & `file-upload`), removing obsolete custom upload endpoint routes. + - Preserves official file attachment and upload pipeline (`ui-attachment` & `file-upload`). + - Delegates session-level model and reasoning selection completely to official DSH 0.1.5-rc.2 core components. -2. **Codex-style Model & Reasoning Effort Selector**: - - Injected into `conversation.input.right` seamlessly alongside the official composer. - - Reuses `ctx.modelDirectories` (`ModelDirectoryResolver`) for per-session directory resolution and selection dispatch. - - Interactive sliding track for reasoning effort adjustments and grouped model picker popup. - -3. **models.dev Cache & Proxy**: +2. **models.dev Cache & Proxy**: - Exposes exact WebServer proxy route `/api/models-dev/models` with in-memory caching and fallback. -4. **Full Bilingual Support (i18n)**: +3. **Full Bilingual Support (i18n)**: - Dynamic localization supporting Simplified Chinese (`zh`) and English (`en`). ## 🛠 Building & Development diff --git a/lib/client.js b/lib/client.js index 16da27f..df010bc 100644 --- a/lib/client.js +++ b/lib/client.js @@ -41,296 +41,7 @@ __export(index_exports, { module.exports = __toCommonJS(index_exports); // src/client/styles.ts -var CODEX_LEVELS_ZH = [ - { id: "off", label: "\u5173", short: "\u5173" }, - { id: "minimal", label: "\u6781\u4F4E", short: "\u6781\u4F4E" }, - { id: "low", label: "\u4F4E", short: "\u4F4E" }, - { id: "medium", label: "\u4E2D", short: "\u4E2D" }, - { id: "high", label: "\u9AD8", short: "\u9AD8" }, - { id: "xhigh", label: "\u8D85\u9AD8", short: "\u8D85\u9AD8" }, - { id: "max", label: "\u6700\u5927", short: "\u6700\u5927" } -]; -var CODEX_LEVELS_EN = [ - { id: "off", label: "Off", short: "Off" }, - { id: "minimal", label: "Minimal", short: "Minimal" }, - { id: "low", label: "Low", short: "Low" }, - { id: "medium", label: "Medium", short: "Medium" }, - { id: "high", label: "High", short: "High" }, - { id: "xhigh", label: "X-High", short: "X-High" }, - { id: "max", label: "Max", short: "Max" } -]; var ENHANCER_CSS = ` -/* Hide default trigger when Codex trigger is active */ -div[class*="_7KE1Ra_root"], div[class*="ModelSelect_module_css_root"] { - display: none !important; -} - -.me-codex-trigger { - box-sizing: border-box; - height: 28px; - padding: 0 8px; - border-radius: 14px; - border: 1px solid transparent; - background: transparent; - color: var(--dsw-alias-label-secondary, #61666b); - font-size: 13px; - font-weight: 500; - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; - outline: none; - transition: all 120ms ease; - user-select: none; -} -.me-codex-trigger:hover:not(:disabled), -.me-codex-trigger.active-open { - border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.15)); - color: var(--dsw-alias-label-primary, #0f1115); -} -body[data-ds-dark-theme] .me-codex-trigger:hover:not(:disabled), -body[data-ds-dark-theme] .me-codex-trigger.active-open { - border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.2)); - color: var(--dsw-alias-label-primary, #fff); -} -.me-codex-trigger-chevron { - transition: transform 140ms ease; - opacity: 0.7; -} -.me-codex-trigger-chevron.open { - transform: rotate(180deg); -} - -.me-codex-popup { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - width: 280px; - background: var(--dsw-specific-menu, #ffffff); - border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06); - border-radius: 14px; - padding: 10px 12px 12px; - z-index: 1200; - display: flex; - flex-direction: column; - gap: 8px; -} -body[data-ds-dark-theme] .me-codex-popup { - background: var(--dsw-specific-menu, #1b1d22); - border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1)); - box-shadow: 0 10px 36px rgba(0, 0, 0, 0.5), 0 2px 10px rgba(0, 0, 0, 0.3); -} - -.me-codex-topbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} -.me-codex-icon-btn { - box-sizing: border-box; - width: 26px; - height: 26px; - border-radius: 6px; - border: none; - background: transparent; - color: var(--dsw-alias-label-secondary, #64748b); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; -} -.me-codex-icon-btn:hover { - background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05)); - color: var(--dsw-alias-label-primary, #0f172a); -} -body[data-ds-dark-theme] .me-codex-icon-btn { - color: var(--dsw-alias-label-secondary, #94a3b8); -} -body[data-ds-dark-theme] .me-codex-icon-btn:hover { - background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.08)); - color: #fff; -} - -.me-codex-center { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - cursor: pointer; - user-select: none; -} -.me-codex-effort-title { - font-size: 13px; - font-weight: 600; - color: var(--dsw-alias-label-primary, #0f172a); - display: flex; - align-items: center; - gap: 3px; -} -body[data-ds-dark-theme] .me-codex-effort-title { - color: #fff; -} -.me-codex-model-name { - font-size: 11px; - color: var(--dsw-alias-label-tertiary, #64748b); - white-space: nowrap; - max-width: 170px; - overflow: hidden; - text-overflow: ellipsis; -} -.me-codex-model-name:hover { - color: #2563eb; -} -body[data-ds-dark-theme] .me-codex-model-name { - color: var(--dsw-alias-label-tertiary, #94a3b8); -} -body[data-ds-dark-theme] .me-codex-model-name:hover { - color: #60a5fa; -} - -.me-slider-track-wrap { - box-sizing: border-box; - padding: 4px 0 2px; - cursor: pointer; - user-select: none; -} -.me-slider-track { - position: relative; - height: 8px; - background: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); - border-radius: 4px; - display: flex; - align-items: center; -} -body[data-ds-dark-theme] .me-slider-track { - background: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.12)); -} - -.me-slider-active-fill { - position: absolute; - left: 0; - height: 100%; - border-radius: 4px; - background: #3b82f6; - pointer-events: none; - transition: width 120ms ease; -} - -.me-slider-dots-layer { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 14px; - pointer-events: none; -} -.me-slider-dot { - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2)); -} -body[data-ds-dark-theme] .me-slider-dot { - background: rgba(255, 255, 255, 0.25); -} -.me-slider-dot.lit { - background: #fff; -} - -.me-slider-thumb { - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - width: 16px; - height: 16px; - border-radius: 50%; - background: #ffffff; - border: 2px solid #3b82f6; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); - pointer-events: none; - transition: left 120ms ease; -} -body[data-ds-dark-theme] .me-slider-thumb { - background: #1e293b; - border-color: #60a5fa; -} - -.me-model-picker-list { - max-height: 210px; - overflow-y: auto; - padding-right: 2px; -} -.me-model-group-section { - margin-top: 8px; - padding-top: 6px; - border-top: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); -} -.me-model-group-section:first-child { - margin-top: 0; - padding-top: 0; - border-top: none; -} -.me-model-picker-group { - font-size: 11px; - color: #2563eb; - font-weight: 600; - padding: 3px 6px; - text-transform: uppercase; - letter-spacing: 0.05em; - display: inline-flex; - align-items: center; - gap: 4px; - background: rgba(37, 99, 235, 0.08); - border-radius: 4px; - margin-bottom: 4px; -} -body[data-ds-dark-theme] .me-model-picker-group { - color: #60a5fa; - background: rgba(59, 130, 246, 0.08); -} -.me-model-picker-item { - box-sizing: border-box; - width: 100%; - padding: 6px 8px; - border-radius: 6px; - background: transparent; - border: none; - color: var(--dsw-alias-label-primary, #0f1115); - font-size: 13px; - text-align: left; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - transition: all 120ms ease; -} -.me-model-picker-item:hover { - background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05)); -} -.me-model-picker-item.active { - background: rgba(37, 99, 235, 0.1); - color: #2563eb; - font-weight: 500; -} -body[data-ds-dark-theme] .me-model-picker-item { - color: var(--dsw-alias-label-primary, #e2e8f0); -} -body[data-ds-dark-theme] .me-model-picker-item:hover { - background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06)); -} -body[data-ds-dark-theme] .me-model-picker-item.active { - background: rgba(56, 189, 248, 0.12); - color: #38bdf8; -} - -/* Footer Section */ .me-footer-card { box-sizing: border-box; margin-top: 16px; @@ -380,9 +91,149 @@ body[data-ds-dark-theme] .me-diff-table th, body[data-ds-dark-theme] .me-diff-ta } `; -// src/client/CodexModelSelect.tsx +// src/client/ModelsFooterSettings.tsx var import_react = __toESM(require("react"), 1); +// src/client/modelsDev.ts +var THINKING_LEVELS_ALL = ["minimal", "low", "medium", "high", "xhigh", "max"]; +var modelsDevGlobalCache = null; +var modelsDevGlobalPromise = null; +async function fetchAllModelsDev() { + if (modelsDevGlobalCache) return modelsDevGlobalCache; + if (modelsDevGlobalPromise) return modelsDevGlobalPromise; + modelsDevGlobalPromise = (async () => { + const endpoints = ["/api/models-dev/models", "https://models.dev/api.json"]; + let lastError = null; + for (const url of endpoints) { + try { + const res = await fetch(url); + if (res.ok) { + const json = await res.json(); + const list = []; + for (const [pId, p] of Object.entries(json)) { + if (p && p.models) { + for (const [mId, m] of Object.entries(p.models)) { + list.push({ + providerKey: pId, + providerName: p.name || pId, + id: m.id || mId, + rawKey: mId, + ...m + }); + } + } + } + modelsDevGlobalCache = list; + return list; + } + } catch (e) { + lastError = e; + } + } + throw lastError || new Error("Failed to load models.dev"); + })(); + try { + return await modelsDevGlobalPromise; + } finally { + modelsDevGlobalPromise = null; + } +} +function sanitizePositiveInt(val) { + if (typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val && val >= 1) { + return val; + } + if (typeof val === "string" && /^\d+$/.test(val.trim())) { + const parsed = parseInt(val.trim(), 10); + if (Number.isFinite(parsed) && parsed >= 1) { + return parsed; + } + } + return void 0; +} +function mergeModelCapabilities(m, matched) { + if (!m) return m; + if (!matched) { + const result2 = { ...m }; + const validExistingContext = sanitizePositiveInt(m.contextWindow); + if (validExistingContext !== void 0) { + result2.contextWindow = validExistingContext; + } else { + delete result2.contextWindow; + } + const validExistingMaxTokens = sanitizePositiveInt(m.maxTokens); + if (validExistingMaxTokens !== void 0) { + result2.maxTokens = validExistingMaxTokens; + } else { + delete result2.maxTokens; + } + return result2; + } + const hasImage = Boolean(matched.modalities?.input?.includes("image")); + const hasReasoning = Boolean(matched.reasoning); + let reasoningLevels = void 0; + if (hasReasoning) { + reasoningLevels = { off: "none" }; + let added = false; + if (Array.isArray(matched.reasoning_options)) { + for (const opt of matched.reasoning_options) { + if (opt.type === "effort" && Array.isArray(opt.values)) { + for (const val of opt.values) { + if (val === "none" || val === "off") reasoningLevels.off = "none"; + else if (THINKING_LEVELS_ALL.includes(val)) { + reasoningLevels[val] = val; + added = true; + } + } + } + } + } + if (!added) { + reasoningLevels.low = "low"; + reasoningLevels.medium = "medium"; + reasoningLevels.high = "high"; + } + } + const result = { + ...m, + name: m.name || matched.name, + input: m.input || (hasImage ? ["text", "image"] : ["text"]), + reasoningEfforts: m.reasoningEfforts !== void 0 ? m.reasoningEfforts : hasReasoning ? reasoningLevels : false + }; + const resolvedContext = sanitizePositiveInt(m.contextWindow) ?? sanitizePositiveInt(matched.limit?.context); + if (resolvedContext !== void 0) { + result.contextWindow = resolvedContext; + } else { + delete result.contextWindow; + } + const resolvedMaxTokens = sanitizePositiveInt(m.maxTokens) ?? sanitizePositiveInt(matched.limit?.output); + if (resolvedMaxTokens !== void 0) { + result.maxTokens = resolvedMaxTokens; + } else { + delete result.maxTokens; + } + return result; +} +function normModelKey(key) { + if (!key || typeof key !== "string") return ""; + return key.toLowerCase().replace(/^[a-z0-9_-]+\//, "").replace(/[-_.:/]/g, "").replace(/\d{8}$/, ""); +} +function findDevModel(queryId, allList) { + if (!queryId || !allList) return null; + const q = queryId.trim().toLowerCase(); + const qNorm = normModelKey(q); + let m = allList.find((x) => x.id?.toLowerCase() === q || x.rawKey?.toLowerCase() === q); + if (m) return m; + m = allList.find((x) => x.id?.split("/").pop()?.toLowerCase() === q); + if (m) return m; + m = allList.find((x) => normModelKey(x.id) === qNorm || normModelKey(x.name || "") === qNorm); + if (m) return m; + m = allList.find((x) => { + const n = normModelKey(x.id); + return n.length >= 3 && (n.includes(qNorm) || qNorm.includes(n)); + }); + return m || null; +} + // src/client/i18n.ts var I18N_DICT = { zh: { @@ -435,20 +286,7 @@ var I18N_DICT = { thinkingOff: "\u5173\u95ED\u601D\u8003", thinkingOn: "\u5F00\u542F", cancel: "\u53D6\u6D88", - applySelected: "\u5E94\u7528\u5DF2\u9009\u63A8\u8350\u9879", - // Codex Slider - effortOff: "\u5173", - effortMinimal: "\u6781\u4F4E", - effortLow: "\u4F4E", - effortMedium: "\u4E2D", - effortHigh: "\u9AD8", - effortXHigh: "\u8D85\u9AD8", - effortMax: "\u6700\u5927", - effortReasoningTitle: "\u63A8\u7406\u5F3A\u5EA6", - effortSelectModelTip: "\u70B9\u51FB\u5207\u6362\u6A21\u578B", - effortResetTip: "\u6062\u590D\u9ED8\u8BA4\u6863\u4F4D", - effortChooseModel: "\u9009\u62E9\u6A21\u578B", - commandsTitle: "\u547D\u4EE4\u83DC\u5355 (/)" + applySelected: "\u5E94\u7528\u5DF2\u9009\u63A8\u8350\u9879" }, en: { // ModelListEditor & Badges @@ -500,20 +338,7 @@ var I18N_DICT = { thinkingOff: "Disabled", thinkingOn: "Enabled", cancel: "Cancel", - applySelected: "Apply Selected", - // Codex Slider - effortOff: "Off", - effortMinimal: "Minimal", - effortLow: "Low", - effortMedium: "Medium", - effortHigh: "High", - effortXHigh: "X-High", - effortMax: "Max", - effortReasoningTitle: "Reasoning Effort", - effortSelectModelTip: "Click to switch model", - effortResetTip: "Reset to default effort", - effortChooseModel: "Select model", - commandsTitle: "Commands (/)" + applySelected: "Apply Selected" } }; function isZhLocale() { @@ -534,350 +359,14 @@ function getTranslation(key, isZh = isZhLocale(), params) { return str; } -// src/client/CodexModelSelect.tsx -function CodexModelSelect(props) { - const { directory, load, select, available, locked } = props; - const state = (0, import_react.useSyncExternalStore)(directory.subscribe, directory.getSnapshot); - const [open, setOpen] = (0, import_react.useState)(false); - const [showModelPicker, setShowModelPicker] = (0, import_react.useState)(false); - const rootRef = (0, import_react.useRef)(null); - const isZh = isZhLocale(); - const codexLevels = isZh ? CODEX_LEVELS_ZH : CODEX_LEVELS_EN; - (0, import_react.useEffect)(() => { - if (available) load(); - }, [available]); - (0, import_react.useEffect)(() => { - if (!open) return; - const onMouseDown = (e) => { - if (!rootRef.current?.contains(e.target)) { - setOpen(false); - setShowModelPicker(false); - } - }; - document.addEventListener("mousedown", onMouseDown); - return () => document.removeEventListener("mousedown", onMouseDown); - }, [open]); - const choices = (0, import_react.useMemo)(() => (state.groups || []).flatMap((g) => (g.models || []).map((m) => ({ - group: g, - model: m - }))), [state.groups]); - const currentChoice = choices.find((c) => c.group.id === state.current?.provider && c.model.id === state.current?.model); - const currentModel = currentChoice?.model; - const reasoning = currentModel?.reasoning; - const availableEfforts = (0, import_react.useMemo)(() => { - if (!reasoning || !reasoning.efforts || reasoning.efforts.length === 0) return []; - return reasoning.efforts.map((e) => { - const found = codexLevels.find((l) => l.id === e.id); - return { - id: e.id, - label: found?.label || e.name || e.id, - short: found?.short || e.name || e.id - }; - }); - }, [reasoning, isZh]); - const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort; - const currentEffortIndex = (0, import_react.useMemo)(() => { - if (availableEfforts.length === 0) return 0; - const idx = availableEfforts.findIndex((e) => e.id === effectiveEffort); - return idx === -1 ? 0 : idx; - }, [availableEfforts, effectiveEffort]); - const currentEffortObj = availableEfforts[currentEffortIndex]; - const effortDisplayLabel = currentEffortObj ? currentEffortObj.label : isZh ? "\u4E2D" : "Medium"; - const triggerModelName = currentModel?.name || (isZh ? "\u9009\u62E9\u6A21\u578B" : "Select model"); - const triggerEffortLabel = reasoning ? currentEffortObj ? currentEffortObj.short : isZh ? "\u9ED8\u8BA4" : "Default" : null; - const onSelectEffortByIndex = (idx) => { - if (!currentChoice || !availableEfforts[idx]) return; - const effort = availableEfforts[idx].id; - select({ - provider: currentChoice.group.id, - model: currentChoice.model.id, - reasoningEffort: effort - }); - }; - const onResetDefault = () => { - if (!currentChoice) return; - select({ - provider: currentChoice.group.id, - model: currentChoice.model.id, - reasoningEffort: reasoning?.defaultEffort - }); - }; - const onTrackClick = (e) => { - if (availableEfforts.length <= 1) return; - const rect = e.currentTarget.getBoundingClientRect(); - const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); - const idx = Math.round(ratio * (availableEfforts.length - 1)); - onSelectEffortByIndex(idx); - }; - if (!available) return null; - return import_react.default.createElement( - "div", - { - ref: rootRef, - style: { position: "relative", display: "inline-flex", alignItems: "center" } - }, - import_react.default.createElement( - "button", - { - type: "button", - className: `me-codex-trigger ${open ? "active-open" : ""}`, - disabled: locked, - onClick: () => setOpen(!open) - }, - import_react.default.createElement("span", null, triggerModelName), - triggerEffortLabel ? import_react.default.createElement("span", { - style: { color: "#3b82f6", fontWeight: 600 } - }, "\xB7 ", triggerEffortLabel) : null, - import_react.default.createElement("svg", { - className: `me-codex-trigger-chevron ${open ? "open" : ""}`, - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "1.8" - }, import_react.default.createElement("path", { d: "M4 6l4 4 4-4" })) - ), - open ? import_react.default.createElement( - "div", - { - className: "me-codex-popup" - }, - import_react.default.createElement( - "div", - { - className: "me-codex-topbar" - }, - import_react.default.createElement( - "div", - { - className: "me-codex-icon-btn", - title: isZh ? "\u63A8\u7406\u5F3A\u5EA6" : "Reasoning Effort" - }, - import_react.default.createElement("svg", { - width: "16", - height: "16", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - strokeWidth: "2" - }, import_react.default.createElement("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })) - ), - import_react.default.createElement( - "div", - { - className: "me-codex-center", - onClick: () => setShowModelPicker(!showModelPicker), - title: isZh ? "\u70B9\u51FB\u5207\u6362\u6A21\u578B" : "Click to switch model" - }, - import_react.default.createElement( - "div", - { - className: "me-codex-effort-title" - }, - effortDisplayLabel, - import_react.default.createElement("svg", { - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "2", - style: { transform: showModelPicker ? "rotate(90deg)" : void 0, transition: "transform 140ms ease" } - }, import_react.default.createElement("path", { d: "M6 3.5L10.5 8L6 12.5" })) - ), - import_react.default.createElement("div", { - className: "me-codex-model-name" - }, currentModel?.name || (isZh ? "\u9009\u62E9\u6A21\u578B" : "Select model")) - ), - import_react.default.createElement( - "button", - { - type: "button", - className: "me-codex-icon-btn", - title: isZh ? "\u6062\u590D\u9ED8\u8BA4\u6863\u4F4D" : "Reset to default effort", - onClick: onResetDefault - }, - import_react.default.createElement("svg", { - width: "16", - height: "16", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - strokeWidth: "2" - }, import_react.default.createElement("path", { d: "M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8m0 0V3m0 5h5" })) - ) - ), - showModelPicker ? import_react.default.createElement( - "div", - { - className: "me-model-picker-list" - }, - (state.groups || []).map((group) => import_react.default.createElement( - "div", - { - key: group.id, - className: "me-model-group-section" - }, - import_react.default.createElement( - "div", - { - className: "me-model-picker-group" - }, - import_react.default.createElement("svg", { - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "1.8" - }, import_react.default.createElement("path", { d: "M2 4h12M2 8h12M2 12h8" })), - group.name - ), - (group.models || []).map((m) => { - const active = state.current?.provider === group.id && state.current?.model === m.id; - return import_react.default.createElement( - "button", - { - key: m.id, - type: "button", - className: `me-model-picker-item ${active ? "active" : ""}`, - onClick: () => { - select({ - provider: group.id, - model: m.id, - reasoningEffort: m.reasoning?.defaultEffort - }); - setShowModelPicker(false); - } - }, - import_react.default.createElement("span", null, m.name), - active ? import_react.default.createElement("svg", { - width: "14", - height: "14", - viewBox: "0 0 16 16", - fill: "none", - stroke: "#38bdf8", - strokeWidth: "2" - }, import_react.default.createElement("path", { d: "M3.5 8.5l3 3 6-6" })) : null - ); - }) - )) - ) : import_react.default.createElement( - "div", - { - className: "me-slider-track-wrap", - onClick: onTrackClick - }, - import_react.default.createElement( - "div", - { - className: "me-slider-track" - }, - import_react.default.createElement("div", { - className: "me-slider-active-fill", - style: { - width: availableEfforts.length <= 1 ? "100%" : `calc(16px + (100% - 32px) * ${currentEffortIndex / (availableEfforts.length - 1)})` - } - }), - import_react.default.createElement( - "div", - { - className: "me-slider-dots-layer" - }, - availableEfforts.map((eff, i) => import_react.default.createElement("div", { - key: eff.id, - className: `me-slider-dot ${i <= currentEffortIndex ? "lit" : ""}` - })) - ), - import_react.default.createElement("div", { - className: "me-slider-thumb", - style: { - left: availableEfforts.length <= 1 ? "calc(100% - 16px)" : `calc(16px + (100% - 32px) * ${currentEffortIndex / (availableEfforts.length - 1)})` - } - }) - ) - ) - ) : null - ); -} - -// src/client/ModelsFooterSettings.tsx -var import_react2 = __toESM(require("react"), 1); - -// src/client/modelsDev.ts -var THINKING_LEVELS_ALL = ["minimal", "low", "medium", "high", "xhigh", "max"]; -var modelsDevGlobalCache = null; -var modelsDevGlobalPromise = null; -async function fetchAllModelsDev() { - if (modelsDevGlobalCache) return modelsDevGlobalCache; - if (modelsDevGlobalPromise) return modelsDevGlobalPromise; - modelsDevGlobalPromise = (async () => { - const endpoints = ["/api/models-dev/models", "https://models.dev/api.json"]; - let lastError = null; - for (const url of endpoints) { - try { - const res = await fetch(url); - if (res.ok) { - const json = await res.json(); - const list = []; - for (const [pId, p] of Object.entries(json)) { - if (p && p.models) { - for (const [mId, m] of Object.entries(p.models)) { - list.push({ - providerKey: pId, - providerName: p.name || pId, - id: m.id || mId, - rawKey: mId, - ...m - }); - } - } - } - modelsDevGlobalCache = list; - return list; - } - } catch (e) { - lastError = e; - } - } - throw lastError || new Error("Failed to load models.dev"); - })(); - try { - return await modelsDevGlobalPromise; - } finally { - modelsDevGlobalPromise = null; - } -} -function normModelKey(key) { - if (!key || typeof key !== "string") return ""; - return key.toLowerCase().replace(/^[a-z0-9_-]+\//, "").replace(/[-_.:/]/g, "").replace(/\d{8}$/, ""); -} -function findDevModel(queryId, allList) { - if (!queryId || !allList) return null; - const q = queryId.trim().toLowerCase(); - const qNorm = normModelKey(q); - let m = allList.find((x) => x.id?.toLowerCase() === q || x.rawKey?.toLowerCase() === q); - if (m) return m; - m = allList.find((x) => x.id?.split("/").pop()?.toLowerCase() === q); - if (m) return m; - m = allList.find((x) => normModelKey(x.id) === qNorm || normModelKey(x.name || "") === qNorm); - if (m) return m; - m = allList.find((x) => { - const n = normModelKey(x.id); - return n.length >= 3 && (n.includes(qNorm) || qNorm.includes(n)); - }); - return m || null; -} - // src/client/ModelsFooterSettings.tsx function ModelsFooterSettings({ ctx }) { const isZh = isZhLocale(); - const [loading, setLoading] = (0, import_react2.useState)(false); - const [providerList, setProviderList] = (0, import_react2.useState)([]); - const [selectedProvider, setSelectedProvider] = (0, import_react2.useState)(""); - const [statusMsg, setStatusMsg] = (0, import_react2.useState)(null); - const [batching, setBatching] = (0, import_react2.useState)(false); + const [loading, setLoading] = (0, import_react.useState)(false); + const [providerList, setProviderList] = (0, import_react.useState)([]); + const [selectedProvider, setSelectedProvider] = (0, import_react.useState)(""); + const [statusMsg, setStatusMsg] = (0, import_react.useState)(null); + const [batching, setBatching] = (0, import_react.useState)(false); const loadProviders = async () => { try { setLoading(true); @@ -894,7 +383,7 @@ function ModelsFooterSettings({ ctx }) { setLoading(false); } }; - (0, import_react2.useEffect)(() => { + (0, import_react.useEffect)(() => { loadProviders(); }, []); const handleBatchDevMatch = async () => { @@ -920,41 +409,8 @@ function ModelsFooterSettings({ ctx }) { const nextModels = models.map((m) => { if (!m || !m.id) return m; const matched = findDevModel(m.id, allDevModels); - if (!matched) return m; - count++; - const hasImage = Boolean(matched.modalities?.input?.includes("image")); - const hasReasoning = Boolean(matched.reasoning); - let reasoningLevels = void 0; - if (hasReasoning) { - reasoningLevels = { off: "none" }; - let added = false; - if (Array.isArray(matched.reasoning_options)) { - for (const opt of matched.reasoning_options) { - if (opt.type === "effort" && Array.isArray(opt.values)) { - for (const val of opt.values) { - if (val === "none" || val === "off") reasoningLevels.off = "none"; - else if (THINKING_LEVELS_ALL.includes(val)) { - reasoningLevels[val] = val; - added = true; - } - } - } - } - } - if (!added) { - reasoningLevels.low = "low"; - reasoningLevels.medium = "medium"; - reasoningLevels.high = "high"; - } - } - return { - ...m, - name: m.name || matched.name, - contextWindow: m.contextWindow || matched.limit?.context, - maxTokens: m.maxTokens || matched.limit?.output, - input: m.input || (hasImage ? ["text", "image"] : ["text"]), - reasoningEfforts: m.reasoningEfforts !== void 0 ? m.reasoningEfforts : hasReasoning ? reasoningLevels : false - }; + if (matched) count++; + return mergeModelCapabilities(m, matched); }); const ops = [{ op: "set", @@ -982,41 +438,41 @@ function ModelsFooterSettings({ ctx }) { setBatching(false); } }; - return import_react2.default.createElement( + return import_react.default.createElement( "div", { className: "me-footer-card" }, - import_react2.default.createElement( + import_react.default.createElement( "div", { className: "me-footer-title" }, - import_react2.default.createElement("svg", { + import_react.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" - }, import_react2.default.createElement("path", { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" })), - import_react2.default.createElement("span", null, getTranslation("enhancerTitle", isZh)) + }, import_react.default.createElement("path", { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" })), + import_react.default.createElement("span", null, getTranslation("enhancerTitle", isZh)) ), - import_react2.default.createElement("div", { + import_react.default.createElement("div", { className: "me-footer-desc" }, getTranslation("enhancerIntro", isZh)), - import_react2.default.createElement( + import_react.default.createElement( "div", { style: { display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" } }, - import_react2.default.createElement( + import_react.default.createElement( "label", { style: { display: "inline-flex", alignItems: "center", gap: "6px", fontSize: "13px", fontWeight: 500 } }, - import_react2.default.createElement("span", null, getTranslation("targetProvider", isZh)), - import_react2.default.createElement( + import_react.default.createElement("span", null, getTranslation("targetProvider", isZh)), + import_react.default.createElement( "select", { value: selectedProvider, @@ -1031,10 +487,10 @@ function ModelsFooterSettings({ ctx }) { fontSize: "13px" } }, - providerList.map((p) => import_react2.default.createElement("option", { key: p.provider, value: p.provider }, `${p.displayName || p.provider} (${p.settingsNs || "generic"})`)) + providerList.map((p) => import_react.default.createElement("option", { key: p.provider, value: p.provider }, `${p.displayName || p.provider} (${p.settingsNs || "generic"})`)) ) ), - import_react2.default.createElement( + import_react.default.createElement( "button", { type: "button", @@ -1058,7 +514,7 @@ function ModelsFooterSettings({ ctx }) { }, batching ? getTranslation("batchBtnBusy", isZh) : getTranslation("batchBtn", isZh) ), - import_react2.default.createElement("button", { + import_react.default.createElement("button", { type: "button", disabled: loading, onClick: loadProviders, @@ -1074,7 +530,7 @@ function ModelsFooterSettings({ ctx }) { } }, getTranslation("refreshSettings", isZh)) ), - statusMsg ? import_react2.default.createElement("div", { + statusMsg ? import_react.default.createElement("div", { style: { fontSize: "12px", padding: "6px 10px", @@ -1087,11 +543,11 @@ function ModelsFooterSettings({ ctx }) { } // src/client/ProviderCardExtras.tsx -var import_react3 = __toESM(require("react"), 1); +var import_react2 = __toESM(require("react"), 1); function ProviderCardExtras({ provider, configured, keyConfigured, ctx }) { const isZh = isZhLocale(); - const [matching, setMatching] = (0, import_react3.useState)(false); - const [notice, setNotice] = (0, import_react3.useState)(null); + const [matching, setMatching] = (0, import_react2.useState)(false); + const [notice, setNotice] = (0, import_react2.useState)(null); if (provider.settingsNs !== "llm-pi-ai") { return null; } @@ -1118,41 +574,8 @@ function ProviderCardExtras({ provider, configured, keyConfigured, ctx }) { const nextModels = models.map((m) => { if (!m || !m.id) return m; const matched = findDevModel(m.id, allDevModels); - if (!matched) return m; - count++; - const hasImage = Boolean(matched.modalities?.input?.includes("image")); - const hasReasoning = Boolean(matched.reasoning); - let reasoningLevels = void 0; - if (hasReasoning) { - reasoningLevels = { off: "none" }; - let added = false; - if (Array.isArray(matched.reasoning_options)) { - for (const opt of matched.reasoning_options) { - if (opt.type === "effort" && Array.isArray(opt.values)) { - for (const val of opt.values) { - if (val === "none" || val === "off") reasoningLevels.off = "none"; - else if (THINKING_LEVELS_ALL.includes(val)) { - reasoningLevels[val] = val; - added = true; - } - } - } - } - } - if (!added) { - reasoningLevels.low = "low"; - reasoningLevels.medium = "medium"; - reasoningLevels.high = "high"; - } - } - return { - ...m, - name: m.name || matched.name, - contextWindow: m.contextWindow || matched.limit?.context, - maxTokens: m.maxTokens || matched.limit?.output, - input: m.input || (hasImage ? ["text", "image"] : ["text"]), - reasoningEfforts: m.reasoningEfforts !== void 0 ? m.reasoningEfforts : hasReasoning ? reasoningLevels : false - }; + if (matched) count++; + return mergeModelCapabilities(m, matched); }); const ops = [{ op: "set", @@ -1177,7 +600,7 @@ function ProviderCardExtras({ provider, configured, keyConfigured, ctx }) { setMatching(false); } }; - return import_react3.default.createElement( + return import_react2.default.createElement( "div", { style: { @@ -1192,10 +615,10 @@ function ProviderCardExtras({ provider, configured, keyConfigured, ctx }) { gap: "8px" } }, - import_react3.default.createElement("span", { + import_react2.default.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #666)" } }, getTranslation("activeEnhancerNotice", isZh, { provider: provider.displayName })), - import_react3.default.createElement("button", { + import_react2.default.createElement("button", { type: "button", disabled: matching, onClick: handleAutoFillProvider, @@ -1212,7 +635,7 @@ function ProviderCardExtras({ provider, configured, keyConfigured, ctx }) { opacity: matching ? 0.7 : 1 } }, matching ? getTranslation("singleBtnBusy", isZh) : getTranslation("batchBtn", isZh)), - notice ? import_react3.default.createElement("span", { + notice ? import_react2.default.createElement("span", { style: { fontSize: "11px", color: notice.type === "ok" ? "#10b981" : "#ef4444", @@ -1228,9 +651,7 @@ var inject = [ "slots", "remote", "remote.settings", - "remote.llm", - "modelDirectories", - "sessions" + "remote.llm" ]; function apply(ctx) { if (typeof document !== "undefined" && !document.querySelector("style[data-plugin='dsh-model-enhancer']")) { @@ -1239,32 +660,6 @@ function apply(ctx) { tag.textContent = ENHANCER_CSS; document.head.appendChild(tag); } - ctx.inject(["slots"], (scope) => { - const sessions = ctx.get("sessions"); - scope.slots.inject("conversation.input.right", () => scope.slots.register({ - name: "conversation.input.right", - id: "me-codex-model-selector", - order: 100, - inject: (sessionId) => { - const dirSvc = ctx.get("modelDirectories"); - const directory = dirSvc ? dirSvc.directoryFor(sessionId) : null; - const available = sessions ? sessions.subagentAddress(sessionId) === void 0 : true; - return { - available: available && Boolean(directory), - directory: directory ? directory.store : { - subscribe: () => () => { - }, - getSnapshot: () => ({ status: "idle", groups: [], current: null }) - }, - load: () => { - if (available && directory) directory.load().catch(() => { - }); - }, - select: (selection) => available && directory ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false) - }; - } - }, CodexModelSelect)); - }); ctx.inject(["slots"], (scope) => { scope.slots.inject("settings.models.footer", () => scope.slots.register({ name: "settings.models.footer", diff --git a/lib/types/client/CodexModelSelect.d.ts b/lib/types/client/CodexModelSelect.d.ts deleted file mode 100644 index 97f551b..0000000 --- a/lib/types/client/CodexModelSelect.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -export interface CodexModelSelectProps { - directory: { - subscribe: (listener: () => void) => () => void; - getSnapshot: () => any; - }; - load: () => void; - select: (selection: { - provider: string; - model: string; - reasoningEffort?: string; - }) => Promise; - available: boolean; - locked?: boolean; -} -export declare function CodexModelSelect(props: CodexModelSelectProps): React.DetailedReactHTMLElement<{ - ref: React.MutableRefObject; - style: { - position: "relative"; - display: "inline-flex"; - alignItems: "center"; - }; -}, HTMLDivElement>; diff --git a/lib/types/client/modelsDev.d.ts b/lib/types/client/modelsDev.d.ts index b058194..a0e1f88 100644 --- a/lib/types/client/modelsDev.d.ts +++ b/lib/types/client/modelsDev.d.ts @@ -21,5 +21,7 @@ export interface DevModelInfo { } export declare const THINKING_LEVELS_ALL: string[]; export declare function fetchAllModelsDev(): Promise; +export declare function sanitizePositiveInt(val: unknown): number | undefined; +export declare function mergeModelCapabilities(m: any, matched?: DevModelInfo | null): any; export declare function normModelKey(key: string): string; export declare function findDevModel(queryId: string, allList: DevModelInfo[]): DevModelInfo | null; diff --git a/lib/types/client/styles.d.ts b/lib/types/client/styles.d.ts index 241e394..8757db3 100644 --- a/lib/types/client/styles.d.ts +++ b/lib/types/client/styles.d.ts @@ -1,11 +1 @@ -export declare const CODEX_LEVELS_ZH: { - id: string; - label: string; - short: string; -}[]; -export declare const CODEX_LEVELS_EN: { - id: string; - label: string; - short: string; -}[]; -export declare const ENHANCER_CSS = "\n/* Hide default trigger when Codex trigger is active */\ndiv[class*=\"_7KE1Ra_root\"], div[class*=\"ModelSelect_module_css_root\"] {\n display: none !important;\n}\n\n.me-codex-trigger {\n box-sizing: border-box;\n height: 28px;\n padding: 0 8px;\n border-radius: 14px;\n border: 1px solid transparent;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #61666b);\n font-size: 13px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n outline: none;\n transition: all 120ms ease;\n user-select: none;\n}\n.me-codex-trigger:hover:not(:disabled),\n.me-codex-trigger.active-open {\n border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.15));\n color: var(--dsw-alias-label-primary, #0f1115);\n}\nbody[data-ds-dark-theme] .me-codex-trigger:hover:not(:disabled),\nbody[data-ds-dark-theme] .me-codex-trigger.active-open {\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.2));\n color: var(--dsw-alias-label-primary, #fff);\n}\n.me-codex-trigger-chevron {\n transition: transform 140ms ease;\n opacity: 0.7;\n}\n.me-codex-trigger-chevron.open {\n transform: rotate(180deg);\n}\n\n.me-codex-popup {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n width: 280px;\n background: var(--dsw-specific-menu, #ffffff);\n border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n border-radius: 14px;\n padding: 10px 12px 12px;\n z-index: 1200;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\nbody[data-ds-dark-theme] .me-codex-popup {\n background: var(--dsw-specific-menu, #1b1d22);\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));\n box-shadow: 0 10px 36px rgba(0, 0, 0, 0.5), 0 2px 10px rgba(0, 0, 0, 0.3);\n}\n\n.me-codex-topbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.me-codex-icon-btn {\n box-sizing: border-box;\n width: 26px;\n height: 26px;\n border-radius: 6px;\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #64748b);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n padding: 0;\n}\n.me-codex-icon-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));\n color: var(--dsw-alias-label-primary, #0f172a);\n}\nbody[data-ds-dark-theme] .me-codex-icon-btn {\n color: var(--dsw-alias-label-secondary, #94a3b8);\n}\nbody[data-ds-dark-theme] .me-codex-icon-btn:hover {\n background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.08));\n color: #fff;\n}\n\n.me-codex-center {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n cursor: pointer;\n user-select: none;\n}\n.me-codex-effort-title {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #0f172a);\n display: flex;\n align-items: center;\n gap: 3px;\n}\nbody[data-ds-dark-theme] .me-codex-effort-title {\n color: #fff;\n}\n.me-codex-model-name {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #64748b);\n white-space: nowrap;\n max-width: 170px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.me-codex-model-name:hover {\n color: #2563eb;\n}\nbody[data-ds-dark-theme] .me-codex-model-name {\n color: var(--dsw-alias-label-tertiary, #94a3b8);\n}\nbody[data-ds-dark-theme] .me-codex-model-name:hover {\n color: #60a5fa;\n}\n\n.me-slider-track-wrap {\n box-sizing: border-box;\n padding: 4px 0 2px;\n cursor: pointer;\n user-select: none;\n}\n.me-slider-track {\n position: relative;\n height: 8px;\n background: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n border-radius: 4px;\n display: flex;\n align-items: center;\n}\nbody[data-ds-dark-theme] .me-slider-track {\n background: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.12));\n}\n\n.me-slider-active-fill {\n position: absolute;\n left: 0;\n height: 100%;\n border-radius: 4px;\n background: #3b82f6;\n pointer-events: none;\n transition: width 120ms ease;\n}\n\n.me-slider-dots-layer {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0 14px;\n pointer-events: none;\n}\n.me-slider-dot {\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2));\n}\nbody[data-ds-dark-theme] .me-slider-dot {\n background: rgba(255, 255, 255, 0.25);\n}\n.me-slider-dot.lit {\n background: #fff;\n}\n\n.me-slider-thumb {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: #ffffff;\n border: 2px solid #3b82f6;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);\n pointer-events: none;\n transition: left 120ms ease;\n}\nbody[data-ds-dark-theme] .me-slider-thumb {\n background: #1e293b;\n border-color: #60a5fa;\n}\n\n.me-model-picker-list {\n max-height: 210px;\n overflow-y: auto;\n padding-right: 2px;\n}\n.me-model-group-section {\n margin-top: 8px;\n padding-top: 6px;\n border-top: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n}\n.me-model-group-section:first-child {\n margin-top: 0;\n padding-top: 0;\n border-top: none;\n}\n.me-model-picker-group {\n font-size: 11px;\n color: #2563eb;\n font-weight: 600;\n padding: 3px 6px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: rgba(37, 99, 235, 0.08);\n border-radius: 4px;\n margin-bottom: 4px;\n}\nbody[data-ds-dark-theme] .me-model-picker-group {\n color: #60a5fa;\n background: rgba(59, 130, 246, 0.08);\n}\n.me-model-picker-item {\n box-sizing: border-box;\n width: 100%;\n padding: 6px 8px;\n border-radius: 6px;\n background: transparent;\n border: none;\n color: var(--dsw-alias-label-primary, #0f1115);\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n transition: all 120ms ease;\n}\n.me-model-picker-item:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));\n}\n.me-model-picker-item.active {\n background: rgba(37, 99, 235, 0.1);\n color: #2563eb;\n font-weight: 500;\n}\nbody[data-ds-dark-theme] .me-model-picker-item {\n color: var(--dsw-alias-label-primary, #e2e8f0);\n}\nbody[data-ds-dark-theme] .me-model-picker-item:hover {\n background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06));\n}\nbody[data-ds-dark-theme] .me-model-picker-item.active {\n background: rgba(56, 189, 248, 0.12);\n color: #38bdf8;\n}\n\n/* Footer Section */\n.me-footer-card {\n box-sizing: border-box;\n margin-top: 16px;\n padding: 16px;\n border-radius: 12px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.02));\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\nbody[data-ds-dark-theme] .me-footer-card {\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));\n background: var(--dsw-alias-surface-l1, rgba(255, 255, 255, 0.02));\n}\n.me-footer-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #0f1115);\n display: flex;\n align-items: center;\n gap: 8px;\n}\nbody[data-ds-dark-theme] .me-footer-title {\n color: #fff;\n}\n.me-footer-desc {\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, #61666b);\n line-height: 20px;\n}\n.me-diff-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n.me-diff-table th, .me-diff-table td {\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n}\nbody[data-ds-dark-theme] .me-diff-table th, body[data-ds-dark-theme] .me-diff-table td {\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));\n}\n.me-val-new {\n color: var(--dsw-alias-state-success-primary, #10b981);\n font-weight: 500;\n}\n"; +export declare const ENHANCER_CSS = "\n.me-footer-card {\n box-sizing: border-box;\n margin-top: 16px;\n padding: 16px;\n border-radius: 12px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n background: var(--dsw-alias-bg-module-platform, rgba(0, 0, 0, 0.02));\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\nbody[data-ds-dark-theme] .me-footer-card {\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));\n background: var(--dsw-alias-surface-l1, rgba(255, 255, 255, 0.02));\n}\n.me-footer-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #0f1115);\n display: flex;\n align-items: center;\n gap: 8px;\n}\nbody[data-ds-dark-theme] .me-footer-title {\n color: #fff;\n}\n.me-footer-desc {\n font-size: 13px;\n color: var(--dsw-alias-label-secondary, #61666b);\n line-height: 20px;\n}\n.me-diff-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n.me-diff-table th, .me-diff-table td {\n padding: 6px 8px;\n border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08));\n}\nbody[data-ds-dark-theme] .me-diff-table th, body[data-ds-dark-theme] .me-diff-table td {\n border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1));\n}\n.me-val-new {\n color: var(--dsw-alias-state-success-primary, #10b981);\n font-weight: 500;\n}\n"; diff --git a/package.json b/package.json index b706bdf..d8a0abd 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "@deepseek-ai/dsh-client-ui-settings", "@deepseek-ai/dsh-client-ui-settings-models", "@deepseek-ai/dsh-client-ui-slots", - "@deepseek-ai/dsh-client-ui-model-selection", "@deepseek-ai/dsh-api-remotes" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..aaaa1ca --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1090 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@deepseek-ai/cordis': + specifier: ^4.0.2 + version: 4.0.2 + devDependencies: + '@types/node': + specifier: ^22.20.2 + version: 22.20.2 + '@types/react': + specifier: ^18.3.1 + version: 18.3.31 + esbuild: + specifier: ^0.28.2 + version: 0.28.2 + typescript: + specifier: ^5.8.2 + version: 5.9.3 + vitest: + specifier: ^3.0.0 + version: 3.2.7(@types/node@22.20.2) + +packages: + + '@deepseek-ai/cordis@4.0.2': + resolution: {integrity: sha512-asOnXP1TzFSFQlHb1iegDZp0z/8WD1c7YNrwJR/Tx2bzNuMXfcekE/I67Iv6SQXeLB4csxqCngzQKANP7gdw0g==} + hasBin: true + peerDependencies: + '@deepseek-ai/cordis-plugin-include': ^1.0.7 + '@deepseek-ai/cordis-plugin-loader': ^1.0.3 + peerDependenciesMeta: + '@deepseek-ai/cordis-plugin-include': + optional: true + '@deepseek-ai/cordis-plugin-loader': + optional: true + + '@deepseek-ai/cosmokit@1.8.3': + resolution: {integrity: sha512-qBo+ronVM6Eu2WNVJXi8JcMiqZ19T9BRIpV+5qJUFPXjGH/Z0QKcQMC/IZJ7L394YTOtJgcovbk9qP0w2GsBXQ==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-android-arm-eabi@4.63.1': + resolution: {integrity: sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.63.1': + resolution: {integrity: sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.63.1': + resolution: {integrity: sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.63.1': + resolution: {integrity: sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.63.1': + resolution: {integrity: sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.63.1': + resolution: {integrity: sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + resolution: {integrity: sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + resolution: {integrity: sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + resolution: {integrity: sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.63.1': + resolution: {integrity: sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + resolution: {integrity: sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.63.1': + resolution: {integrity: sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + resolution: {integrity: sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + resolution: {integrity: sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + resolution: {integrity: sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + resolution: {integrity: sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + resolution: {integrity: sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.63.1': + resolution: {integrity: sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.63.1': + resolution: {integrity: sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.63.1': + resolution: {integrity: sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.63.1': + resolution: {integrity: sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + resolution: {integrity: sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + resolution: {integrity: sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.63.1': + resolution: {integrity: sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.63.1': + resolution: {integrity: sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/node@22.20.2': + resolution: {integrity: sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react@18.3.31': + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + + '@vitest/expect@3.2.7': + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} + + '@vitest/mocker@3.2.7': + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.7': + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==} + + '@vitest/snapshot@3.2.7': + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + + '@vitest/spy@3.2.7': + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} + + '@vitest/utils@3.2.7': + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.63.1: + resolution: {integrity: sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.7: + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.7 + '@vitest/ui': 3.2.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + +snapshots: + + '@deepseek-ai/cordis@4.0.2': + dependencies: + '@deepseek-ai/cosmokit': 1.8.3 + '@standard-schema/spec': 1.1.0 + + '@deepseek-ai/cosmokit@1.8.3': {} + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@rollup/rollup-android-arm-eabi@4.63.1': + optional: true + + '@rollup/rollup-android-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.63.1': + optional: true + + '@rollup/rollup-darwin-x64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.63.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.63.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.63.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.63.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.63.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.63.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.63.1': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/node@22.20.2': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react@18.3.31': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@vitest/expect@3.2.7': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@22.20.2))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(@types/node@22.20.2) + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.7': + dependencies: + '@vitest/utils': 3.2.7 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.7': + dependencies: + tinyspy: 4.0.6 + + '@vitest/utils@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + check-error@2.1.3: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.4.0: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + js-tokens@9.0.1: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.63.1: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.63.1 + '@rollup/rollup-android-arm64': 4.63.1 + '@rollup/rollup-darwin-arm64': 4.63.1 + '@rollup/rollup-darwin-x64': 4.63.1 + '@rollup/rollup-freebsd-arm64': 4.63.1 + '@rollup/rollup-freebsd-x64': 4.63.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.63.1 + '@rollup/rollup-linux-arm-musleabihf': 4.63.1 + '@rollup/rollup-linux-arm64-gnu': 4.63.1 + '@rollup/rollup-linux-arm64-musl': 4.63.1 + '@rollup/rollup-linux-loong64-gnu': 4.63.1 + '@rollup/rollup-linux-loong64-musl': 4.63.1 + '@rollup/rollup-linux-ppc64-gnu': 4.63.1 + '@rollup/rollup-linux-ppc64-musl': 4.63.1 + '@rollup/rollup-linux-riscv64-gnu': 4.63.1 + '@rollup/rollup-linux-riscv64-musl': 4.63.1 + '@rollup/rollup-linux-s390x-gnu': 4.63.1 + '@rollup/rollup-linux-x64-gnu': 4.63.1 + '@rollup/rollup-linux-x64-musl': 4.63.1 + '@rollup/rollup-openbsd-x64': 4.63.1 + '@rollup/rollup-openharmony-arm64': 4.63.1 + '@rollup/rollup-win32-arm64-msvc': 4.63.1 + '@rollup/rollup-win32-ia32-msvc': 4.63.1 + '@rollup/rollup-win32-x64-gnu': 4.63.1 + '@rollup/rollup-win32-x64-msvc': 4.63.1 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.6: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + vite-node@3.2.4(@types/node@22.20.2): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.6(@types/node@22.20.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.6(@types/node@22.20.2): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + postcss: 8.5.28 + rollup: 4.63.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.2 + fsevents: 2.3.3 + + vitest@3.2.7(@types/node@22.20.2): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@22.20.2)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.6(@types/node@22.20.2) + vite-node: 3.2.4(@types/node@22.20.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.20.2 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..00f6fc4 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: set this to true or false diff --git a/src/client/CodexModelSelect.tsx b/src/client/CodexModelSelect.tsx deleted file mode 100644 index bada39c..0000000 --- a/src/client/CodexModelSelect.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import React, { useState, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'; -import { CODEX_LEVELS_ZH, CODEX_LEVELS_EN } from './styles'; -import { isZhLocale } from './i18n'; - -export interface CodexModelSelectProps { - directory: { - subscribe: (listener: () => void) => () => void; - getSnapshot: () => any; - }; - load: () => void; - select: (selection: { provider: string; model: string; reasoningEffort?: string }) => Promise; - available: boolean; - locked?: boolean; -} - -export function CodexModelSelect(props: CodexModelSelectProps) { - const { directory, load, select, available, locked } = props; - const state = useSyncExternalStore(directory.subscribe, directory.getSnapshot); - const [open, setOpen] = useState(false); - const [showModelPicker, setShowModelPicker] = useState(false); - const rootRef = useRef(null); - const isZh = isZhLocale(); - const codexLevels = isZh ? CODEX_LEVELS_ZH : CODEX_LEVELS_EN; - - useEffect(() => { - if (available) load(); - }, [available]); - - useEffect(() => { - if (!open) return; - const onMouseDown = (e: MouseEvent) => { - if (!rootRef.current?.contains(e.target as Node)) { - setOpen(false); - setShowModelPicker(false); - } - }; - document.addEventListener("mousedown", onMouseDown); - return () => document.removeEventListener("mousedown", onMouseDown); - }, [open]); - - const choices = useMemo(() => (state.groups || []).flatMap((g: any) => (g.models || []).map((m: any) => ({ - group: g, - model: m - }))), [state.groups]); - - const currentChoice = choices.find((c: any) => c.group.id === state.current?.provider && c.model.id === state.current?.model); - const currentModel = currentChoice?.model; - const reasoning = currentModel?.reasoning; - - const availableEfforts = useMemo(() => { - if (!reasoning || !reasoning.efforts || reasoning.efforts.length === 0) return []; - return reasoning.efforts.map((e: any) => { - const found = codexLevels.find((l) => l.id === e.id); - return { - id: e.id, - label: found?.label || e.name || e.id, - short: found?.short || e.name || e.id - }; - }); - }, [reasoning, isZh]); - - const effectiveEffort = state.current?.reasoningEffort ?? reasoning?.defaultEffort; - const currentEffortIndex = useMemo(() => { - if (availableEfforts.length === 0) return 0; - const idx = availableEfforts.findIndex((e: any) => e.id === effectiveEffort); - return idx === -1 ? 0 : idx; - }, [availableEfforts, effectiveEffort]); - - const currentEffortObj = availableEfforts[currentEffortIndex]; - const effortDisplayLabel = currentEffortObj ? currentEffortObj.label : (isZh ? "中" : "Medium"); - - const triggerModelName = currentModel?.name || (isZh ? "选择模型" : "Select model"); - const triggerEffortLabel = reasoning ? (currentEffortObj ? currentEffortObj.short : (isZh ? "默认" : "Default")) : null; - - const onSelectEffortByIndex = (idx: number) => { - if (!currentChoice || !availableEfforts[idx]) return; - const effort = availableEfforts[idx].id; - select({ - provider: currentChoice.group.id, - model: currentChoice.model.id, - reasoningEffort: effort - }); - }; - - const onResetDefault = () => { - if (!currentChoice) return; - select({ - provider: currentChoice.group.id, - model: currentChoice.model.id, - reasoningEffort: reasoning?.defaultEffort - }); - }; - - const onTrackClick = (e: React.MouseEvent) => { - if (availableEfforts.length <= 1) return; - const rect = e.currentTarget.getBoundingClientRect(); - const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); - const idx = Math.round(ratio * (availableEfforts.length - 1)); - onSelectEffortByIndex(idx); - }; - - if (!available) return null; - - return React.createElement("div", { - ref: rootRef, - style: { position: "relative", display: "inline-flex", alignItems: "center" } - }, - React.createElement("button", { - type: "button", - className: `me-codex-trigger ${open ? "active-open" : ""}`, - disabled: locked, - onClick: () => setOpen(!open) - }, - React.createElement("span", null, triggerModelName), - triggerEffortLabel ? React.createElement("span", { - style: { color: "#3b82f6", fontWeight: 600 } - }, "· ", triggerEffortLabel) : null, - React.createElement("svg", { - className: `me-codex-trigger-chevron ${open ? "open" : ""}`, - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "1.8" - }, React.createElement("path", { d: "M4 6l4 4 4-4" })) - ), - open ? React.createElement("div", { - className: "me-codex-popup" - }, - React.createElement("div", { - className: "me-codex-topbar" - }, - React.createElement("div", { - className: "me-codex-icon-btn", - title: isZh ? "推理强度" : "Reasoning Effort" - }, - React.createElement("svg", { - width: "16", - height: "16", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - strokeWidth: "2" - }, React.createElement("path", { d: "M13 2L3 14h9l-1 8 10-12h-9l1-8z" })) - ), - React.createElement("div", { - className: "me-codex-center", - onClick: () => setShowModelPicker(!showModelPicker), - title: isZh ? "点击切换模型" : "Click to switch model" - }, - React.createElement("div", { - className: "me-codex-effort-title" - }, - effortDisplayLabel, - React.createElement("svg", { - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "2", - style: { transform: showModelPicker ? "rotate(90deg)" : undefined, transition: "transform 140ms ease" } - }, React.createElement("path", { d: "M6 3.5L10.5 8L6 12.5" })) - ), - React.createElement("div", { - className: "me-codex-model-name" - }, currentModel?.name || (isZh ? "选择模型" : "Select model")) - ), - React.createElement("button", { - type: "button", - className: "me-codex-icon-btn", - title: isZh ? "恢复默认档位" : "Reset to default effort", - onClick: onResetDefault - }, - React.createElement("svg", { - width: "16", - height: "16", - viewBox: "0 0 24 24", - fill: "none", - stroke: "currentColor", - strokeWidth: "2" - }, React.createElement("path", { d: "M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8m0 0V3m0 5h5" })) - ) - ), - showModelPicker ? React.createElement("div", { - className: "me-model-picker-list" - }, - (state.groups || []).map((group: any) => React.createElement("div", { - key: group.id, - className: "me-model-group-section" - }, - React.createElement("div", { - className: "me-model-picker-group" - }, - React.createElement("svg", { - width: "12", - height: "12", - viewBox: "0 0 16 16", - fill: "none", - stroke: "currentColor", - strokeWidth: "1.8" - }, React.createElement("path", { d: "M2 4h12M2 8h12M2 12h8" })), - group.name - ), - (group.models || []).map((m: any) => { - const active = state.current?.provider === group.id && state.current?.model === m.id; - return React.createElement("button", { - key: m.id, - type: "button", - className: `me-model-picker-item ${active ? "active" : ""}`, - onClick: () => { - select({ - provider: group.id, - model: m.id, - reasoningEffort: m.reasoning?.defaultEffort - }); - setShowModelPicker(false); - } - }, - React.createElement("span", null, m.name), - active ? React.createElement("svg", { - width: "14", - height: "14", - viewBox: "0 0 16 16", - fill: "none", - stroke: "#38bdf8", - strokeWidth: "2" - }, React.createElement("path", { d: "M3.5 8.5l3 3 6-6" })) : null - ); - }) - )) - ) : React.createElement("div", { - className: "me-slider-track-wrap", - onClick: onTrackClick - }, - React.createElement("div", { - className: "me-slider-track" - }, - React.createElement("div", { - className: "me-slider-active-fill", - style: { - width: availableEfforts.length <= 1 ? "100%" : `calc(16px + (100% - 32px) * ${currentEffortIndex / (availableEfforts.length - 1)})` - } - }), - React.createElement("div", { - className: "me-slider-dots-layer" - }, - availableEfforts.map((eff: any, i: number) => React.createElement("div", { - key: eff.id, - className: `me-slider-dot ${i <= currentEffortIndex ? "lit" : ""}` - })) - ), - React.createElement("div", { - className: "me-slider-thumb", - style: { - left: availableEfforts.length <= 1 ? "calc(100% - 16px)" : `calc(16px + (100% - 32px) * ${currentEffortIndex / (availableEfforts.length - 1)})` - } - }) - ) - ) - ) : null - ); -} diff --git a/src/client/ModelsFooterSettings.tsx b/src/client/ModelsFooterSettings.tsx index e180e56..8e2f84b 100644 --- a/src/client/ModelsFooterSettings.tsx +++ b/src/client/ModelsFooterSettings.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo } from 'react'; -import { fetchAllModelsDev, findDevModel, THINKING_LEVELS_ALL, DevModelInfo } from './modelsDev'; +import { fetchAllModelsDev, findDevModel, mergeModelCapabilities } from './modelsDev'; import { isZhLocale, getTranslation } from './i18n'; export interface ModelsFooterSettingsProps { @@ -63,43 +63,8 @@ export function ModelsFooterSettings({ ctx }: ModelsFooterSettingsProps) { const nextModels = models.map((m: any) => { if (!m || !m.id) return m; const matched = findDevModel(m.id, allDevModels); - if (!matched) return m; - count++; - const hasImage = Boolean(matched.modalities?.input?.includes('image')); - const hasReasoning = Boolean(matched.reasoning); - let reasoningLevels: any = undefined; - - if (hasReasoning) { - reasoningLevels = { off: "none" }; - let added = false; - if (Array.isArray(matched.reasoning_options)) { - for (const opt of matched.reasoning_options) { - if (opt.type === "effort" && Array.isArray(opt.values)) { - for (const val of opt.values) { - if (val === "none" || val === "off") reasoningLevels.off = "none"; - else if (THINKING_LEVELS_ALL.includes(val)) { - reasoningLevels[val] = val; - added = true; - } - } - } - } - } - if (!added) { - reasoningLevels.low = "low"; - reasoningLevels.medium = "medium"; - reasoningLevels.high = "high"; - } - } - - return { - ...m, - name: m.name || matched.name, - contextWindow: m.contextWindow || matched.limit?.context, - maxTokens: m.maxTokens || matched.limit?.output, - input: m.input || (hasImage ? ["text", "image"] : ["text"]), - reasoningEfforts: m.reasoningEfforts !== undefined ? m.reasoningEfforts : (hasReasoning ? reasoningLevels : false) - }; + if (matched) count++; + return mergeModelCapabilities(m, matched); }); // revision-safe mutate diff --git a/src/client/ProviderCardExtras.tsx b/src/client/ProviderCardExtras.tsx index e5101ad..9ab6809 100644 --- a/src/client/ProviderCardExtras.tsx +++ b/src/client/ProviderCardExtras.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { fetchAllModelsDev, findDevModel, THINKING_LEVELS_ALL, DevModelInfo } from './modelsDev'; +import { fetchAllModelsDev, findDevModel, mergeModelCapabilities } from './modelsDev'; import { isZhLocale, getTranslation } from './i18n'; export interface ProviderCardExtrasProps { @@ -54,43 +54,8 @@ export function ProviderCardExtras({ provider, configured, keyConfigured, ctx }: const nextModels = models.map((m: any) => { if (!m || !m.id) return m; const matched = findDevModel(m.id, allDevModels); - if (!matched) return m; - count++; - const hasImage = Boolean(matched.modalities?.input?.includes('image')); - const hasReasoning = Boolean(matched.reasoning); - let reasoningLevels: any = undefined; - - if (hasReasoning) { - reasoningLevels = { off: "none" }; - let added = false; - if (Array.isArray(matched.reasoning_options)) { - for (const opt of matched.reasoning_options) { - if (opt.type === "effort" && Array.isArray(opt.values)) { - for (const val of opt.values) { - if (val === "none" || val === "off") reasoningLevels.off = "none"; - else if (THINKING_LEVELS_ALL.includes(val)) { - reasoningLevels[val] = val; - added = true; - } - } - } - } - } - if (!added) { - reasoningLevels.low = "low"; - reasoningLevels.medium = "medium"; - reasoningLevels.high = "high"; - } - } - - return { - ...m, - name: m.name || matched.name, - contextWindow: m.contextWindow || matched.limit?.context, - maxTokens: m.maxTokens || matched.limit?.output, - input: m.input || (hasImage ? ["text", "image"] : ["text"]), - reasoningEfforts: m.reasoningEfforts !== undefined ? m.reasoningEfforts : (hasReasoning ? reasoningLevels : false) - }; + if (matched) count++; + return mergeModelCapabilities(m, matched); }); const ops = [{ diff --git a/src/client/i18n.ts b/src/client/i18n.ts index 52100d7..c4e1a37 100644 --- a/src/client/i18n.ts +++ b/src/client/i18n.ts @@ -54,21 +54,7 @@ export const I18N_DICT: { zh: TranslationMap; en: TranslationMap } = { thinkingOff: "关闭思考", thinkingOn: "开启", cancel: "取消", - applySelected: "应用已选推荐项", - - // Codex Slider - effortOff: "关", - effortMinimal: "极低", - effortLow: "低", - effortMedium: "中", - effortHigh: "高", - effortXHigh: "超高", - effortMax: "最大", - effortReasoningTitle: "推理强度", - effortSelectModelTip: "点击切换模型", - effortResetTip: "恢复默认档位", - effortChooseModel: "选择模型", - commandsTitle: "命令菜单 (/)" + applySelected: "应用已选推荐项" }, en: { // ModelListEditor & Badges @@ -121,21 +107,7 @@ export const I18N_DICT: { zh: TranslationMap; en: TranslationMap } = { thinkingOff: "Disabled", thinkingOn: "Enabled", cancel: "Cancel", - applySelected: "Apply Selected", - - // Codex Slider - effortOff: "Off", - effortMinimal: "Minimal", - effortLow: "Low", - effortMedium: "Medium", - effortHigh: "High", - effortXHigh: "X-High", - effortMax: "Max", - effortReasoningTitle: "Reasoning Effort", - effortSelectModelTip: "Click to switch model", - effortResetTip: "Reset to default effort", - effortChooseModel: "Select model", - commandsTitle: "Commands (/)" + applySelected: "Apply Selected" } }; diff --git a/src/client/index.ts b/src/client/index.ts index a9ec8d3..05a8669 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,6 +1,5 @@ import React from 'react'; import { ENHANCER_CSS } from './styles'; -import { CodexModelSelect } from './CodexModelSelect'; import { ModelsFooterSettings } from './ModelsFooterSettings'; import { ProviderCardExtras } from './ProviderCardExtras'; @@ -10,9 +9,7 @@ export const inject = [ 'slots', 'remote', 'remote.settings', - 'remote.llm', - 'modelDirectories', - 'sessions' + 'remote.llm' ]; export function apply(ctx: any) { @@ -24,35 +21,7 @@ export function apply(ctx: any) { document.head.appendChild(tag); } - // 1. Register Codex-style Model and Reasoning Selector in conversation.input.right - ctx.inject(['slots'], (scope: any) => { - const sessions = ctx.get('sessions'); - scope.slots.inject('conversation.input.right', () => scope.slots.register({ - name: 'conversation.input.right', - id: 'me-codex-model-selector', - order: 100, - inject: (sessionId: string) => { - const dirSvc = ctx.get('modelDirectories'); - const directory = dirSvc ? dirSvc.directoryFor(sessionId) : null; - const available = sessions ? sessions.subagentAddress(sessionId) === undefined : true; - return { - available: available && Boolean(directory), - directory: directory ? directory.store : { - subscribe: () => () => {}, - getSnapshot: () => ({ status: 'idle', groups: [], current: null }) - }, - load: () => { - if (available && directory) directory.load().catch(() => {}); - }, - select: (selection: any) => (available && directory) - ? directory.select(selection).then(() => true, () => false) - : Promise.resolve(false) - }; - } - }, CodexModelSelect)); - }); - - // 2. Register into official settings extension slot: settings.models.footer + // 1. Register into official settings extension slot: settings.models.footer ctx.inject(['slots'], (scope: any) => { scope.slots.inject('settings.models.footer', () => scope.slots.register({ name: 'settings.models.footer', @@ -62,7 +31,7 @@ export function apply(ctx: any) { }, ModelsFooterSettings)); }); - // 3. Register into official settings extension slot: settings.models.provider-card for llm-pi-ai + // 2. Register into official settings extension slot: settings.models.provider-card for llm-pi-ai ctx.inject(['slots'], (scope: any) => { scope.slots.inject('settings.models.provider-card', () => scope.slots.register({ name: 'settings.models.provider-card', diff --git a/src/client/modelsDev.ts b/src/client/modelsDev.ts index f521c8d..ff09278 100644 --- a/src/client/modelsDev.ts +++ b/src/client/modelsDev.ts @@ -68,6 +68,90 @@ export async function fetchAllModelsDev(): Promise { } } +export function sanitizePositiveInt(val: unknown): number | undefined { + if (typeof val === 'number' && Number.isFinite(val) && Math.floor(val) === val && val >= 1) { + return val; + } + if (typeof val === 'string' && /^\d+$/.test(val.trim())) { + const parsed = parseInt(val.trim(), 10); + if (Number.isFinite(parsed) && parsed >= 1) { + return parsed; + } + } + return undefined; +} + +export function mergeModelCapabilities(m: any, matched?: DevModelInfo | null): any { + if (!m) return m; + if (!matched) { + const result: any = { ...m }; + const validExistingContext = sanitizePositiveInt(m.contextWindow); + if (validExistingContext !== undefined) { + result.contextWindow = validExistingContext; + } else { + delete result.contextWindow; + } + + const validExistingMaxTokens = sanitizePositiveInt(m.maxTokens); + if (validExistingMaxTokens !== undefined) { + result.maxTokens = validExistingMaxTokens; + } else { + delete result.maxTokens; + } + return result; + } + + const hasImage = Boolean(matched.modalities?.input?.includes('image')); + const hasReasoning = Boolean(matched.reasoning); + let reasoningLevels: any = undefined; + + if (hasReasoning) { + reasoningLevels = { off: "none" }; + let added = false; + if (Array.isArray(matched.reasoning_options)) { + for (const opt of matched.reasoning_options) { + if (opt.type === "effort" && Array.isArray(opt.values)) { + for (const val of opt.values) { + if (val === "none" || val === "off") reasoningLevels.off = "none"; + else if (THINKING_LEVELS_ALL.includes(val)) { + reasoningLevels[val] = val; + added = true; + } + } + } + } + } + if (!added) { + reasoningLevels.low = "low"; + reasoningLevels.medium = "medium"; + reasoningLevels.high = "high"; + } + } + + const result: any = { + ...m, + name: m.name || matched.name, + input: m.input || (hasImage ? ["text", "image"] : ["text"]), + reasoningEfforts: m.reasoningEfforts !== undefined ? m.reasoningEfforts : (hasReasoning ? reasoningLevels : false) + }; + + const resolvedContext = sanitizePositiveInt(m.contextWindow) ?? sanitizePositiveInt(matched.limit?.context); + if (resolvedContext !== undefined) { + result.contextWindow = resolvedContext; + } else { + delete result.contextWindow; + } + + const resolvedMaxTokens = sanitizePositiveInt(m.maxTokens) ?? sanitizePositiveInt(matched.limit?.output); + if (resolvedMaxTokens !== undefined) { + result.maxTokens = resolvedMaxTokens; + } else { + delete result.maxTokens; + } + + return result; +} + export function normModelKey(key: string): string { if (!key || typeof key !== "string") return ""; return key.toLowerCase() diff --git a/src/client/styles.ts b/src/client/styles.ts index fa86deb..1f292ed 100644 --- a/src/client/styles.ts +++ b/src/client/styles.ts @@ -1,295 +1,5 @@ -export const CODEX_LEVELS_ZH = [ - { id: "off", label: "关", short: "关" }, - { id: "minimal", label: "极低", short: "极低" }, - { id: "low", label: "低", short: "低" }, - { id: "medium", label: "中", short: "中" }, - { id: "high", label: "高", short: "高" }, - { id: "xhigh", label: "超高", short: "超高" }, - { id: "max", label: "最大", short: "最大" } -]; - -export const CODEX_LEVELS_EN = [ - { id: "off", label: "Off", short: "Off" }, - { id: "minimal", label: "Minimal", short: "Minimal" }, - { id: "low", label: "Low", short: "Low" }, - { id: "medium", label: "Medium", short: "Medium" }, - { id: "high", label: "High", short: "High" }, - { id: "xhigh", label: "X-High", short: "X-High" }, - { id: "max", label: "Max", short: "Max" } -]; - -export const ENHANCER_CSS = ` -/* Hide default trigger when Codex trigger is active */ -div[class*="_7KE1Ra_root"], div[class*="ModelSelect_module_css_root"] { - display: none !important; -} - -.me-codex-trigger { - box-sizing: border-box; - height: 28px; - padding: 0 8px; - border-radius: 14px; - border: 1px solid transparent; - background: transparent; - color: var(--dsw-alias-label-secondary, #61666b); - font-size: 13px; - font-weight: 500; - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; - outline: none; - transition: all 120ms ease; - user-select: none; -} -.me-codex-trigger:hover:not(:disabled), -.me-codex-trigger.active-open { - border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.15)); - color: var(--dsw-alias-label-primary, #0f1115); -} -body[data-ds-dark-theme] .me-codex-trigger:hover:not(:disabled), -body[data-ds-dark-theme] .me-codex-trigger.active-open { - border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.2)); - color: var(--dsw-alias-label-primary, #fff); -} -.me-codex-trigger-chevron { - transition: transform 140ms ease; - opacity: 0.7; -} -.me-codex-trigger-chevron.open { - transform: rotate(180deg); -} - -.me-codex-popup { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - width: 280px; - background: var(--dsw-specific-menu, #ffffff); - border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06); - border-radius: 14px; - padding: 10px 12px 12px; - z-index: 1200; - display: flex; - flex-direction: column; - gap: 8px; -} -body[data-ds-dark-theme] .me-codex-popup { - background: var(--dsw-specific-menu, #1b1d22); - border-color: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.1)); - box-shadow: 0 10px 36px rgba(0, 0, 0, 0.5), 0 2px 10px rgba(0, 0, 0, 0.3); -} - -.me-codex-topbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} -.me-codex-icon-btn { - box-sizing: border-box; - width: 26px; - height: 26px; - border-radius: 6px; - border: none; - background: transparent; - color: var(--dsw-alias-label-secondary, #64748b); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; -} -.me-codex-icon-btn:hover { - background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05)); - color: var(--dsw-alias-label-primary, #0f172a); -} -body[data-ds-dark-theme] .me-codex-icon-btn { - color: var(--dsw-alias-label-secondary, #94a3b8); -} -body[data-ds-dark-theme] .me-codex-icon-btn:hover { - background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.08)); - color: #fff; -} - -.me-codex-center { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - cursor: pointer; - user-select: none; -} -.me-codex-effort-title { - font-size: 13px; - font-weight: 600; - color: var(--dsw-alias-label-primary, #0f172a); - display: flex; - align-items: center; - gap: 3px; -} -body[data-ds-dark-theme] .me-codex-effort-title { - color: #fff; -} -.me-codex-model-name { - font-size: 11px; - color: var(--dsw-alias-label-tertiary, #64748b); - white-space: nowrap; - max-width: 170px; - overflow: hidden; - text-overflow: ellipsis; -} -.me-codex-model-name:hover { - color: #2563eb; -} -body[data-ds-dark-theme] .me-codex-model-name { - color: var(--dsw-alias-label-tertiary, #94a3b8); -} -body[data-ds-dark-theme] .me-codex-model-name:hover { - color: #60a5fa; -} - -.me-slider-track-wrap { - box-sizing: border-box; - padding: 4px 0 2px; - cursor: pointer; - user-select: none; -} -.me-slider-track { - position: relative; - height: 8px; - background: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); - border-radius: 4px; - display: flex; - align-items: center; -} -body[data-ds-dark-theme] .me-slider-track { - background: var(--dsw-alias-border-l3, rgba(255, 255, 255, 0.12)); -} - -.me-slider-active-fill { - position: absolute; - left: 0; - height: 100%; - border-radius: 4px; - background: #3b82f6; - pointer-events: none; - transition: width 120ms ease; -} - -.me-slider-dots-layer { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 14px; - pointer-events: none; -} -.me-slider-dot { - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--dsw-alias-border-l3, rgba(0, 0, 0, 0.2)); -} -body[data-ds-dark-theme] .me-slider-dot { - background: rgba(255, 255, 255, 0.25); -} -.me-slider-dot.lit { - background: #fff; -} - -.me-slider-thumb { - position: absolute; - top: 50%; - transform: translate(-50%, -50%); - width: 16px; - height: 16px; - border-radius: 50%; - background: #ffffff; - border: 2px solid #3b82f6; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); - pointer-events: none; - transition: left 120ms ease; -} -body[data-ds-dark-theme] .me-slider-thumb { - background: #1e293b; - border-color: #60a5fa; -} - -.me-model-picker-list { - max-height: 210px; - overflow-y: auto; - padding-right: 2px; -} -.me-model-group-section { - margin-top: 8px; - padding-top: 6px; - border-top: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.08)); -} -.me-model-group-section:first-child { - margin-top: 0; - padding-top: 0; - border-top: none; -} -.me-model-picker-group { - font-size: 11px; - color: #2563eb; - font-weight: 600; - padding: 3px 6px; - text-transform: uppercase; - letter-spacing: 0.05em; - display: inline-flex; - align-items: center; - gap: 4px; - background: rgba(37, 99, 235, 0.08); - border-radius: 4px; - margin-bottom: 4px; -} -body[data-ds-dark-theme] .me-model-picker-group { - color: #60a5fa; - background: rgba(59, 130, 246, 0.08); -} -.me-model-picker-item { - box-sizing: border-box; - width: 100%; - padding: 6px 8px; - border-radius: 6px; - background: transparent; - border: none; - color: var(--dsw-alias-label-primary, #0f1115); - font-size: 13px; - text-align: left; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - transition: all 120ms ease; -} -.me-model-picker-item:hover { - background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05)); -} -.me-model-picker-item.active { - background: rgba(37, 99, 235, 0.1); - color: #2563eb; - font-weight: 500; -} -body[data-ds-dark-theme] .me-model-picker-item { - color: var(--dsw-alias-label-primary, #e2e8f0); -} -body[data-ds-dark-theme] .me-model-picker-item:hover { - background: var(--dsw-alias-surface-l2, rgba(255, 255, 255, 0.06)); -} -body[data-ds-dark-theme] .me-model-picker-item.active { - background: rgba(56, 189, 248, 0.12); - color: #38bdf8; -} - /* Footer Section */ +export const ENHANCER_CSS = ` .me-footer-card { box-sizing: border-box; margin-top: 16px; diff --git a/tests/index.test.ts b/tests/index.test.ts index 1d81a91..0da98f5 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,5 +1,8 @@ import { describe, it, expect, vi } from 'vitest'; -import { apply } from '../src/index'; +import { apply as applyHost } from '../src/index'; +import * as clientModule from '../src/client/index'; +import { ENHANCER_CSS } from '../src/client/styles'; +import React from 'react'; describe('Host plugin', () => { it('registers models dev exact proxy route', () => { @@ -11,10 +14,67 @@ describe('Host plugin', () => { }, }; - apply(mockCtx as any); + applyHost(mockCtx as any); expect(registerMock).toHaveBeenCalled(); const callArg = registerMock.mock.calls[0][0]; expect(callArg.path).toBe('/api/models-dev/models'); expect(callArg.kind).toBe('exact'); }); }); + +describe('Client plugin contracts', () => { + it('registers settings extension slots only and leaves model selection untouched', async () => { + const registeredSlots: string[] = []; + const registeredEntries: any[] = []; + + const mockScope = { + slots: { + inject: (slotName: string, cb: any) => { + registeredSlots.push(slotName); + cb(); + }, + register: vi.fn((def: any, component: any) => { + registeredEntries.push({ def, component }); + return def; + }), + } + }; + + const mockCtx = { + inject: vi.fn((deps: string[], cb: any) => { + cb(mockScope); + }), + get: vi.fn(), + }; + + clientModule.apply(mockCtx as any); + + // Verify slots registered + expect(registeredSlots).toContain('settings.models.footer'); + expect(registeredSlots).toContain('settings.models.provider-card'); + // Ensure no composer / model-selection slots are registered + expect(registeredSlots).not.toContain('conversation.input.right'); + expect(registeredSlots).not.toContain('conversation.input.model'); + + // Verify footer registration definition + const footerEntry = registeredEntries.find(e => e.def.name === 'settings.models.footer'); + expect(footerEntry).toBeDefined(); + expect(footerEntry.def.id).toBe('me-models-footer'); + + // Verify provider card registration definition + const cardEntry = registeredEntries.find(e => e.def.name === 'settings.models.provider-card'); + expect(cardEntry).toBeDefined(); + expect(cardEntry.def.key).toBe('llm-pi-ai'); + }); + + it('ENHANCER_CSS contains only non-selector settings styles', () => { + expect(ENHANCER_CSS).toContain('.me-footer-card'); + expect(ENHANCER_CSS).toContain('.me-footer-title'); + expect(ENHANCER_CSS).toContain('.me-diff-table'); + // Ensure no selector styles or hide rules remain + expect(ENHANCER_CSS).not.toContain('me-compact'); + expect(ENHANCER_CSS).not.toContain('me-slider'); + expect(ENHANCER_CSS).not.toContain('me-effort'); + expect(ENHANCER_CSS).not.toContain('display: none'); + }); +}); diff --git a/tests/mocks/react.ts b/tests/mocks/react.ts new file mode 100644 index 0000000..592c96d --- /dev/null +++ b/tests/mocks/react.ts @@ -0,0 +1,21 @@ +export default { + createElement: () => ({}), + Fragment: () => ({}), + useState: (initial: any) => [initial, () => {}], + useEffect: () => {}, + useLayoutEffect: () => {}, + useMemo: (fn: any) => fn(), + useRef: (val: any) => ({ current: val }), + useSyncExternalStore: () => ({}) +}; +export const createElement = () => ({}); +export const jsx = () => ({}); +export const jsxs = () => ({}); +export const jsxDEV = () => ({}); +export const Fragment = () => ({}); +export const useState = (initial: any) => [initial, () => {}]; +export const useEffect = () => {}; +export const useLayoutEffect = () => {}; +export const useMemo = (fn: any) => fn(); +export const useRef = (val: any) => ({ current: val }); +export const useSyncExternalStore = () => ({}); diff --git a/tests/modelsDev.test.ts b/tests/modelsDev.test.ts index 13b7c52..7ef7dfa 100644 --- a/tests/modelsDev.test.ts +++ b/tests/modelsDev.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { normModelKey, findDevModel } from '../src/client/modelsDev'; +import { normModelKey, findDevModel, sanitizePositiveInt, mergeModelCapabilities } from '../src/client/modelsDev'; import { I18N_DICT } from '../src/client/i18n'; describe('modelsDev utils', () => { @@ -21,12 +21,118 @@ describe('modelsDev utils', () => { expect(findDevModel('deepseek-r1', list)?.id).toBe('deepseek/deepseek-r1'); }); + it('sanitizes positive integers correctly and filters non-positive or non-finite values', () => { + expect(sanitizePositiveInt(128000)).toBe(128000); + expect(sanitizePositiveInt(1)).toBe(1); + expect(sanitizePositiveInt('4096')).toBe(4096); + + expect(sanitizePositiveInt(0)).toBeUndefined(); + expect(sanitizePositiveInt(-10)).toBeUndefined(); + expect(sanitizePositiveInt(NaN)).toBeUndefined(); + expect(sanitizePositiveInt(Infinity)).toBeUndefined(); + expect(sanitizePositiveInt(-Infinity)).toBeUndefined(); + expect(sanitizePositiveInt(128.5)).toBeUndefined(); + expect(sanitizePositiveInt('0')).toBeUndefined(); + expect(sanitizePositiveInt('-1')).toBeUndefined(); + expect(sanitizePositiveInt('abc')).toBeUndefined(); + expect(sanitizePositiveInt(null)).toBeUndefined(); + expect(sanitizePositiveInt(undefined)).toBeUndefined(); + }); + + it('merges model capabilities without writing zero or invalid contextWindow/maxTokens', () => { + const matchedWithZero = { + id: 'test/model-zero', + rawKey: 'model-zero', + name: 'Model Zero', + limit: { + context: 0, + output: 0 + }, + modalities: { input: ['text'] }, + reasoning: false + }; + + const targetModel = { + id: 'model-zero', + name: 'Existing Name' + }; + + const merged = mergeModelCapabilities(targetModel, matchedWithZero as any); + expect(merged.id).toBe('model-zero'); + expect(merged.name).toBe('Existing Name'); + expect(merged.contextWindow).toBeUndefined(); + expect('contextWindow' in merged).toBe(false); + expect(merged.maxTokens).toBeUndefined(); + expect('maxTokens' in merged).toBe(false); + }); + + it('preserves valid existing contextWindow/maxTokens when incoming is zero or missing', () => { + const matchedWithZero = { + id: 'test/model-zero', + rawKey: 'model-zero', + name: 'Model Zero', + limit: { + context: 0, + output: 0 + } + }; + + const existingModel = { + id: 'model-zero', + contextWindow: 131072, + maxTokens: 8192 + }; + + const merged = mergeModelCapabilities(existingModel, matchedWithZero as any); + expect(merged.contextWindow).toBe(131072); + expect(merged.maxTokens).toBe(8192); + }); + + it('populates valid positive contextWindow/maxTokens from models.dev when existing is missing', () => { + const matchedValid = { + id: 'openai/gpt-4o', + rawKey: 'gpt-4o', + name: 'GPT-4o', + limit: { + context: 128000, + output: 16384 + } + }; + + const existingModel = { + id: 'gpt-4o' + }; + + const merged = mergeModelCapabilities(existingModel, matchedValid as any); + expect(merged.contextWindow).toBe(128000); + expect(merged.maxTokens).toBe(16384); + }); + + it('cleans up invalid existing values if present and matched is missing or zero', () => { + const matchedWithZero = { + id: 'test/model-bad', + rawKey: 'model-bad', + limit: { + context: 0, + output: -1 + } + }; + + const existingModelWithZero = { + id: 'model-bad', + contextWindow: 0, + maxTokens: -5 + }; + + const merged = mergeModelCapabilities(existingModelWithZero, matchedWithZero as any); + expect('contextWindow' in merged).toBe(false); + expect('maxTokens' in merged).toBe(false); + }); + it('provides bilingual dictionary keys', () => { expect(I18N_DICT.zh.modalTitle).toBeDefined(); expect(I18N_DICT.en.modalTitle).toBeDefined(); expect(I18N_DICT.zh.batchBtn).toBeDefined(); expect(I18N_DICT.en.batchBtn).toBeDefined(); - expect(I18N_DICT.zh.effortReasoningTitle).toBeDefined(); - expect(I18N_DICT.en.effortReasoningTitle).toBeDefined(); }); }); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..b6a101f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + esbuild: { + jsx: 'transform', + jsxFactory: 'React.createElement', + jsxFragment: 'React.Fragment' + }, + resolve: { + alias: { + 'react/jsx-runtime': new URL('./tests/mocks/react.ts', import.meta.url).pathname, + 'react/jsx-dev-runtime': new URL('./tests/mocks/react.ts', import.meta.url).pathname, + react: new URL('./tests/mocks/react.ts', import.meta.url).pathname + } + }, + test: { + environment: 'node' + } +});