@lynn123411/dsh-a6api 1.5.9 → 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/lib/client.js +168 -7
- package/lib/client.js.map +4 -4
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ A6API 多模型聚合站深度接入插件:将 A6API 聚合网关无缝注册
|
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- **多标签页优雅布局**:设置交互拆分为「可用模型」、「模型目录」、「账户资产」、「基础配置」四大专属页面,分类清晰、告别页面拥挤。
|
|
8
|
+
- **共享「API中转」设置页**:本插件的四个页面整体作为「设置 - API中转」页的首个 tab(`settings.section` id `relay`),与 `dsh-llm-agentrouter` 的端点卡片同页并列,参与者可增删。内核不允许一页被多个插件共同声明,因此每个参与者各持一份运行时逻辑一致的页壳,**先加载者当选页面宿主**、其余只注册卡片;当选者被卸载后下次启动自动改选。做法与约束见仓库 `docs/rules/shared-settings-page.md`。
|
|
8
9
|
- **全真官方模型卡片复刻**:1:1 像素级复刻 A6API 官网模型卡片视图,包含品牌图标、模型名、商户 ID、渠道说明、输入/输出/缓存读写单价、实时倍率比值胶囊、实时/24h/7d 阶梯成功率点阵、延迟与缓存命中率进度条、智能标签(稳定/低价/高速/高质/保真)以及真实的固定与禁用操作。
|
|
9
10
|
- **混合价估算标注(≈¥x/亿 tokens)**:每张模型卡片价格区新增绿色「≈ ¥x.xx/亿」胶囊——按卡片 24h 实测缓存命中率,把「缓存读价(命中)/ 输入价(未命中)/ 输出价」折算为每 1 亿 tokens 的混合人民币成本(输出 token 占比固定 0.35%,与官网模型市场标注口径一致),悬停展示完整估算式与每 1M 单价,对比模型贵贱一目了然;按次计费等无 token 单价的卡片不展示。
|
|
10
11
|
- **给模型固定商家**:卡片一键「固定商家」/「取消固定」,直接调用 A6API 平台固定接口,把当前卡片商家绑定为该模型的服务渠道(优先路由);固定自动解析并绑定当前 API Key 令牌,异常时自动切换智能优选(平台默认兜底,无多余开关);检测到模型已固定到其他商家时,卡片自动切换为该固定商家(含价格/健康度指标),无需手动探测;卡片状态实时跟随官网——官网侧解除固定(如不接受涨价)后插件按钮自动还原。
|
|
@@ -19,8 +20,8 @@ A6API 多模型聚合站深度接入插件:将 A6API 聚合网关无缝注册
|
|
|
19
20
|
- **「模型市场」直达胶囊**:设置页标题栏与侧边栏浮层的胶囊行中,价格波动右侧常驻同款「模型市场」胶囊(白底灰字、hover 变绿),点击新标签直达 `https://a6api.com/models`。
|
|
20
21
|
- **侧边栏快捷模型卡片**:主界面左下角(设置按钮上方)常驻「A6api」入口按钮,**任何会话(含子代理)均可展开浮层**,向上弹出当前会话所用模型对应的完整商户卡片(与设置页同一数据源与操作能力:探测/固定/禁用/详情全部可用);会话切换模型时卡片实时跟随;当前会话未使用 A6api 模型时,模型卡片区域整体置灰且不可交互,顶部账户速览胶囊(余额/价格波动/模型市场)保持正常可用。
|
|
21
22
|
- **侧边栏账户速览胶囊**:浮层顶部常驻「账户余额」(纯展示,每 60s 自动同步)、「价格波动」(行为与标题栏一致,可一键跳官网)与「模型市场」(点击新标签直达 `https://a6api.com/models`)三枚同款胶囊,账号级数据与模型卡片无关,加载/空态恒显示。
|
|
22
|
-
- **后台预热无感加载**:插件随 DSH 启动即后台预热一次完整状态,之后每 60s 静默整体刷新;服务端 `/state` 将余额/模型列表/日志/固定记录等独立上游调用并行拉取(串行 5 次 RTT → 1 次)—— 侧边栏浮层与设置页打开时数据已就绪,秒开无加载等待。服务端对 `/state` 保留 120s 短缓存供轮询复用;「可用模型」页「刷新列表」按钮带 `force=1` 绕过缓存立即向 A6API
|
|
23
|
-
-
|
|
23
|
+
- **后台预热无感加载**:插件随 DSH 启动即后台预热一次完整状态,之后每 60s 静默整体刷新;服务端 `/state` 将余额/模型列表/日志/固定记录等独立上游调用并行拉取(串行 5 次 RTT → 1 次)—— 侧边栏浮层与设置页打开时数据已就绪,秒开无加载等待。服务端对 `/state` 保留 120s 短缓存供轮询复用;「可用模型」页「刷新列表」按钮带 `force=1` 绕过缓存立即向 A6API 重建最新数据。
|
|
24
|
+
- **模型目录管理**:「可用模型」右侧的「模型目录」页——一键「从 A6API 获取市场模型」翻页拉取市场支持的全部模型 ID(参数初始为空),再「从 OpenRouter 一键查询」自动填充 `contextWindow / maxTokens / input`(查不到的保持留空可手动填写,每行也可单独查询;`name` 仅用户手动填写,未填则不写入 DSH);行内编辑全部采用 DSH `settings.yaml` 原生模型字段(name / contextWindow / maxTokens / input / reasoningEfforts),保存后若该模型已在 DSH 启用则立即重写 `llm-pi-ai.providers.a6api` 对应条目即时生效;OpenRouter 查询(单行「查询参数」/「一键查询」)填充到已启用模型时同样立即重写;支持按「可用(当前令牌白名单)/ 不可用」与「参数已填 / 未填」筛选;可一键「清空目录」(二次确认)后重新从 A6API 获取并重新用 OpenRouter 填充;新获取的模型默认声明 DSH 全部思考档位(off / minimal / low / medium / high / xhigh / max)。不内置静态模型目录,所有参数以运行时数据为准(存于 `~/.dsh/dsh-a6api/catalog.json` 插件子目录,与 DSH「组件自建 home 子目录」惯例一致;根目录遗留的 `dsh-a6api-catalog.json` 首次加载时自动搬迁并删除),写入 settings.yaml 时缺省字段自动省略(由 DSH 默认值兜底)。
|
|
24
25
|
|
|
25
26
|
## 安装
|
|
26
27
|
|
package/lib/client.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
name: () => name
|
|
37
37
|
});
|
|
38
38
|
module.exports = __toCommonJS(index_exports);
|
|
39
|
-
var
|
|
39
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
40
40
|
|
|
41
41
|
// src/client/components/A6ApiSettings.tsx
|
|
42
42
|
var import_react5 = require("react");
|
|
@@ -2688,6 +2688,141 @@ var A6ApiSidebarCardBody = ({
|
|
|
2688
2688
|
] });
|
|
2689
2689
|
};
|
|
2690
2690
|
|
|
2691
|
+
// src/client/relay-settings-page.js
|
|
2692
|
+
var import_react7 = require("react");
|
|
2693
|
+
var RELAY_PAGE_ID = "relay";
|
|
2694
|
+
var RELAY_PAGE_ORDER = 120;
|
|
2695
|
+
var RELAY_ITEM_SLOT = "relay.settings.item";
|
|
2696
|
+
var TABLIST_STYLE = {
|
|
2697
|
+
display: "flex",
|
|
2698
|
+
alignItems: "flex-end",
|
|
2699
|
+
gap: "22px",
|
|
2700
|
+
marginTop: "2px",
|
|
2701
|
+
marginBottom: "16px"
|
|
2702
|
+
};
|
|
2703
|
+
var TAB_STYLE = {
|
|
2704
|
+
appearance: "none",
|
|
2705
|
+
background: "transparent",
|
|
2706
|
+
// No border on ANY tab: the marker below is the active tab's own element, so
|
|
2707
|
+
// an inactive tab has nothing that could render a line.
|
|
2708
|
+
border: "none",
|
|
2709
|
+
position: "relative",
|
|
2710
|
+
padding: "7px 1px 11px",
|
|
2711
|
+
cursor: "pointer",
|
|
2712
|
+
font: "inherit",
|
|
2713
|
+
fontSize: "13px",
|
|
2714
|
+
lineHeight: "20px",
|
|
2715
|
+
color: "var(--dsw-alias-label-tertiary, inherit)"
|
|
2716
|
+
};
|
|
2717
|
+
var TAB_ACTIVE_STYLE = Object.assign({}, TAB_STYLE, {
|
|
2718
|
+
color: "var(--dsw-alias-label-primary, inherit)"
|
|
2719
|
+
});
|
|
2720
|
+
var TAB_MARKER_STYLE = {
|
|
2721
|
+
position: "absolute",
|
|
2722
|
+
left: 0,
|
|
2723
|
+
right: 0,
|
|
2724
|
+
bottom: 0,
|
|
2725
|
+
height: "2px",
|
|
2726
|
+
borderRadius: "2px 2px 0 0",
|
|
2727
|
+
background: "var(--dsw-alias-label-primary, currentColor)"
|
|
2728
|
+
};
|
|
2729
|
+
var PANEL_STYLE = { margin: 0 };
|
|
2730
|
+
var PANEL_HIDDEN_STYLE = { margin: 0, display: "none" };
|
|
2731
|
+
function readLabel(label) {
|
|
2732
|
+
if (typeof label === "function") return label();
|
|
2733
|
+
return typeof label === "string" ? label : "";
|
|
2734
|
+
}
|
|
2735
|
+
function createRelayTabs(ctx) {
|
|
2736
|
+
const locale = ctx.get("locale");
|
|
2737
|
+
let version = -1;
|
|
2738
|
+
let revision = -1;
|
|
2739
|
+
let tabs = [];
|
|
2740
|
+
return {
|
|
2741
|
+
getSnapshot: () => {
|
|
2742
|
+
const nextVersion = ctx.slots.getVersion(RELAY_ITEM_SLOT);
|
|
2743
|
+
const nextRevision = locale === void 0 ? 0 : locale.getSnapshot().revision;
|
|
2744
|
+
if (nextVersion === version && nextRevision === revision) return tabs;
|
|
2745
|
+
version = nextVersion;
|
|
2746
|
+
revision = nextRevision;
|
|
2747
|
+
tabs = ctx.slots.entries(RELAY_ITEM_SLOT).map((entry) => ({
|
|
2748
|
+
id: entry.options.id ?? "",
|
|
2749
|
+
order: entry.options.order ?? 0,
|
|
2750
|
+
label: readLabel(entry.options.label)
|
|
2751
|
+
})).sort((left, right) => left.order - right.order);
|
|
2752
|
+
return tabs;
|
|
2753
|
+
},
|
|
2754
|
+
subscribe: (listener) => {
|
|
2755
|
+
const offSlots = ctx.slots.subscribe(RELAY_ITEM_SLOT, listener);
|
|
2756
|
+
const offLocale = locale === void 0 ? void 0 : locale.subscribe(listener);
|
|
2757
|
+
return () => {
|
|
2758
|
+
offSlots();
|
|
2759
|
+
if (offLocale !== void 0) offLocale();
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
function RelaySettingsSection({ renderSlot, relayTabs }) {
|
|
2765
|
+
const tabs = (0, import_react7.useSyncExternalStore)(
|
|
2766
|
+
relayTabs.subscribe,
|
|
2767
|
+
relayTabs.getSnapshot,
|
|
2768
|
+
relayTabs.getSnapshot
|
|
2769
|
+
);
|
|
2770
|
+
const [requested, setRequested] = (0, import_react7.useState)(null);
|
|
2771
|
+
const selected = requested !== null && tabs.some((tab) => tab.id === requested) ? requested : tabs.length > 0 ? tabs[0].id : null;
|
|
2772
|
+
if (selected === null) return null;
|
|
2773
|
+
return (0, import_react7.createElement)(
|
|
2774
|
+
"div",
|
|
2775
|
+
null,
|
|
2776
|
+
(0, import_react7.createElement)(
|
|
2777
|
+
"div",
|
|
2778
|
+
{ role: "tablist", style: TABLIST_STYLE },
|
|
2779
|
+
tabs.map((tab) => (0, import_react7.createElement)(
|
|
2780
|
+
"button",
|
|
2781
|
+
{
|
|
2782
|
+
key: tab.id,
|
|
2783
|
+
type: "button",
|
|
2784
|
+
role: "tab",
|
|
2785
|
+
"aria-selected": tab.id === selected,
|
|
2786
|
+
style: tab.id === selected ? TAB_ACTIVE_STYLE : TAB_STYLE,
|
|
2787
|
+
onClick: () => {
|
|
2788
|
+
setRequested(tab.id);
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
tab.label,
|
|
2792
|
+
tab.id === selected ? (0, import_react7.createElement)("span", { style: TAB_MARKER_STYLE, "aria-hidden": true }) : null
|
|
2793
|
+
))
|
|
2794
|
+
),
|
|
2795
|
+
tabs.map((tab) => (0, import_react7.createElement)(
|
|
2796
|
+
"div",
|
|
2797
|
+
{
|
|
2798
|
+
key: tab.id,
|
|
2799
|
+
role: "tabpanel",
|
|
2800
|
+
hidden: tab.id !== selected,
|
|
2801
|
+
style: tab.id === selected ? PANEL_STYLE : PANEL_HIDDEN_STYLE
|
|
2802
|
+
},
|
|
2803
|
+
renderSlot(RELAY_ITEM_SLOT, {}, { only: tab.id })
|
|
2804
|
+
))
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
function relayPageClaimed(ctx) {
|
|
2808
|
+
return ctx.slots.entries("settings.section").some((entry) => entry.options.id === RELAY_PAGE_ID);
|
|
2809
|
+
}
|
|
2810
|
+
function claimRelaySettingsPage(ctx, label) {
|
|
2811
|
+
if (relayPageClaimed(ctx)) return () => {
|
|
2812
|
+
};
|
|
2813
|
+
const relayTabs = createRelayTabs(ctx);
|
|
2814
|
+
const children = {};
|
|
2815
|
+
children[RELAY_ITEM_SLOT] = { kind: "list", scope: "root" };
|
|
2816
|
+
return ctx.slots.register({
|
|
2817
|
+
name: "settings.section",
|
|
2818
|
+
id: RELAY_PAGE_ID,
|
|
2819
|
+
order: RELAY_PAGE_ORDER,
|
|
2820
|
+
label,
|
|
2821
|
+
inject: () => ({ relayTabs }),
|
|
2822
|
+
children
|
|
2823
|
+
}, RelaySettingsSection);
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2691
2826
|
// src/client/styles/main.css
|
|
2692
2827
|
var main_default = `/* A6API Plugin Styles - Clean Professional DSH Native Theme Integration */
|
|
2693
2828
|
|
|
@@ -4956,7 +5091,28 @@ body.dsh-a6api-tooltip-active [data-tooltip]::before {
|
|
|
4956
5091
|
|
|
4957
5092
|
// src/client/index.ts
|
|
4958
5093
|
var name = "@lynn123411/dsh-a6api";
|
|
4959
|
-
var inject = ["slots"];
|
|
5094
|
+
var inject = ["slots", "locale"];
|
|
5095
|
+
var TAB_NAV = { zh: "A6api", en: "A6api" };
|
|
5096
|
+
var RELAY_PAGE_NAV = { zh: "API\u4E2D\u8F6C", en: "API relay" };
|
|
5097
|
+
var NS = "settings.a6api";
|
|
5098
|
+
function registerNavDicts(ctx) {
|
|
5099
|
+
const locale = ctx && typeof ctx.get === "function" ? ctx.get("locale") : void 0;
|
|
5100
|
+
if (!locale || typeof locale.register !== "function") return;
|
|
5101
|
+
ctx.effect(
|
|
5102
|
+
() => locale.register(NS, {
|
|
5103
|
+
zh: { pageNav: RELAY_PAGE_NAV.zh, tabNav: TAB_NAV.zh },
|
|
5104
|
+
en: { pageNav: RELAY_PAGE_NAV.en, tabNav: TAB_NAV.en }
|
|
5105
|
+
}),
|
|
5106
|
+
"dsh-a6api: shared page dictionaries"
|
|
5107
|
+
);
|
|
5108
|
+
}
|
|
5109
|
+
function navLabel(ctx, key) {
|
|
5110
|
+
const locale = ctx && typeof ctx.get === "function" ? ctx.get("locale") : void 0;
|
|
5111
|
+
if (locale && typeof locale.bind === "function") {
|
|
5112
|
+
return () => locale.bind(NS)(key);
|
|
5113
|
+
}
|
|
5114
|
+
return () => key === "pageNav" ? RELAY_PAGE_NAV.zh : TAB_NAV.zh;
|
|
5115
|
+
}
|
|
4960
5116
|
function injectStyles() {
|
|
4961
5117
|
if (typeof document === "undefined") return;
|
|
4962
5118
|
const styleId = "dsh-a6api-styles";
|
|
@@ -5182,13 +5338,18 @@ function apply(ctx) {
|
|
|
5182
5338
|
const slots = ctx?.slots || (ctx?.get ? ctx.get("slots") : null);
|
|
5183
5339
|
if (!slots || typeof slots.inject !== "function") return;
|
|
5184
5340
|
slots.inject("settings.section", () => {
|
|
5341
|
+
registerNavDicts(ctx);
|
|
5342
|
+
return claimRelaySettingsPage(ctx, navLabel(ctx, "pageNav"));
|
|
5343
|
+
});
|
|
5344
|
+
slots.inject(RELAY_ITEM_SLOT, () => {
|
|
5185
5345
|
return slots.register(
|
|
5186
5346
|
{
|
|
5187
|
-
name:
|
|
5347
|
+
name: RELAY_ITEM_SLOT,
|
|
5348
|
+
// id = 本插件的设置命名空间:共享页按 id 过滤本 tab 的面板,故须与卡片自身命名空间一致
|
|
5188
5349
|
id: "dsh-a6api",
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
label: (
|
|
5350
|
+
order: 10,
|
|
5351
|
+
// label 由共享页投影为外层 tab 标题
|
|
5352
|
+
label: navLabel(ctx, "tabNav")
|
|
5192
5353
|
},
|
|
5193
5354
|
A6ApiSettingsPanel
|
|
5194
5355
|
);
|
|
@@ -5202,7 +5363,7 @@ function apply(ctx) {
|
|
|
5202
5363
|
order: -1,
|
|
5203
5364
|
label: () => "A6api"
|
|
5204
5365
|
},
|
|
5205
|
-
(props) =>
|
|
5366
|
+
(props) => import_react8.default.createElement(A6ApiSidebarCard, {
|
|
5206
5367
|
...props || {},
|
|
5207
5368
|
getModelDirectories
|
|
5208
5369
|
})
|