dsh-router-core 0.4.6 → 0.4.8
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 +156 -76
- package/cordis.patch.yml +2 -7
- package/docs/ext.md +4 -18
- package/docs/protocols.md +198 -0
- package/lib/client-registry.js +193 -668
- package/lib/client.js +193 -668
- package/lib/ext-prompt/index.js +202 -32
- package/lib/index.js +2805 -547
- package/lib/types/ext/contract.d.ts +20 -1
- package/lib/types/ext-prompt/content.d.ts +18 -1
- package/lib/types/ext-prompt/index.d.ts +7 -9
- package/lib/types/ext-prompt/mount.d.ts +14 -3
- package/lib/types/ext-prompt/plugin.d.ts +13 -5
- package/lib/types/ext-prompt/render.d.ts +14 -3
- package/lib/types/ext-prompt/sections.d.ts +95 -0
- package/lib/types/protocol/anthropic.d.ts +35 -0
- package/lib/types/protocol/chat.d.ts +25 -0
- package/lib/types/protocol/entry.d.ts +137 -0
- package/lib/types/protocol/gemini.d.ts +57 -0
- package/lib/types/protocol/index.d.ts +13 -0
- package/lib/types/protocol/responses.d.ts +41 -0
- package/lib/types/protocol/sse.d.ts +41 -0
- package/lib/types/protocol/stream.d.ts +92 -0
- package/lib/types/protocol/types.d.ts +224 -0
- package/lib/types/router/index.d.ts +42 -1
- package/lib/types/router/types.d.ts +19 -0
- package/lib/types/shared.d.ts +11 -0
- package/lib/types/suppliers/contract.d.ts +8 -0
- package/lib/types/suppliers/registry.d.ts +0 -1
- package/package.json +6 -15
- package/src/client/ExtDetail.tsx +26 -18
- package/src/client/group-controls.test.ts +53 -0
- package/src/client/group-controls.ts +43 -0
- package/src/client/index.tsx +0 -17
- package/src/client/router-components.test.ts +1 -1
- package/src/client/router.css +23 -355
- package/src/ext/contract.ts +20 -1
- package/src/ext-prompt/assembly.test.ts +63 -0
- package/src/ext-prompt/content.ts +25 -22
- package/src/ext-prompt/ext-prompt.test.ts +41 -31
- package/src/ext-prompt/index.ts +57 -11
- package/src/ext-prompt/mount.test.ts +18 -1
- package/src/ext-prompt/mount.ts +24 -9
- package/src/ext-prompt/plugin.ts +67 -6
- package/src/ext-prompt/render.test.ts +1 -1
- package/src/ext-prompt/render.ts +17 -6
- package/src/ext-prompt/section-controls.test.ts +148 -0
- package/src/ext-prompt/section-filter.test.ts +140 -0
- package/src/ext-prompt/sections.test.ts +123 -0
- package/src/ext-prompt/sections.ts +193 -0
- package/src/ext-route.test.ts +16 -0
- package/src/index.ts +109 -1
- package/src/protocol/anthropic.ts +662 -0
- package/src/protocol/chat.ts +272 -0
- package/src/protocol/entry.ts +440 -0
- package/src/protocol/gemini.ts +548 -0
- package/src/protocol/index.ts +31 -0
- package/src/protocol/protocol.test.ts +728 -0
- package/src/protocol/responses.ts +551 -0
- package/src/protocol/sse.ts +94 -0
- package/src/protocol/stream.ts +195 -0
- package/src/protocol/types.ts +249 -0
- package/src/protocol/wire-e2e.test.ts +208 -0
- package/src/protocol/wiring.test.ts +225 -0
- package/src/router/index.ts +144 -26
- package/src/router/models-cache.test.ts +5 -6
- package/src/router/supplier-switch.test.ts +1 -1
- package/src/router/types.ts +19 -0
- package/src/shared.ts +11 -0
- package/src/suppliers/contract.ts +8 -0
- package/src/suppliers/registry.ts +0 -71
- package/lib/contract-D4BWZ7Xe.js +0 -13
- package/lib/ext-test/index.js +0 -36
- package/lib/ext-test/locale/en.json +0 -6
- package/lib/ext-test/locale/zh.json +0 -6
- package/lib/ext-test/package.json +0 -5
- package/lib/types/ext-test/index.d.ts +0 -21
- package/lib/types/ext-test/plugin.d.ts +0 -23
- package/lib/types/suppliers/probe.d.ts +0 -160
- package/src/client/ExtTestPanel.tsx +0 -392
- package/src/client/comp-button-scope.test.ts +0 -58
- package/src/client/comp-panel-layout.test.ts +0 -58
- package/src/client/ext-panels.test.ts +0 -44
- package/src/client/ext-panels.ts +0 -49
- package/src/client/model-choice.test.ts +0 -41
- package/src/client/model-choice.ts +0 -37
- package/src/ext-test/ext-test.test.ts +0 -60
- package/src/ext-test/index.ts +0 -35
- package/src/ext-test/locale/en.json +0 -6
- package/src/ext-test/locale/zh.json +0 -6
- package/src/ext-test/plugin.ts +0 -34
- package/src/ext-test/row.json +0 -5
- package/src/suppliers/probe.test.ts +0 -266
- package/src/suppliers/probe.ts +0 -475
package/src/client/ExtDetail.tsx
CHANGED
|
@@ -13,13 +13,12 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 列表页只列**已启用**的扩展(关闭的连卡片都不出现),所以这里也不必再显示
|
|
15
15
|
* 「已启用/未启用」——那行字只能是常量。布局同供应商详情:返回链接 + 图标 +
|
|
16
|
-
*
|
|
17
|
-
* 按需加独立面板,此处不内置任何具体扩展的专属视图。
|
|
16
|
+
* 名称/副行,下面跟扩展说明卡与未就绪横幅。
|
|
18
17
|
*/
|
|
19
|
-
import { useEffect, useState } from 'react'
|
|
18
|
+
import { Fragment, useEffect, useState } from 'react'
|
|
20
19
|
import { Switch } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
21
20
|
import { ROUTER_API_BASE, type ExtControlItem, type RouterExtItem, type RouterExtResponse } from '../shared.ts'
|
|
22
|
-
import {
|
|
21
|
+
import { groupControlRows } from './group-controls.ts'
|
|
23
22
|
import { Modal } from './Modal.tsx'
|
|
24
23
|
|
|
25
24
|
/**
|
|
@@ -436,14 +435,30 @@ function ExtControls({ item }: { item: RouterExtItem }): JSX.Element {
|
|
|
436
435
|
它只剩两个字,却仍占一行 —— 页面进来第一眼是「子开关」这三个字而不是
|
|
437
436
|
那些规则本身。列表自带每条的标题与开关,不需要再套一层说明。 */}
|
|
438
437
|
<ul className="dshr-compRows">
|
|
439
|
-
{
|
|
438
|
+
{/* 分组渲染(`ExtControl.group`):无组节(准则分类)平铺在前 ——
|
|
439
|
+
没用 group 的扩展拿到的就是原来的平铺列表,行为不变;有组的
|
|
440
|
+
条目(宿主段落)每组前插一行组标题。组的先后与组内顺序都来自
|
|
441
|
+
服务端 `rows`(= prompt 序),这里不重排。 */}
|
|
442
|
+
{groupControlRows(controls).map((g) => (
|
|
443
|
+
<Fragment key={g.label ?? '__plain__'}>
|
|
444
|
+
{g.label !== undefined && (
|
|
445
|
+
<li className="dshr-extGroupTitle">{g.label}</li>
|
|
446
|
+
)}
|
|
447
|
+
{g.items.map((c) => {
|
|
448
|
+
// 下标永远对 `controls`(= 服务端 rows)取:分组只影响**渲染
|
|
449
|
+
// 顺序**,拖动落盘的 ids 必须基于原列表 —— 段落行不可拖(见下),
|
|
450
|
+
// 可拖的准则全在无组节,原下标与视图位置天然一致。
|
|
451
|
+
const index = controls.indexOf(c)
|
|
452
|
+
const sortable = c.sortable !== false
|
|
453
|
+
return (
|
|
440
454
|
<li
|
|
441
455
|
key={c.id}
|
|
442
456
|
className={`dshr-compRow${dragIndex === index ? ' dshr-linkRowDragging' : ''}`}
|
|
443
|
-
draggable
|
|
444
|
-
onDragStart={(e) => { setDragIndex(index); e.dataTransfer.effectAllowed = 'move' }}
|
|
445
|
-
onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move' }}
|
|
457
|
+
draggable={sortable}
|
|
458
|
+
onDragStart={(e) => { if (!sortable) return; setDragIndex(index); e.dataTransfer.effectAllowed = 'move' }}
|
|
459
|
+
onDragOver={(e) => { if (!sortable) return; e.preventDefault(); e.dataTransfer.dropEffect = 'move' }}
|
|
446
460
|
onDrop={(e) => {
|
|
461
|
+
if (!sortable) { setDragIndex(null); return }
|
|
447
462
|
e.preventDefault()
|
|
448
463
|
if (dragIndex === null || dragIndex === index) { setDragIndex(null); return }
|
|
449
464
|
const next = [...controls]
|
|
@@ -511,6 +526,9 @@ function ExtControls({ item }: { item: RouterExtItem }): JSX.Element {
|
|
|
511
526
|
onChange={(next) => { void toggle(c.id, next) }}
|
|
512
527
|
/>
|
|
513
528
|
</li>
|
|
529
|
+
)
|
|
530
|
+
})}
|
|
531
|
+
</Fragment>
|
|
514
532
|
))}
|
|
515
533
|
</ul>
|
|
516
534
|
{withBody.length === 0 && (
|
|
@@ -579,10 +597,6 @@ interface ExtDetailProps {
|
|
|
579
597
|
}
|
|
580
598
|
|
|
581
599
|
export function ExtDetail({ item, onBack }: ExtDetailProps): JSX.Element {
|
|
582
|
-
// 扩展**自带**详情面板时用它(插件自检就是这么做的:选供应商/模型/连接 + 跑测试)。
|
|
583
|
-
// 没有就退回下面这张通用只读页 —— 保留返回链接与标题,导航形状与有没有自定义
|
|
584
|
-
// 面板无关,用户点进去的预期是稳定的。
|
|
585
|
-
const Custom = extPanel(item.id)
|
|
586
600
|
return (
|
|
587
601
|
<div className="dshr-tabBody">
|
|
588
602
|
{/* Header(同供应商详情:返回链接 + 图标 + 名称/副行) */}
|
|
@@ -615,13 +629,9 @@ export function ExtDetail({ item, onBack }: ExtDetailProps): JSX.Element {
|
|
|
615
629
|
</div>
|
|
616
630
|
</div>
|
|
617
631
|
|
|
618
|
-
{/* 扩展自带的面板优先(注册表命中时下面几块都不渲染) */}
|
|
619
|
-
{Custom !== undefined ? <Custom /> : (
|
|
620
|
-
<>
|
|
621
632
|
{/* 子开关(扩展自己的行为细节;没有 controls 就不渲染这一块) */}
|
|
622
633
|
{item.controls !== undefined && item.controls.length > 0 && <ExtControls item={item} />}
|
|
623
634
|
|
|
624
|
-
{/* 实际生效的提示词(只读预览;回答「关掉的真的没进去吗」) */}
|
|
625
635
|
{/* 未就绪警示(同供应商详情的出错横幅) */}
|
|
626
636
|
{item.ready === false && (
|
|
627
637
|
<div className="dshr-alert">
|
|
@@ -629,8 +639,6 @@ export function ExtDetail({ item, onBack }: ExtDetailProps): JSX.Element {
|
|
|
629
639
|
<span>{item.detail !== undefined && item.detail !== '' ? item.detail : '当前不可用'}</span>
|
|
630
640
|
</div>
|
|
631
641
|
)}
|
|
632
|
-
</>
|
|
633
|
-
)}
|
|
634
642
|
</div>
|
|
635
643
|
)
|
|
636
644
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 详情页 controls 分组的判据(`group-controls.ts`)。
|
|
3
|
+
*
|
|
4
|
+
* 关键是**向后兼容不可退**:没用 `group` 字段的扩展必须
|
|
5
|
+
* 拿到"一个无组节、顺序不变"的结果 —— 分组是新能力的**叠加**,不是重排。
|
|
6
|
+
*/
|
|
7
|
+
import test from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { groupControlRows } from './group-controls.ts'
|
|
10
|
+
import type { ExtControlItem } from '../shared.ts'
|
|
11
|
+
|
|
12
|
+
function row(id: string, extra: Partial<ExtControlItem> = {}): ExtControlItem {
|
|
13
|
+
return { id, title: id, on: true, custom: false, overridden: false, ...extra }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('★ 全部无组 ⇒ 单一个无组节(旧扩展的面板展示与改动前一致)', () => {
|
|
17
|
+
const groups = groupControlRows([row('a'), row('b')])
|
|
18
|
+
assert.equal(groups.length, 1)
|
|
19
|
+
assert.equal(groups[0]!.label, undefined)
|
|
20
|
+
assert.deepEqual(groups[0]!.items.map((i) => i.id), ['a', 'b'], '无组条目顺序被改了')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('★ 无组节恒排最前,且不产出空的无组节', () => {
|
|
24
|
+
const groups = groupControlRows([row('a'), row('t1', { group: '工具说明' })])
|
|
25
|
+
assert.deepEqual(groups.map((g) => g.label), [undefined, '工具说明'])
|
|
26
|
+
const onlyGrouped = groupControlRows([row('t1', { group: '工具说明' })])
|
|
27
|
+
assert.deepEqual(onlyGrouped.map((g) => g.label), ['工具说明'], '只有分组条目时不该冒出一个空的无组节')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('组的先后 = 首次出现序(不按字母重排)', () => {
|
|
31
|
+
const groups = groupControlRows([
|
|
32
|
+
row('h', { group: '开场身份' }),
|
|
33
|
+
row('w', { group: '工具说明' }),
|
|
34
|
+
row('e', { group: '工具说明' }),
|
|
35
|
+
row('p', { group: '计划模式' }),
|
|
36
|
+
])
|
|
37
|
+
assert.deepEqual(groups.map((g) => g.label), ['开场身份', '工具说明', '计划模式'])
|
|
38
|
+
assert.deepEqual(groups[1]!.items.map((i) => i.id), ['w', 'e'], '组内顺序被重排 ⇒ 面板序与 prompt 序脱钩')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('同名组跨位置仍归一组(不因中间夹了别的组而拆开)', () => {
|
|
42
|
+
const groups = groupControlRows([
|
|
43
|
+
row('w1', { group: '工具说明' }),
|
|
44
|
+
row('h', { group: '开场身份' }),
|
|
45
|
+
row('w2', { group: '工具说明' }),
|
|
46
|
+
])
|
|
47
|
+
const tool = groups.find((g) => g.label === '工具说明')!
|
|
48
|
+
assert.deepEqual(tool.items.map((i) => i.id), ['w1', 'w2'])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('空输入 ⇒ 空数组', () => {
|
|
52
|
+
assert.deepEqual(groupControlRows([]), [])
|
|
53
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扩展详情页 controls 的**分组**纯逻辑(客户端,可单测)。
|
|
3
|
+
*
|
|
4
|
+
* 分组是**归属信息**,不是样式:一条扩展同时管两类东西时(分层提示词 =
|
|
5
|
+
* 准则分类 + 宿主系统提示词段落),平铺成一长列无从分辨哪条是谁的。
|
|
6
|
+
*
|
|
7
|
+
* 两条规则:
|
|
8
|
+
* ① **无组节恒排最前** —— 没用 `group` 字段的扩展拿到的
|
|
9
|
+
* 分组结果就是原来的平铺列表(一个无名节),面板行为一个像素不变;
|
|
10
|
+
* ② 组的先后 = 组名在 `rows` 里的**首次出现序**,组内保持原顺序 ——
|
|
11
|
+
* 不按字母重排:`rows` 的顺序来自服务端(段落按 prompt 序),重排
|
|
12
|
+
* 就会把"面板顺序 ≈ 模型看到的顺序"这条对应关系打断。
|
|
13
|
+
*/
|
|
14
|
+
import type { ExtControlItem } from '../shared.ts'
|
|
15
|
+
|
|
16
|
+
/** 一个分组:`label === undefined` 表示"无组节"(平铺条目)。 */
|
|
17
|
+
export interface ControlGroup {
|
|
18
|
+
label?: string
|
|
19
|
+
items: ExtControlItem[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 按 `group` 分组;空输入返回空数组(不产出空的无组节)。 */
|
|
23
|
+
export function groupControlRows(rows: readonly ExtControlItem[]): ControlGroup[] {
|
|
24
|
+
const plain: ExtControlItem[] = []
|
|
25
|
+
const groups: ControlGroup[] = []
|
|
26
|
+
const byLabel = new Map<string, ExtControlItem[]>()
|
|
27
|
+
for (const c of rows) {
|
|
28
|
+
if (c.group === undefined) {
|
|
29
|
+
plain.push(c)
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
let items = byLabel.get(c.group)
|
|
33
|
+
if (!items) {
|
|
34
|
+
items = []
|
|
35
|
+
byLabel.set(c.group, items)
|
|
36
|
+
groups.push({ label: c.group, items })
|
|
37
|
+
}
|
|
38
|
+
items.push(c)
|
|
39
|
+
}
|
|
40
|
+
const out: ControlGroup[] = []
|
|
41
|
+
if (plain.length > 0) out.push({ items: plain })
|
|
42
|
+
return [...out, ...groups]
|
|
43
|
+
}
|
package/src/client/index.tsx
CHANGED
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
import { mountRouterWorkspace } from './workspace-mount.tsx'
|
|
12
12
|
import { RouterSettingsSection } from './settings-section.tsx'
|
|
13
13
|
import { RouterComponentsSection } from './RouterComponentsSection.tsx'
|
|
14
|
-
import { ExtTestPanel } from './ExtTestPanel.tsx'
|
|
15
|
-
import { registerExtPanel } from './ext-panels.ts'
|
|
16
|
-
import { EXT_TEST_ID } from '../ext-test/plugin.ts'
|
|
17
14
|
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
|
|
18
15
|
import { rewriteRouterModelHint } from './model-hint-copy.ts'
|
|
19
16
|
import { LastHitDock } from './LastHitDock.tsx'
|
|
@@ -97,19 +94,6 @@ function mountComponentsSection(ctx: Ctx): (() => void) | undefined {
|
|
|
97
94
|
}, RouterComponentsSection))
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
/**
|
|
101
|
-
* 给「插件自检」扩展登记它的详情面板。
|
|
102
|
-
*
|
|
103
|
-
* 面板的**唯一入口**是设置 → 路由 → 扩展 → 点开那张卡片(`ExtDetail` 查注册表拿到
|
|
104
|
-
* 本组件)。**刻意不给插件页那一行再挂一个详情页**:那一行的宿主行开关已经能开/关
|
|
105
|
-
* 它了,再点进去看同一块面板就是同一个东西有两个入口 —— 与「内置扩展不在自绘节里
|
|
106
|
-
* 重复列」是同一条理由。
|
|
107
|
-
*/
|
|
108
|
-
function registerExtTestPanel(ctx: Ctx): void {
|
|
109
|
-
const dispose = registerExtPanel(EXT_TEST_ID, ExtTestPanel)
|
|
110
|
-
ctx.effect(() => dispose, 'dsh-router: ext test panel registry')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
97
|
export function apply(ctx: Ctx): void {
|
|
114
98
|
// 「最近命中」徽章:走官方 conversation.composer.dock(scope: session → 组件
|
|
115
99
|
// props 注入 sessionId,按当前会话取命中)。0.2.0 起这个座位无条件可用 ——
|
|
@@ -128,7 +112,6 @@ export function apply(ctx: Ctx): void {
|
|
|
128
112
|
if (disposeComponents !== undefined) {
|
|
129
113
|
ctx.effect(() => disposeComponents, 'dsh-router: plugin page components')
|
|
130
114
|
}
|
|
131
|
-
registerExtTestPanel(ctx)
|
|
132
115
|
// 换掉宿主的默认齿轮(契约没有 icon 字段,只能注册后认领自己的行)
|
|
133
116
|
ctx.effect(() => registerSettingsNavIcon(SECTION_LABEL), 'dsh-router: settings nav icon')
|
|
134
117
|
// 设置-模型 里 Router 卡片的那句死路提示 → 正确说法(同样只能认领后就地改写)
|
|
@@ -105,7 +105,7 @@ test('内置扩展不进这一节(它已经是原生「包含的组件」里
|
|
|
105
105
|
const out = groupRouterComponents({ ok: true }, {
|
|
106
106
|
ok: true,
|
|
107
107
|
enhancers: [
|
|
108
|
-
{ id: '
|
|
108
|
+
{ id: 'prompt', name: '分层提示词', source: 'builtin' },
|
|
109
109
|
{ id: 'rtk', name: 'RTK' },
|
|
110
110
|
],
|
|
111
111
|
})
|
package/src/client/router.css
CHANGED
|
@@ -3360,6 +3360,25 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
3360
3360
|
list-style: none;
|
|
3361
3361
|
}
|
|
3362
3362
|
|
|
3363
|
+
/* 分组标题行 —— `ExtControl.group` 的渲染落点,插在 `.dshr-compRows` 里
|
|
3364
|
+
的非交互 <li>(无拖把/开关,不可点)。与 `compTitle` 的区别:那是详情页
|
|
3365
|
+
主标题(页头),这是**组**名(次要色 + 小字号)。左右 14px 对齐
|
|
3366
|
+
`.dshr-compRow` 的行内缩,组名与行文字左缘一致。 */
|
|
3367
|
+
.dshr-extGroupTitle {
|
|
3368
|
+
margin: 14px 0 2px;
|
|
3369
|
+
padding: 0 14px;
|
|
3370
|
+
color: var(--dsw-alias-label-tertiary);
|
|
3371
|
+
font-size: 12px;
|
|
3372
|
+
font-weight: 500;
|
|
3373
|
+
line-height: 18px;
|
|
3374
|
+
list-style: none;
|
|
3375
|
+
}
|
|
3376
|
+
|
|
3377
|
+
/* 全部分组的扩展:首个渲染节点就是组标题,去掉上边距避免顶双空。 */
|
|
3378
|
+
.dshr-extGroupTitle:first-child {
|
|
3379
|
+
margin-top: 0;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3363
3382
|
.dshr-compRow {
|
|
3364
3383
|
display: flex;
|
|
3365
3384
|
align-items: center;
|
|
@@ -3437,9 +3456,9 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
3437
3456
|
}
|
|
3438
3457
|
|
|
3439
3458
|
/* 标题 + 「修改」按钮的**同一行**(`ExtDetail` 的子开关区)。
|
|
3440
|
-
⚠️ 单独一个类而不改 `.dshr-compRowName`:那个类 `RouterComponentsSection`
|
|
3441
|
-
|
|
3442
|
-
flex
|
|
3459
|
+
⚠️ 单独一个类而不改 `.dshr-compRowName`:那个类 `RouterComponentsSection`
|
|
3460
|
+
也在用,给它加 `display:flex` 会把**那一页的标题**也变成
|
|
3461
|
+
flex 盒子(那边标题后面没有按钮,平白多一层)。
|
|
3443
3462
|
没有这层时按钮会掉到**下一行**:`.dshr-compRowMain` 是 column,而
|
|
3444
3463
|
`.dshr-compRowName` 是行内元素、标题一长就折行(2026-09-29 用户报)。 */
|
|
3445
3464
|
/* 子开关区:`.dshr-compGroup` 的 `gap: 12px` 覆盖为 0(2026-09-30 用户要求)。
|
|
@@ -3489,358 +3508,7 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
3489
3508
|
overflow-wrap: anywhere;
|
|
3490
3509
|
/* 保留换行:子开关的**原文**是这个类的另一个用法(2026-09-29 合并进来),
|
|
3491
3510
|
它是多行段落。状态文案都是单行,所以这条对它们没有副作用。
|
|
3492
|
-
⇒ 原来的 `.dshr-compRowBody`
|
|
3511
|
+
⇒ 原来的 `.dshr-compRowBody` 整块删掉:各条规则的正文不需要左边框 +
|
|
3493
3512
|
缩进那种「引用块」的额外装饰(用户要求),层级只靠颜色就够。 */
|
|
3494
3513
|
white-space: pre-wrap;
|
|
3495
3514
|
}
|
|
3496
|
-
|
|
3497
|
-
/* ============================================================
|
|
3498
|
-
dsh-router — 插件自检面板(设置 → 路由 → 扩展 → 插件自检)
|
|
3499
|
-
------------------------------------------------------------
|
|
3500
|
-
这一页渲染在**宿主的插件页**里,DOM 祖先既不是 .dshr-settings 也不是
|
|
3501
|
-
.dshr-shell,所以文件顶部那套 `--rs-*` 主题变量在这里**不存在**(未定义的
|
|
3502
|
-
var() 会让整条声明失效:边框/文字色退成继承值)。
|
|
3503
|
-
|
|
3504
|
-
所以这里**不用**设置页那套组件样式,直接按宿主页面的视觉语言写,用宿主的
|
|
3505
|
-
`--dsw-alias-*` token:几何对齐它(14px 小标题、12px 次要文字、0.5px 描边、
|
|
3506
|
-
12px 圆角),配色也从同一套 token 取 —— 于是这块面板看起来就是原生的一部分。
|
|
3507
|
-
主按钮不用在这里画:交给宿主的 Button 原语(见 env.d.ts),配色由原语自己的
|
|
3508
|
-
token 家族决定,不必猜「主色底上配什么字色」。
|
|
3509
|
-
============================================================ */
|
|
3510
|
-
|
|
3511
|
-
.dshr-comp {
|
|
3512
|
-
gap: 16px;
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
.dshr-compIntro {
|
|
3516
|
-
margin: 0;
|
|
3517
|
-
color: var(--dsw-alias-label-secondary);
|
|
3518
|
-
font-size: 13px;
|
|
3519
|
-
line-height: 20px;
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
/* 三栏收进同一块面板,读起来是「一组控件」而不是三个飘着的输入框。
|
|
3523
|
-
窄了换行(详情区宽度随窗口变,写死三列会在窄窗口挤成一团)。 */
|
|
3524
|
-
.dshr-compForm {
|
|
3525
|
-
display: grid;
|
|
3526
|
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
3527
|
-
gap: 14px 16px;
|
|
3528
|
-
padding: 14px 16px;
|
|
3529
|
-
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
3530
|
-
border-radius: 12px;
|
|
3531
|
-
background: var(--dsw-alias-bg-layer-1);
|
|
3532
|
-
}
|
|
3533
|
-
|
|
3534
|
-
.dshr-compField {
|
|
3535
|
-
display: flex;
|
|
3536
|
-
flex-direction: column;
|
|
3537
|
-
gap: 6px;
|
|
3538
|
-
min-width: 0;
|
|
3539
|
-
}
|
|
3540
|
-
|
|
3541
|
-
.dshr-compFieldHead {
|
|
3542
|
-
display: flex;
|
|
3543
|
-
align-items: baseline;
|
|
3544
|
-
justify-content: space-between;
|
|
3545
|
-
gap: 8px;
|
|
3546
|
-
}
|
|
3547
|
-
|
|
3548
|
-
.dshr-compFieldLabel {
|
|
3549
|
-
color: var(--dsw-alias-label-primary);
|
|
3550
|
-
font-size: 12px;
|
|
3551
|
-
font-weight: 500;
|
|
3552
|
-
line-height: 18px;
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
/* 标题右侧的用法要点。语义放这里比塞进顶部那段长文字更容易被读到 ——
|
|
3556
|
-
「模型只列可用的」「连接留空 = 池内任选」都是用这一栏时才关心的。 */
|
|
3557
|
-
.dshr-compFieldNote {
|
|
3558
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3559
|
-
font-size: 12px;
|
|
3560
|
-
line-height: 18px;
|
|
3561
|
-
text-align: right;
|
|
3562
|
-
}
|
|
3563
|
-
|
|
3564
|
-
/* 36px / 12px 圆角,与宿主 Button 原语的 md 尺寸对齐。 */
|
|
3565
|
-
.dshr-compSelect {
|
|
3566
|
-
width: 100%;
|
|
3567
|
-
height: 36px;
|
|
3568
|
-
padding: 0 32px 0 11px;
|
|
3569
|
-
appearance: none;
|
|
3570
|
-
border: 0.5px solid var(--dsw-alias-border-l2);
|
|
3571
|
-
border-radius: 12px;
|
|
3572
|
-
background-color: var(--dsw-alias-bg-layer-2);
|
|
3573
|
-
background-image:
|
|
3574
|
-
linear-gradient(45deg, transparent 50%, var(--dsw-alias-label-tertiary) 50%),
|
|
3575
|
-
linear-gradient(135deg, var(--dsw-alias-label-tertiary) 50%, transparent 50%);
|
|
3576
|
-
background-position:
|
|
3577
|
-
calc(100% - 17px) 16px,
|
|
3578
|
-
calc(100% - 12px) 16px;
|
|
3579
|
-
background-size: 5px 5px;
|
|
3580
|
-
background-repeat: no-repeat;
|
|
3581
|
-
color: var(--dsw-alias-label-primary);
|
|
3582
|
-
font: inherit;
|
|
3583
|
-
font-size: 13px;
|
|
3584
|
-
line-height: 18px;
|
|
3585
|
-
text-overflow: ellipsis;
|
|
3586
|
-
cursor: pointer;
|
|
3587
|
-
}
|
|
3588
|
-
|
|
3589
|
-
.dshr-compSelect:hover:not(:disabled) {
|
|
3590
|
-
border-color: var(--dsw-alias-border-l3);
|
|
3591
|
-
}
|
|
3592
|
-
|
|
3593
|
-
.dshr-compSelect:focus {
|
|
3594
|
-
outline: none;
|
|
3595
|
-
border-color: var(--dsw-alias-brand-primary);
|
|
3596
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-brand-primary) 22%, transparent);
|
|
3597
|
-
}
|
|
3598
|
-
|
|
3599
|
-
.dshr-compSelect:disabled {
|
|
3600
|
-
border-color: var(--dsw-alias-border-l1);
|
|
3601
|
-
background-color: var(--dsw-alias-bg-layer-1);
|
|
3602
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3603
|
-
cursor: not-allowed;
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
|
-
/* 动作行:把按钮与它旁边的提示排在一条基线上。两个动作行(访问测试 / 契约体检)
|
|
3607
|
-
共用 `dshr-compBtnRow` —— 那条抬特异性的字色规则就挂在它上面(见下)。 */
|
|
3608
|
-
.dshr-compActions {
|
|
3609
|
-
display: flex;
|
|
3610
|
-
align-items: center;
|
|
3611
|
-
gap: 12px;
|
|
3612
|
-
}
|
|
3613
|
-
|
|
3614
|
-
.dshr-compHint {
|
|
3615
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3616
|
-
font-size: 12px;
|
|
3617
|
-
line-height: 18px;
|
|
3618
|
-
}
|
|
3619
|
-
|
|
3620
|
-
/* 结果区:成功/失败/请求失败 三种语气。底下垫一层极淡的状态色,比只有一行
|
|
3621
|
-
彩色文字更容易扫到 —— 自检的结果是这一页唯一重要的信息。 */
|
|
3622
|
-
.dshr-compNotice {
|
|
3623
|
-
display: flex;
|
|
3624
|
-
gap: 10px;
|
|
3625
|
-
padding: 12px 14px;
|
|
3626
|
-
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
3627
|
-
border-radius: 12px;
|
|
3628
|
-
background: var(--dsw-alias-bg-layer-1);
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
|
-
.dshr-compNotice[data-tone="ok"] {
|
|
3632
|
-
border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 34%, transparent);
|
|
3633
|
-
background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 8%, transparent);
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
.dshr-compNotice[data-tone="fail"],
|
|
3637
|
-
.dshr-compNotice[data-tone="network"] {
|
|
3638
|
-
border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 34%, transparent);
|
|
3639
|
-
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
|
|
3640
|
-
}
|
|
3641
|
-
|
|
3642
|
-
.dshr-compNoticeIcon {
|
|
3643
|
-
flex: none;
|
|
3644
|
-
width: 16px;
|
|
3645
|
-
height: 16px;
|
|
3646
|
-
margin-top: 2px;
|
|
3647
|
-
color: var(--dsw-alias-state-success-secondary);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
.dshr-compNotice[data-tone="fail"] .dshr-compNoticeIcon,
|
|
3651
|
-
.dshr-compNotice[data-tone="network"] .dshr-compNoticeIcon {
|
|
3652
|
-
color: var(--dsw-alias-state-error-primary);
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
.dshr-compNoticeBody {
|
|
3656
|
-
display: flex;
|
|
3657
|
-
flex-direction: column;
|
|
3658
|
-
gap: 2px;
|
|
3659
|
-
min-width: 0;
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3662
|
-
.dshr-compNoticeTitle {
|
|
3663
|
-
color: var(--dsw-alias-label-primary);
|
|
3664
|
-
font-size: 13px;
|
|
3665
|
-
font-weight: 500;
|
|
3666
|
-
line-height: 20px;
|
|
3667
|
-
}
|
|
3668
|
-
|
|
3669
|
-
.dshr-compNoticeText {
|
|
3670
|
-
color: var(--dsw-alias-label-secondary);
|
|
3671
|
-
font-size: 12.5px;
|
|
3672
|
-
line-height: 19px;
|
|
3673
|
-
/* 上游可能回一整段 HTML/JSON,限定宽度换行,别把布局撑破 */
|
|
3674
|
-
overflow-wrap: anywhere;
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
/* 回显测的是哪三样。一条「通过」如果不带对象,就是没法归因的一串绿字。 */
|
|
3678
|
-
.dshr-compNoticeMeta {
|
|
3679
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3680
|
-
font-size: 12px;
|
|
3681
|
-
line-height: 18px;
|
|
3682
|
-
overflow-wrap: anywhere;
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
|
-
/* 宿主 Button 原语的 primary 想把字色设成 --dsw-alias-label-primary-foreground,
|
|
3686
|
-
但上面那条兜底 `.dshr-settings button { color: inherit; font: inherit }`
|
|
3687
|
-
特异性 (1,1) **高于**原语的 `.primary` (1,0) —— 赢的是它。于是按钮变成
|
|
3688
|
-
「#0f1115 的底 + 继承来的 #0f1115 的字」,黑底黑字、按钮看着是空的。
|
|
3689
|
-
|
|
3690
|
-
按本文件既有的抬特异性办法修(见文件里 .dshr-tab-on / .dshr-period 那几处注释:
|
|
3691
|
-
凡是受这条兜底影响的规则,都必须带挂载容器抬到 (2,0) 以上)。这里
|
|
3692
|
-
`.dshr-comp .dshr-compBtnRow button` 是 (2,1),不依赖源码顺序,以后有人挪块也不会翻车。
|
|
3693
|
-
|
|
3694
|
-
作用域挂在**动作行的共用类 `dshr-compBtnRow`** 上,不是某个具体容器:上一版这条
|
|
3695
|
-
只写了 `.dshr-compActions`,随后新增的「契约体检」按钮在另一个容器里,于是又变回
|
|
3696
|
-
黑底黑字。现在两个动作行共用这个类,再加按钮只需沿用它。
|
|
3697
|
-
|
|
3698
|
-
修的是**把原语的意图还回去**,不是自己另挑一个颜色:字色仍由原语那个 token 决定。 */
|
|
3699
|
-
.dshr-comp .dshr-compBtnRow button {
|
|
3700
|
-
color: var(--dsw-alias-label-primary-foreground);
|
|
3701
|
-
font-size: 14px;
|
|
3702
|
-
line-height: 22px;
|
|
3703
|
-
}
|
|
3704
|
-
|
|
3705
|
-
/* --- 契约体检区(插件自检面板的第二块) ---
|
|
3706
|
-
状态靠 data-state 上色,和「结果卡」那套语气一致:过了是绿、失败是红、
|
|
3707
|
-
没实现是灰、不执行是中性(它是**故意**不跑的,不是问题)。 */
|
|
3708
|
-
|
|
3709
|
-
.dshr-compSection {
|
|
3710
|
-
display: flex;
|
|
3711
|
-
flex-direction: column;
|
|
3712
|
-
gap: 12px;
|
|
3713
|
-
margin-top: 4px;
|
|
3714
|
-
padding-top: 16px;
|
|
3715
|
-
border-top: 0.5px solid var(--dsw-alias-border-l1);
|
|
3716
|
-
}
|
|
3717
|
-
|
|
3718
|
-
.dshr-compHead {
|
|
3719
|
-
display: flex;
|
|
3720
|
-
align-items: baseline;
|
|
3721
|
-
justify-content: space-between;
|
|
3722
|
-
gap: 8px;
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
|
-
.dshr-compSectionTitle {
|
|
3726
|
-
margin: 0;
|
|
3727
|
-
color: var(--dsw-alias-label-primary);
|
|
3728
|
-
font-size: 14px;
|
|
3729
|
-
font-weight: 500;
|
|
3730
|
-
line-height: 20px;
|
|
3731
|
-
}
|
|
3732
|
-
|
|
3733
|
-
.dshr-compSectionNote {
|
|
3734
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3735
|
-
font-size: 12px;
|
|
3736
|
-
line-height: 18px;
|
|
3737
|
-
}
|
|
3738
|
-
|
|
3739
|
-
.dshr-compProbeRow {
|
|
3740
|
-
display: flex;
|
|
3741
|
-
align-items: flex-end;
|
|
3742
|
-
gap: 12px;
|
|
3743
|
-
}
|
|
3744
|
-
|
|
3745
|
-
.dshr-compProbeRow .dshr-compField {
|
|
3746
|
-
flex: 1;
|
|
3747
|
-
}
|
|
3748
|
-
|
|
3749
|
-
.dshr-compProbeBtn {
|
|
3750
|
-
flex: none;
|
|
3751
|
-
}
|
|
3752
|
-
|
|
3753
|
-
.dshr-compReport {
|
|
3754
|
-
display: flex;
|
|
3755
|
-
flex-direction: column;
|
|
3756
|
-
gap: 8px;
|
|
3757
|
-
padding: 12px 14px;
|
|
3758
|
-
border: 0.5px solid var(--dsw-alias-border-l1);
|
|
3759
|
-
border-radius: 12px;
|
|
3760
|
-
background: var(--dsw-alias-bg-layer-1);
|
|
3761
|
-
}
|
|
3762
|
-
|
|
3763
|
-
.dshr-compReportHead {
|
|
3764
|
-
display: flex;
|
|
3765
|
-
align-items: baseline;
|
|
3766
|
-
justify-content: space-between;
|
|
3767
|
-
gap: 10px;
|
|
3768
|
-
flex-wrap: wrap;
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
.dshr-compReportName {
|
|
3772
|
-
color: var(--dsw-alias-label-primary);
|
|
3773
|
-
font-size: 13px;
|
|
3774
|
-
font-weight: 500;
|
|
3775
|
-
line-height: 20px;
|
|
3776
|
-
}
|
|
3777
|
-
|
|
3778
|
-
.dshr-compReportSum {
|
|
3779
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3780
|
-
font-size: 12px;
|
|
3781
|
-
line-height: 18px;
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
.dshr-compMembers {
|
|
3785
|
-
display: flex;
|
|
3786
|
-
flex-direction: column;
|
|
3787
|
-
margin: 0;
|
|
3788
|
-
padding: 0;
|
|
3789
|
-
list-style: none;
|
|
3790
|
-
}
|
|
3791
|
-
|
|
3792
|
-
.dshr-compMember {
|
|
3793
|
-
display: grid;
|
|
3794
|
-
grid-template-columns: 16px minmax(0, auto) minmax(0, 1fr);
|
|
3795
|
-
align-items: baseline;
|
|
3796
|
-
gap: 10px;
|
|
3797
|
-
padding: 5px 0;
|
|
3798
|
-
border-bottom: 0.5px solid var(--dsw-alias-border-l1);
|
|
3799
|
-
}
|
|
3800
|
-
|
|
3801
|
-
.dshr-compMember:last-child {
|
|
3802
|
-
border-bottom: 0;
|
|
3803
|
-
}
|
|
3804
|
-
|
|
3805
|
-
.dshr-compMemberState {
|
|
3806
|
-
flex: none;
|
|
3807
|
-
text-align: center;
|
|
3808
|
-
font-size: 12px;
|
|
3809
|
-
line-height: 18px;
|
|
3810
|
-
}
|
|
3811
|
-
|
|
3812
|
-
.dshr-compMember[data-state="ok"] .dshr-compMemberState {
|
|
3813
|
-
color: var(--dsw-alias-state-success-secondary);
|
|
3814
|
-
}
|
|
3815
|
-
|
|
3816
|
-
.dshr-compMember[data-state="fail"] .dshr-compMemberState {
|
|
3817
|
-
color: var(--dsw-alias-state-error-primary);
|
|
3818
|
-
}
|
|
3819
|
-
|
|
3820
|
-
.dshr-compMember[data-state="absent"] .dshr-compMemberState,
|
|
3821
|
-
.dshr-compMember[data-state="skipped"] .dshr-compMemberState {
|
|
3822
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3823
|
-
}
|
|
3824
|
-
|
|
3825
|
-
.dshr-compMemberName {
|
|
3826
|
-
color: var(--dsw-alias-label-primary);
|
|
3827
|
-
font-size: 12.5px;
|
|
3828
|
-
line-height: 19px;
|
|
3829
|
-
white-space: nowrap;
|
|
3830
|
-
}
|
|
3831
|
-
|
|
3832
|
-
.dshr-compMemberKey {
|
|
3833
|
-
margin-left: 6px;
|
|
3834
|
-
color: var(--dsw-alias-label-tertiary);
|
|
3835
|
-
font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
3836
|
-
font-size: 11px;
|
|
3837
|
-
}
|
|
3838
|
-
|
|
3839
|
-
.dshr-compMemberDetail {
|
|
3840
|
-
color: var(--dsw-alias-label-secondary);
|
|
3841
|
-
font-size: 12px;
|
|
3842
|
-
line-height: 18px;
|
|
3843
|
-
overflow-wrap: anywhere;
|
|
3844
|
-
}
|
|
3845
|
-
|
|
3846
|
-
|
package/src/ext/contract.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface RouterExt {
|
|
|
68
68
|
* 扩展自带的**子开关**(可选;不声明 = 只有总开关)。
|
|
69
69
|
*
|
|
70
70
|
* 为什么需要:有些扩展的「开/关」不是一件事而是**几件独立的事** —— 例如分层
|
|
71
|
-
*
|
|
71
|
+
* 提示词的准则分类,每条都该能单独关。硬塞进一个总开关 = 要么全给、
|
|
72
72
|
* 要么全不给。
|
|
73
73
|
*
|
|
74
74
|
* ⚠️ **与总开关的关系**:子开关是**叠加**在总开关之上的,总开关关 ⇒ 全部子开关
|
|
@@ -179,6 +179,25 @@ export interface ExtControl {
|
|
|
179
179
|
readonly custom?: boolean
|
|
180
180
|
/** 文本被用户改过(决定「还原」按钮是否可点)。 */
|
|
181
181
|
readonly overridden?: boolean
|
|
182
|
+
/**
|
|
183
|
+
* 分组名(可选;缺省 = 无组)—— 面板按它把控制条**分节**渲染,组间插一条
|
|
184
|
+
* 组标题行,组内顺序照旧。**无组的条目照旧平铺** ⇒ 没用这个字段的扩展
|
|
185
|
+
* 面板行为一个像素都不变。
|
|
186
|
+
*
|
|
187
|
+
* 为什么要有:一条扩展同时管两类东西时(分层提示词 = 准则分类 + 宿主段落),
|
|
188
|
+
* 平铺成一长列会让"哪条是准则、哪条是宿主的段"无从分辨 —— 分组是**归属
|
|
189
|
+
* 信息**,不是样式。
|
|
190
|
+
*/
|
|
191
|
+
readonly group?: string
|
|
192
|
+
/**
|
|
193
|
+
* 这一条参与拖动排序(可选;缺省 = 参与,保持既有行为)。
|
|
194
|
+
*
|
|
195
|
+
* 为什么需要显式关掉:排序落盘只认扩展自己的条目 id(`setControlOrder`
|
|
196
|
+
* 过滤未知 id)—— 宿主段落的 id 不在其中,拖它只会被静默丢弃 ⇒ 看起来能拖、
|
|
197
|
+
* 松手弹回,是假交互。**由扩展逐条声明**,与 `editable` 同一理由:能力在
|
|
198
|
+
* 实现方手里,由它说哪条有。
|
|
199
|
+
*/
|
|
200
|
+
readonly sortable?: boolean
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
/** 面板/API 用的一条扩展器信息 = 核心存的开关 + 插件报的运行时事实。 */
|