@modesto_ma/dsh-channels 0.5.0-beta.4 → 0.5.0-beta.5
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 +26 -22
- package/docs/attachment-pipeline-v2.md +1 -1
- package/docs/feishu-onboarding.md +2 -2
- package/lib/client.js +1304 -1490
- package/lib/client.js.map +1 -1
- package/lib/index.js +400 -348
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/number-field.ts +97 -0
- package/src/client/panel/access-tab.tsx +61 -20
- package/src/client/panel/advanced-tab.tsx +59 -68
- package/src/client/panel/connection-tab.tsx +147 -164
- package/src/client/panel/credentials-card.tsx +124 -87
- package/src/client/panel/fields-tab.tsx +27 -0
- package/src/client/panel/index.tsx +55 -46
- package/src/client/panel/shared.tsx +358 -253
- package/src/client/panel/whitelist.tsx +197 -0
- package/src/client/panel/wiki-tab.tsx +24 -38
- package/src/client/styles.ts +108 -91
- package/src/index.ts +1 -1
- package/src/settings/contract.ts +197 -152
- package/src/settings/surface.ts +179 -197
- package/src/client/panel/access-card.tsx +0 -329
- package/src/client/panel/analyze-token-card.tsx +0 -70
- package/src/client/panel/model-tab.tsx +0 -23
- package/src/client/panel/sessions-tab.tsx +0 -24
- package/src/client/panel/workspace-tab.tsx +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modesto_ma/dsh-channels",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.5",
|
|
4
4
|
"description": "DeepSeek Harness (DSH) 飞书渠道插件:一个 dsh 实例对应一个飞书机器人,私聊/群聊/话题群 @ 触发会话,话题绑定 session,卡片 2.0 流式回复;Web 设置里提供「飞书渠道」状态与常用配置面板",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器半区的 `number` 字段辅助:按 Host 描述的 `scale` 在显示单位与存储
|
|
3
|
+
* 单位之间换算、保存前校验上下界,以及字节数的 MiB 读数。
|
|
4
|
+
*
|
|
5
|
+
* 不含 JSX/React,便于单测。Host 保存时按 config schema 复核。
|
|
6
|
+
*
|
|
7
|
+
* @module dsh-channels/client/number-field
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fillCopy, PANEL_COPY } from '../settings/contract.ts'
|
|
11
|
+
import type { ConfigValue, EditableFieldWire } from '../settings/contract.ts'
|
|
12
|
+
|
|
13
|
+
/** Drop float noise from `stored / scale` (e.g. 0.30000000000000004). */
|
|
14
|
+
function tidy(value: number): number {
|
|
15
|
+
return Number(value.toPrecision(12))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Value the input shows for one field.
|
|
20
|
+
*
|
|
21
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
22
|
+
* @param value - Draft (the typed string) or effective value (stored units).
|
|
23
|
+
* @returns The input text.
|
|
24
|
+
*/
|
|
25
|
+
export function numberInputValue(field: EditableFieldWire, value: ConfigValue | undefined): string {
|
|
26
|
+
if (typeof value === 'string') return value
|
|
27
|
+
if (typeof value !== 'number') return ''
|
|
28
|
+
return String(field.scale === undefined ? value : tidy(value / field.scale))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Input `step` in display units.
|
|
33
|
+
*
|
|
34
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
35
|
+
* @returns The step, or `undefined` when the Host gives none.
|
|
36
|
+
*/
|
|
37
|
+
export function numberInputStep(field: EditableFieldWire): number | undefined {
|
|
38
|
+
if (field.step === undefined) return undefined
|
|
39
|
+
return field.scale === undefined ? field.step : tidy(field.step / field.scale)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** One bound in display units with the field's unit appended. */
|
|
43
|
+
function boundLabel(field: EditableFieldWire, stored: number): string {
|
|
44
|
+
const shown = field.scale === undefined ? stored : tidy(stored / field.scale)
|
|
45
|
+
return field.unit === undefined ? String(shown) : `${shown} ${field.unit}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Turn one number draft into its save payload, checking the Host bounds.
|
|
50
|
+
*
|
|
51
|
+
* @param field - The Host descriptor (`kind: 'number'`).
|
|
52
|
+
* @param raw - The draft: the typed string, or a stored-unit number.
|
|
53
|
+
* @returns The stored-unit value (`''` clears an optional field), or the reason it is rejected.
|
|
54
|
+
*/
|
|
55
|
+
export function numberDraftValue(
|
|
56
|
+
field: EditableFieldWire,
|
|
57
|
+
raw: ConfigValue | undefined,
|
|
58
|
+
): { ok: true; value: number | '' } | { ok: false; message: string } {
|
|
59
|
+
let stored: number
|
|
60
|
+
if (typeof raw === 'number') {
|
|
61
|
+
stored = raw
|
|
62
|
+
} else {
|
|
63
|
+
const text = typeof raw === 'string' ? raw.trim() : ''
|
|
64
|
+
if (text === '') {
|
|
65
|
+
return field.optional === true ? { ok: true, value: '' } : { ok: false, message: PANEL_COPY.numberInvalid }
|
|
66
|
+
}
|
|
67
|
+
const parsed = Number(text)
|
|
68
|
+
if (!Number.isFinite(parsed)) return { ok: false, message: PANEL_COPY.numberInvalid }
|
|
69
|
+
stored = field.scale === undefined ? parsed : Math.round(parsed * field.scale)
|
|
70
|
+
}
|
|
71
|
+
if (field.min !== undefined && stored < field.min) {
|
|
72
|
+
return { ok: false, message: fillCopy(PANEL_COPY.numberMin, { n: boundLabel(field, field.min) }) }
|
|
73
|
+
}
|
|
74
|
+
if (field.max !== undefined && stored > field.max) {
|
|
75
|
+
return { ok: false, message: fillCopy(PANEL_COPY.numberMax, { n: boundLabel(field, field.max) }) }
|
|
76
|
+
}
|
|
77
|
+
return { ok: true, value: stored }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Human reading of a byte count, e.g. `20 MiB`.
|
|
82
|
+
*
|
|
83
|
+
* @param text - The input text (bytes).
|
|
84
|
+
* @returns The reading, or `undefined` when the text is not a positive number.
|
|
85
|
+
*/
|
|
86
|
+
export function bytesReading(text: string): string | undefined {
|
|
87
|
+
const bytes = Number(text)
|
|
88
|
+
if (text.trim() === '' || !Number.isFinite(bytes) || bytes <= 0) return undefined
|
|
89
|
+
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
|
|
90
|
+
let value = bytes
|
|
91
|
+
let unit = 0
|
|
92
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
93
|
+
value /= 1024
|
|
94
|
+
unit += 1
|
|
95
|
+
}
|
|
96
|
+
return `${Number(value.toFixed(value >= 100 || unit === 0 ? 0 : 1))} ${units[unit]}`
|
|
97
|
+
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「访问授权」tab
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 「访问授权」tab:待处理申请(有才显示,置顶)、准入字段(调试模式 /
|
|
3
|
+
* 管理员 / 申请有效期)与群、用户白名单。字段保存走全局 draft,白名单增删
|
|
4
|
+
* 与申请处理是独立即时 mutation。
|
|
5
|
+
*
|
|
6
|
+
* `access.loaded === false`(飞书未连接、插件 storage 域未打开)时白名单与
|
|
7
|
+
* 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。
|
|
5
8
|
*
|
|
6
9
|
* @module dsh-channels/client/panel/access-tab
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
|
-
import { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
10
|
-
import {
|
|
11
|
-
import { FieldsCard, SaveBar, TabHelp } from './shared.tsx'
|
|
12
|
+
import { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
13
|
+
import { FieldSections, NoticeBar } from './shared.tsx'
|
|
12
14
|
import type { FormView } from './shared.tsx'
|
|
15
|
+
import { RequestList, Whitelist } from './whitelist.tsx'
|
|
13
16
|
|
|
14
17
|
export interface AccessTabProps {
|
|
15
18
|
form: FormView
|
|
@@ -24,22 +27,60 @@ export interface AccessTabProps {
|
|
|
24
27
|
|
|
25
28
|
export function AccessTab(props: AccessTabProps): JSX.Element {
|
|
26
29
|
const { form, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props
|
|
30
|
+
const { access } = form.panel
|
|
31
|
+
const working = form.busy === 'access'
|
|
32
|
+
const disabled = form.busy !== null || form.locked || !access.loaded
|
|
33
|
+
// The administrator row says who is in charge right now when the owner fallback applies.
|
|
34
|
+
const ownerAdmin = access.mode === 'guarded' && access.admin.source === 'owner' ? access.admin.userId : undefined
|
|
35
|
+
const fields = ownerAdmin === undefined
|
|
36
|
+
? form.fields
|
|
37
|
+
: form.fields.map(field => field.key === 'adminUserId'
|
|
38
|
+
? { ...field, summary: fillCopy(PANEL_COPY.accessAdminOwnerSummary, { id: ownerAdmin }) }
|
|
39
|
+
: field)
|
|
27
40
|
return (
|
|
28
41
|
<div className="dsh-channels-cfg__tabBody">
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
{accessNotice === null ? null : <NoticeBar kind="ok" text={accessNotice} />}
|
|
43
|
+
{accessError === null ? null : <NoticeBar kind="error" text={accessError} />}
|
|
44
|
+
{access.loaded && access.mode === 'guarded' && !access.admin.configured
|
|
45
|
+
? (
|
|
46
|
+
<NoticeBar
|
|
47
|
+
kind="error"
|
|
48
|
+
text={access.admin.ownerLookupError === undefined
|
|
49
|
+
? PANEL_COPY.accessAdminMissingWarning
|
|
50
|
+
: `${PANEL_COPY.accessAdminMissingWarning}(${access.admin.ownerLookupError})`}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
: null}
|
|
54
|
+
{access.loaded && access.requests.length > 0
|
|
55
|
+
? <RequestList requests={access.requests} disabled={disabled} working={working} onResolve={onResolveRequest} />
|
|
56
|
+
: null}
|
|
57
|
+
<FieldSections form={{ ...form, fields }} groups={TAB_FIELD_GROUPS.access} />
|
|
58
|
+
<Whitelist
|
|
59
|
+
title={PANEL_COPY.accessGroupsTitle}
|
|
60
|
+
entries={access.groups.map(group => ({ id: group.chatId, name: group.name, source: group.source }))}
|
|
61
|
+
avatar="群"
|
|
62
|
+
empty={PANEL_COPY.accessGroupsEmpty}
|
|
63
|
+
loaded={access.loaded}
|
|
64
|
+
addLabel={PANEL_COPY.accessAddGroup}
|
|
65
|
+
idPlaceholder={PANEL_COPY.accessGroupChatIdPlaceholder}
|
|
66
|
+
namePlaceholder={PANEL_COPY.accessGroupNamePlaceholder}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
working={working}
|
|
69
|
+
onAdd={onAddGroup}
|
|
70
|
+
onRemove={onRemoveGroup}
|
|
71
|
+
/>
|
|
72
|
+
<Whitelist
|
|
73
|
+
title={PANEL_COPY.accessUsersTitle}
|
|
74
|
+
entries={access.users.map(user => ({ id: user.userId, name: user.name, source: user.source }))}
|
|
75
|
+
empty={PANEL_COPY.accessUsersEmpty}
|
|
76
|
+
loaded={access.loaded}
|
|
77
|
+
addLabel={PANEL_COPY.accessAddUser}
|
|
78
|
+
idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}
|
|
79
|
+
namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
working={working}
|
|
82
|
+
onAdd={onAddUser}
|
|
83
|
+
onRemove={onRemoveUser}
|
|
43
84
|
/>
|
|
44
85
|
</div>
|
|
45
86
|
)
|
|
@@ -1,80 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「高级」tab
|
|
3
|
-
*
|
|
2
|
+
* 「高级」tab:日志分析接口(监听地址字段 + 访问令牌凭据行)、SDK 日志级别
|
|
3
|
+
* 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。
|
|
4
4
|
*
|
|
5
5
|
* @module dsh-channels/client/panel/advanced-tab
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
8
|
+
import { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
9
|
+
import { CredentialRow } from './credentials-card.tsx'
|
|
10
|
+
import { FieldSections, Row, Section } from './shared.tsx'
|
|
11
|
+
import type { AnalyzeTokenView, FormView } from './shared.tsx'
|
|
12
12
|
|
|
13
|
-
export function AdvancedTab(props: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<div className="dsh-channels-cfg__tabBody">
|
|
17
|
-
<TabHelp text={PANEL_COPY.advancedTabHelp} />
|
|
18
|
-
<FieldsCard form={form} groups={TAB_FIELD_GROUPS.advanced} />
|
|
19
|
-
<SaveBar form={form} tab="advanced" />
|
|
20
|
-
<AdvancedCard panel={form.panel} busy={form.busy} locked={form.locked} onClear={onClear} />
|
|
21
|
-
</div>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Read-only deployment facts; the only write action here drops the overlay file. */
|
|
26
|
-
function AdvancedCard(props: {
|
|
27
|
-
panel: PanelWire
|
|
28
|
-
busy: BusyAction | null
|
|
29
|
-
locked: boolean
|
|
13
|
+
export function AdvancedTab(props: {
|
|
14
|
+
form: FormView
|
|
15
|
+
analyzeToken: AnalyzeTokenView
|
|
30
16
|
onClear: () => void
|
|
31
17
|
}): JSX.Element {
|
|
32
|
-
const {
|
|
33
|
-
const {
|
|
18
|
+
const { form, analyzeToken, onClear } = props
|
|
19
|
+
const { panel, busy, locked } = form
|
|
20
|
+
const tokenState = panel.config.credentials.analyzeToken
|
|
34
21
|
const overridden = panel.config.overriddenKeys.length
|
|
35
22
|
return (
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
<span className="dsh-channels-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
23
|
+
<div className="dsh-channels-cfg__tabBody">
|
|
24
|
+
<FieldSections
|
|
25
|
+
form={form}
|
|
26
|
+
groups={TAB_FIELD_GROUPS.advanced}
|
|
27
|
+
extra={{
|
|
28
|
+
analyze: (
|
|
29
|
+
<CredentialRow
|
|
30
|
+
label={PANEL_COPY.analyzeTokenLabel}
|
|
31
|
+
summary={PANEL_COPY.analyzeTokenSummary}
|
|
32
|
+
state={tokenState}
|
|
33
|
+
value={analyzeToken.value}
|
|
34
|
+
secret
|
|
35
|
+
placeholder={PANEL_COPY.analyzeTokenPlaceholder}
|
|
36
|
+
issue={analyzeToken.issues.find(item => item.key === 'analyzeToken')}
|
|
37
|
+
busy={analyzeToken.busy}
|
|
38
|
+
disabled={analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable}
|
|
39
|
+
onChange={analyzeToken.onChange}
|
|
40
|
+
onSave={analyzeToken.onSave}
|
|
41
|
+
onClear={analyzeToken.onClear}
|
|
42
|
+
/>
|
|
43
|
+
),
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
<Section title={PANEL_COPY.overlayTitle}>
|
|
47
|
+
<Row
|
|
48
|
+
label={PANEL_COPY.overlayPath}
|
|
49
|
+
summary={<span className="dsh-channels-cfg__mono">{panel.status.overlayPath}</span>}
|
|
50
|
+
details={(
|
|
51
|
+
<>
|
|
52
|
+
{PANEL_COPY.overlayPathHelp}
|
|
53
|
+
{' '}
|
|
54
|
+
{overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })}
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
control={(
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--danger"
|
|
61
|
+
onClick={onClear}
|
|
62
|
+
disabled={overridden === 0 || busy !== null || locked}
|
|
63
|
+
>
|
|
64
|
+
{busy === 'clear' ? PANEL_COPY.clearing : PANEL_COPY.clear}
|
|
65
|
+
</button>
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
</Section>
|
|
69
|
+
</div>
|
|
79
70
|
)
|
|
80
71
|
}
|