@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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tabs made only of Host fields (「会话」「模型」「附件与工具」): one titled
|
|
3
|
+
* section per field group the tab owns (`TAB_FIELD_GROUPS`).
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-channels/client/panel/fields-tab
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ReactNode } from 'react'
|
|
9
|
+
import { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
10
|
+
import type { FieldGroup, PanelTabId } from '../../settings/contract.ts'
|
|
11
|
+
import { FieldSections } from './shared.tsx'
|
|
12
|
+
import type { FormView } from './shared.tsx'
|
|
13
|
+
|
|
14
|
+
/** Footnotes under one group's section, per group. */
|
|
15
|
+
const FOOTNOTES: Partial<Record<FieldGroup, ReactNode>> = {
|
|
16
|
+
model: PANEL_COPY.modelFootnote,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function FieldsTab(props: { form: FormView; tab: PanelTabId; note?: string }): JSX.Element {
|
|
20
|
+
const { form, tab, note } = props
|
|
21
|
+
return (
|
|
22
|
+
<div className="dsh-channels-cfg__tabBody">
|
|
23
|
+
{note === undefined ? null : <p className="dsh-channels-cfg__secFoot">{note}</p>}
|
|
24
|
+
<FieldSections form={form} groups={TAB_FIELD_GROUPS[tab]} footnotes={FOOTNOTES} />
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 设置 →「飞书渠道」页面:数据加载、5 秒状态轮询、全局 draft
|
|
3
|
-
* tab
|
|
2
|
+
* 设置 →「飞书渠道」页面:数据加载、5 秒状态轮询、全局 draft、顶部状态头 /
|
|
3
|
+
* tab 导航与底部保存条的外壳。
|
|
4
4
|
*
|
|
5
5
|
* 所有 Host 交互(panel / status / catalog / config / credentials / access /
|
|
6
6
|
* pick-directory)都集中在这里;tab 组件只接收渲染所需的数据与
|
|
@@ -48,14 +48,15 @@ import { applyCatalogFields, catalogFromFields } from '../catalog.ts'
|
|
|
48
48
|
import { AccessTab } from './access-tab.tsx'
|
|
49
49
|
import { AdvancedTab } from './advanced-tab.tsx'
|
|
50
50
|
import { ConnectionTab } from './connection-tab.tsx'
|
|
51
|
-
import {
|
|
52
|
-
import { SessionsTab } from './sessions-tab.tsx'
|
|
51
|
+
import { FieldsTab } from './fields-tab.tsx'
|
|
53
52
|
import {
|
|
54
53
|
fieldLabel,
|
|
54
|
+
formatUptime,
|
|
55
55
|
messageOf,
|
|
56
56
|
NoticeBar,
|
|
57
57
|
phaseBadge,
|
|
58
|
-
|
|
58
|
+
Pill,
|
|
59
|
+
SaveBar,
|
|
59
60
|
validateDraft,
|
|
60
61
|
} from './shared.tsx'
|
|
61
62
|
import type {
|
|
@@ -67,7 +68,6 @@ import type {
|
|
|
67
68
|
Notice,
|
|
68
69
|
} from './shared.tsx'
|
|
69
70
|
import { WikiTab } from './wiki-tab.tsx'
|
|
70
|
-
import { WorkspaceTab } from './workspace-tab.tsx'
|
|
71
71
|
|
|
72
72
|
/** Owner props handed to a `settings.section` contribution (unused beyond parity). */
|
|
73
73
|
export interface FeishuChannelsSectionProps {
|
|
@@ -316,24 +316,14 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
316
316
|
}
|
|
317
317
|
}, [draft, panel])
|
|
318
318
|
|
|
319
|
-
/** Drop the draft
|
|
320
|
-
const
|
|
319
|
+
/** Drop the whole draft (nothing is written). */
|
|
320
|
+
const discardDraft = useCallback((): void => {
|
|
321
321
|
if (panel === null) return
|
|
322
|
-
|
|
323
|
-
const keys = new Set(
|
|
324
|
-
panel.config.fields.filter(field => groups.has(field.group)).map(field => field.key),
|
|
325
|
-
)
|
|
326
|
-
setDraft((previous) => {
|
|
327
|
-
const next = { ...previous }
|
|
328
|
-
for (const key of keys) delete next[key]
|
|
329
|
-
return next
|
|
330
|
-
})
|
|
322
|
+
setDraft({})
|
|
331
323
|
setNotice(null)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
setCatalogBusy(false)
|
|
336
|
-
}
|
|
324
|
+
catalogTicket.current += 1
|
|
325
|
+
setCatalog(catalogFromFields(panel.config.fields))
|
|
326
|
+
setCatalogBusy(false)
|
|
337
327
|
}, [panel])
|
|
338
328
|
|
|
339
329
|
/**
|
|
@@ -359,6 +349,10 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
359
349
|
|
|
360
350
|
/** Drop every Web override and return the refreshed panel. */
|
|
361
351
|
const onClear = useCallback(async (): Promise<void> => {
|
|
352
|
+
const confirmed = typeof window.confirm === 'function'
|
|
353
|
+
? window.confirm(PANEL_COPY.clearConfirm)
|
|
354
|
+
: true
|
|
355
|
+
if (!confirmed) return
|
|
362
356
|
setBusy('clear')
|
|
363
357
|
setNotice(null)
|
|
364
358
|
try {
|
|
@@ -624,6 +618,10 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
624
618
|
[notice],
|
|
625
619
|
)
|
|
626
620
|
const dirtyCount = Object.keys(draft).length
|
|
621
|
+
const restartCount = useMemo(
|
|
622
|
+
() => (panel?.config.fields ?? []).filter(field => field.restartRequired && field.key in draft).length,
|
|
623
|
+
[draft, panel],
|
|
624
|
+
)
|
|
627
625
|
|
|
628
626
|
/** Which tabs currently own a dirty key (the dot is per tab, not global). */
|
|
629
627
|
const dirtyTabs = useMemo<ReadonlySet<PanelTabId>>(() => {
|
|
@@ -649,13 +647,10 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
649
647
|
issues,
|
|
650
648
|
busy,
|
|
651
649
|
locked: hostBusy,
|
|
652
|
-
dirtyCount,
|
|
653
650
|
onChange: updateDraft,
|
|
654
|
-
onSave: () => { void onSave() },
|
|
655
|
-
onDiscardTab: discardTab,
|
|
656
651
|
onPickDirectory,
|
|
657
652
|
}
|
|
658
|
-
}, [busy,
|
|
653
|
+
}, [busy, draft, hostBusy, issues, onPickDirectory, panel, renderedFields, updateDraft])
|
|
659
654
|
|
|
660
655
|
const credentials = useMemo<CredentialsView>(() => ({
|
|
661
656
|
appId: appIdInput,
|
|
@@ -713,8 +708,14 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
713
708
|
locked={hostBusy}
|
|
714
709
|
onRefresh={() => { void onRefresh() }}
|
|
715
710
|
/>
|
|
716
|
-
<TabNav
|
|
717
|
-
|
|
711
|
+
<TabNav
|
|
712
|
+
activeTab={activeTab}
|
|
713
|
+
dirtyTabs={dirtyTabs}
|
|
714
|
+
counts={{ access: load.panel.access.loaded ? load.panel.access.requests.length : 0 }}
|
|
715
|
+
onChange={setActiveTab}
|
|
716
|
+
/>
|
|
717
|
+
{load.panel.access.mode === 'open' ? <div className="dsh-channels-cfg__banner">{PANEL_COPY.debugModeWarning}</div> : null}
|
|
718
|
+
{notice === null ? null : <NoticeBar kind={notice.kind} text={notice.text} />}
|
|
718
719
|
<div
|
|
719
720
|
role="tabpanel"
|
|
720
721
|
id={`dsh-channels-tabpanel-${activeTab}`}
|
|
@@ -731,13 +732,11 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
731
732
|
/>
|
|
732
733
|
)
|
|
733
734
|
: null}
|
|
734
|
-
{activeTab === 'sessions' ? <
|
|
735
|
-
{activeTab === 'model'
|
|
736
|
-
|
|
737
|
-
? (
|
|
738
|
-
<WorkspaceTab form={form} />
|
|
739
|
-
)
|
|
735
|
+
{activeTab === 'sessions' ? <FieldsTab form={form} tab="sessions" /> : null}
|
|
736
|
+
{activeTab === 'model'
|
|
737
|
+
? <FieldsTab form={form} tab="model" note={catalogBusy ? PANEL_COPY.catalogLoading : undefined} />
|
|
740
738
|
: null}
|
|
739
|
+
{activeTab === 'attachments' ? <FieldsTab form={form} tab="attachments" /> : null}
|
|
741
740
|
{activeTab === 'access'
|
|
742
741
|
? (
|
|
743
742
|
<AccessTab
|
|
@@ -764,14 +763,22 @@ export function FeishuChannelsSection(_props: FeishuChannelsSectionProps): JSX.E
|
|
|
764
763
|
)
|
|
765
764
|
: null}
|
|
766
765
|
{activeTab === 'advanced'
|
|
767
|
-
? <AdvancedTab form={form} onClear={() => { void onClear() }} />
|
|
766
|
+
? <AdvancedTab form={form} analyzeToken={analyzeToken} onClear={() => { void onClear() }} />
|
|
768
767
|
: null}
|
|
769
768
|
</div>
|
|
769
|
+
<SaveBar
|
|
770
|
+
dirtyCount={dirtyCount}
|
|
771
|
+
restartCount={restartCount}
|
|
772
|
+
busy={busy}
|
|
773
|
+
locked={hostBusy}
|
|
774
|
+
onSave={() => { void onSave() }}
|
|
775
|
+
onDiscard={discardDraft}
|
|
776
|
+
/>
|
|
770
777
|
</div>
|
|
771
778
|
)
|
|
772
779
|
}
|
|
773
780
|
|
|
774
|
-
/** Page header: title
|
|
781
|
+
/** Page header: title, phase pill, version and uptime, refresh; visible on every tab. */
|
|
775
782
|
function PanelHeader(props: {
|
|
776
783
|
status: ChannelStatusWire
|
|
777
784
|
pollError: string | null
|
|
@@ -782,24 +789,24 @@ function PanelHeader(props: {
|
|
|
782
789
|
const { status, pollError, busy, locked, onRefresh } = props
|
|
783
790
|
const badge = phaseBadge(status.phase)
|
|
784
791
|
return (
|
|
785
|
-
<header className="dsh-channels-cfg__panelHead"
|
|
792
|
+
<header className="dsh-channels-cfg__panelHead">
|
|
786
793
|
<div className="dsh-channels-cfg__headRow">
|
|
787
794
|
<h2 className="dsh-channels-cfg__title">{PANEL_COPY.title}</h2>
|
|
788
|
-
<
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
795
|
+
<Pill tone={badge.tone} dot>{badge.text}</Pill>
|
|
796
|
+
<span className="dsh-channels-cfg__meta dsh-channels-cfg__mono">v{status.version}</span>
|
|
797
|
+
{status.uptimeMs === undefined
|
|
798
|
+
? null
|
|
799
|
+
: <span className="dsh-channels-cfg__meta">· {PANEL_COPY.uptimePrefix} {formatUptime(status.uptimeMs)}</span>}
|
|
792
800
|
<span className="dsh-channels-cfg__spacer" />
|
|
793
801
|
<button
|
|
794
802
|
type="button"
|
|
795
|
-
className="dsh-channels-cfg__btn"
|
|
803
|
+
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--ghost"
|
|
796
804
|
onClick={onRefresh}
|
|
797
805
|
disabled={busy !== null || locked}
|
|
798
806
|
>
|
|
799
807
|
{PANEL_COPY.refresh}
|
|
800
808
|
</button>
|
|
801
809
|
</div>
|
|
802
|
-
<p className="dsh-channels-cfg__summary">{phaseSummary(status.phase)}</p>
|
|
803
810
|
{pollError === null
|
|
804
811
|
? null
|
|
805
812
|
: (
|
|
@@ -815,9 +822,11 @@ function PanelHeader(props: {
|
|
|
815
822
|
function TabNav(props: {
|
|
816
823
|
activeTab: PanelTabId
|
|
817
824
|
dirtyTabs: ReadonlySet<PanelTabId>
|
|
825
|
+
/** Items waiting for the operator per tab (pending access requests). */
|
|
826
|
+
counts: Partial<Record<PanelTabId, number>>
|
|
818
827
|
onChange: (tab: PanelTabId) => void
|
|
819
828
|
}): JSX.Element {
|
|
820
|
-
const { activeTab, dirtyTabs, onChange } = props
|
|
829
|
+
const { activeTab, dirtyTabs, counts, onChange } = props
|
|
821
830
|
const refs = useRef<Array<HTMLButtonElement | null>>([])
|
|
822
831
|
|
|
823
832
|
/** Roving-focus keyboard navigation between tabs (WAI-ARIA tabs pattern). */
|
|
@@ -856,12 +865,12 @@ function TabNav(props: {
|
|
|
856
865
|
onKeyDown={event => onKeyDown(index, event)}
|
|
857
866
|
>
|
|
858
867
|
{tab.label}
|
|
868
|
+
{(counts[tab.id] ?? 0) > 0 ? <span className="dsh-channels-cfg__tabCount">{counts[tab.id]}</span> : null}
|
|
859
869
|
{dirty
|
|
860
870
|
? (
|
|
861
871
|
<span
|
|
862
872
|
className="dsh-channels-cfg__tabDot"
|
|
863
|
-
|
|
864
|
-
aria-label={PANEL_COPY.dirtyCount}
|
|
873
|
+
aria-hidden="true"
|
|
865
874
|
/>
|
|
866
875
|
)
|
|
867
876
|
: null}
|