@chengxianglibra/dsh-data-analysis 0.1.1 → 0.1.2
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 +83 -34
- package/lib/bin/environment.js +1 -1
- package/lib/bin/presentation-lint.js +21 -0
- package/lib/bridges.js +2 -2
- package/lib/client/credentials/create-datasource.js +67 -0
- package/lib/client/credentials/install.js +191 -0
- package/lib/client/credentials/model.js +494 -0
- package/lib/client/credentials/styles.js +17 -0
- package/lib/client/workspace-header-action.js +11 -0
- package/lib/client.js +1133 -736
- package/lib/compatibility.js +1 -1
- package/lib/datasource/authoring.js +70 -0
- package/lib/datasource/bridge-programs.js +30 -18
- package/lib/datasource/bridge.js +100 -8
- package/lib/datasource/index.js +3 -2
- package/lib/datasource/python.js +115 -0
- package/lib/datasource/resolver-program.js +112 -0
- package/lib/datasource/rpc.js +88 -0
- package/lib/datasource/service.js +687 -0
- package/lib/datasource/shell-env.js +2 -333
- package/lib/datasource/test.js +24 -51
- package/lib/environment/binding.js +54 -9
- package/lib/environment/runtime.js +89 -78
- package/lib/environment/subprocess.js +6 -1
- package/lib/environment/summary.js +3 -3
- package/lib/environment/workspace.js +5 -0
- package/lib/index.js +1 -1
- package/lib/plugin.js +168 -51
- package/lib/presentation/assets/portable.js +606 -0
- package/lib/presentation/assets/static.js +945 -0
- package/lib/presentation/build/index.js +71 -0
- package/lib/presentation/commit.js +156 -0
- package/lib/presentation/contracts/charts.js +476 -0
- package/lib/presentation/contracts/editing.js +58 -0
- package/lib/presentation/contracts/index.js +651 -0
- package/lib/presentation/contracts/interaction.js +62 -0
- package/lib/presentation/contracts/types.js +15 -0
- package/lib/presentation/contracts/values.js +28 -0
- package/lib/presentation/delivery.js +56 -0
- package/lib/presentation/files.js +83 -0
- package/lib/presentation/index.js +6 -0
- package/lib/presentation/lint.js +46 -0
- package/lib/presentation/projection/files.js +85 -0
- package/lib/presentation/projection/index.js +195 -0
- package/lib/presentation/projection/program.js +144 -0
- package/lib/presentation/projection/sql-code.js +79 -0
- package/lib/presentation/receipt.js +34 -0
- package/lib/presentation/reports.js +122 -0
- package/lib/presentation/rpc.js +148 -0
- package/lib/presentation/tool.js +122 -0
- package/lib/python-execution-contracts.js +1 -0
- package/lib/python-execution.js +232 -0
- package/lib/semantic-browser/contracts.js +80 -0
- package/lib/semantic-browser/definition.js +64 -0
- package/lib/semantic-browser/program.js +119 -0
- package/lib/semantic-browser/service.js +72 -0
- package/lib/semantic-reference/bridge.js +118 -0
- package/lib/semantic-reference/contracts.js +113 -0
- package/lib/semantic-reference/index.js +3 -0
- package/lib/semantic-reference/rpc.js +122 -0
- package/lib/semantic-reference/search.js +94 -0
- package/lib/semantic-reference/usage.js +143 -0
- package/lib/types/bridges.d.ts +2 -2
- package/lib/types/client/credentials/create-datasource.d.ts +5 -0
- package/lib/types/client/credentials/install.d.ts +1 -0
- package/lib/types/client/credentials/model.d.ts +59 -0
- package/lib/types/client/credentials/styles.d.ts +1 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +9 -0
- package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
- package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
- package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
- package/lib/types/client/presentation/chart-specials.d.ts +10 -0
- package/lib/types/client/presentation/chart-styles.d.ts +1 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
- package/lib/types/client/presentation/chart-view.d.ts +23 -0
- package/lib/types/client/presentation/copy-context.d.ts +5 -0
- package/lib/types/client/presentation/delivery-model.d.ts +54 -0
- package/lib/types/client/presentation/delivery.d.ts +77 -0
- package/lib/types/client/presentation/editor-controls.d.ts +12 -0
- package/lib/types/client/presentation/global-filters.d.ts +6 -0
- package/lib/types/client/presentation/host-entry.d.ts +12 -0
- package/lib/types/client/presentation/icons.d.ts +4 -0
- package/lib/types/client/presentation/install.d.ts +15 -0
- package/lib/types/client/presentation/markdown.d.ts +5 -0
- package/lib/types/client/presentation/model.d.ts +62 -0
- package/lib/types/client/presentation/reader.d.ts +11 -0
- package/lib/types/client/presentation/source-code-model.d.ts +16 -0
- package/lib/types/client/presentation/source-code.d.ts +11 -0
- package/lib/types/client/presentation/source-dialog.d.ts +13 -0
- package/lib/types/client/presentation/source-facts.d.ts +20 -0
- package/lib/types/client/presentation/source-format.d.ts +5 -0
- package/lib/types/client/presentation/source-highlight.d.ts +7 -0
- package/lib/types/client/presentation/sources.d.ts +13 -0
- package/lib/types/client/presentation/styles.d.ts +1 -0
- package/lib/types/client/presentation/table.d.ts +12 -0
- package/lib/types/client/presentation/use-element-width.d.ts +5 -0
- package/lib/types/client/workspace-header-action.d.ts +7 -0
- package/lib/types/client.d.ts +4 -122
- package/lib/types/compatibility.d.ts +2 -2
- package/lib/types/datasource/authoring.d.ts +25 -0
- package/lib/types/datasource/bridge.d.ts +21 -3
- package/lib/types/datasource/index.d.ts +3 -5
- package/lib/types/datasource/python.d.ts +4 -0
- package/lib/types/datasource/resolver-program.d.ts +6 -0
- package/lib/types/datasource/rpc.d.ts +4 -0
- package/lib/types/datasource/service.d.ts +112 -0
- package/lib/types/datasource/shell-env.d.ts +0 -29
- package/lib/types/datasource/test.d.ts +6 -29
- package/lib/types/environment/binding.d.ts +5 -1
- package/lib/types/environment/errors.d.ts +1 -1
- package/lib/types/environment/runtime.d.ts +5 -5
- package/lib/types/environment/types.d.ts +13 -2
- package/lib/types/index.d.ts +1 -1
- package/lib/types/plugin.d.ts +11 -5
- package/lib/types/presentation/build/index.d.ts +11 -0
- package/lib/types/presentation/commit.d.ts +4 -0
- package/lib/types/presentation/contracts/charts.d.ts +13 -0
- package/lib/types/presentation/contracts/editing.d.ts +8 -0
- package/lib/types/presentation/contracts/index.d.ts +11 -0
- package/lib/types/presentation/contracts/interaction.d.ts +8 -0
- package/lib/types/presentation/contracts/types.d.ts +252 -0
- package/lib/types/presentation/contracts/values.d.ts +9 -0
- package/lib/types/presentation/delivery.d.ts +3 -0
- package/lib/types/presentation/files.d.ts +7 -0
- package/lib/types/presentation/index.d.ts +6 -0
- package/lib/types/presentation/lint.d.ts +13 -0
- package/lib/types/presentation/projection/files.d.ts +2 -0
- package/lib/types/presentation/projection/index.d.ts +20 -0
- package/lib/types/presentation/projection/program.d.ts +2 -0
- package/lib/types/presentation/projection/sql-code.d.ts +2 -0
- package/lib/types/presentation/receipt.d.ts +14 -0
- package/lib/types/presentation/reports.d.ts +5 -0
- package/lib/types/presentation/rpc.d.ts +24 -0
- package/lib/types/presentation/tool.d.ts +12 -0
- package/lib/types/python-execution-contracts.d.ts +10 -0
- package/lib/types/python-execution.d.ts +10 -0
- package/lib/types/semantic-browser/contracts.d.ts +40 -0
- package/lib/types/semantic-browser/definition.d.ts +20 -0
- package/lib/types/semantic-browser/program.d.ts +2 -0
- package/lib/types/semantic-browser/service.d.ts +20 -0
- package/lib/types/semantic-reference/bridge.d.ts +11 -0
- package/lib/types/semantic-reference/contracts.d.ts +51 -0
- package/lib/types/semantic-reference/index.d.ts +3 -0
- package/lib/types/semantic-reference/rpc.d.ts +15 -0
- package/lib/types/semantic-reference/search.d.ts +10 -0
- package/lib/types/semantic-reference/usage.d.ts +43 -0
- package/package.json +43 -19
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +24 -0
- package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
- package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +392 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +79 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +35 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +57 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +278 -0
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +19 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples.md +42 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
- package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +138 -0
- package/lib/datasource/access.js +0 -60
- package/lib/datasource/credentials.js +0 -24
- package/lib/evidence/bridge-program.js +0 -153
- package/lib/evidence/bridge.js +0 -166
- package/lib/evidence/index.js +0 -2
- package/lib/evidence/sources.js +0 -263
- package/lib/types/datasource/access.d.ts +0 -27
- package/lib/types/evidence/bridge.d.ts +0 -40
- package/lib/types/evidence/index.d.ts +0 -4
- package/lib/types/evidence/sources.d.ts +0 -57
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +0 -57
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
- package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
package/README.md
CHANGED
|
@@ -4,25 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
DeepSeek Harness 的 Marivo 集成插件。当前包提供:
|
|
6
6
|
|
|
7
|
-
- 精确 Marivo 0.5.
|
|
7
|
+
- 精确 Marivo 0.5.4 共享 Runtime 与 zero-init per-Workspace binding;
|
|
8
8
|
- `marivo_help` 实时公共 Help transport;
|
|
9
|
+
- 会话标题旁的“语义层”对象浏览器:Workspace 分类搜索、只读详情与局部关系图;
|
|
9
10
|
- `marivo_datasource_test` 的 DSH Credentials 收集与显式 connection test;
|
|
10
|
-
- `
|
|
11
|
-
-
|
|
12
|
-
- `
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
- `marivo_python` 的本次执行准入与单次 resolver 凭证注入;
|
|
12
|
+
- 会话标题旁的“数据源与凭证”管理、测试与缺失输入提交后的原调用续接;
|
|
13
|
+
- `marivo_present({ draft_path })` 的一次文件提交、结果卡片、打开与离线 HTML 下载;
|
|
14
|
+
- 唯一插件 Skill `dsh-data-analysis-presentation`,组织图表、表格、报告、看板与可读来源展示;
|
|
15
|
+
- `dsh_data_analysis_presentation.write_dataset(frame, path)` 的 computed typed JSON writer;
|
|
16
|
+
- 内部 Artifact/computed/source-only 数据投影,保存精确来源及 unavailable 状态。
|
|
17
|
+
|
|
18
|
+
共享 reader、离线 builder、`marivo_present` 和展示 Skill 提供统一的展示交付;
|
|
19
|
+
两个 Marivo Runtime Skill `marivo-analysis`、`marivo-semantic` 继续从当前 Runtime 挂载。
|
|
20
|
+
旧 report-kit、report Skill、经典 JS 与 Evidence 卡协议不再分发。升级说明见
|
|
21
|
+
[0.1.2 发布说明](../../docs/releases/0.1.2.md)。
|
|
18
22
|
|
|
19
23
|
## Compatibility
|
|
20
24
|
|
|
21
25
|
包内 `dshDataAnalysisCompatibility` 是唯一运行时兼容声明:
|
|
22
26
|
|
|
23
27
|
- DSH distribution 与所有必需 peer 精确使用 `0.1.1-rc.2`;
|
|
24
|
-
-
|
|
25
|
-
- 项目自有 Runtime marker 为 `dsh-data-analysis-runtime/
|
|
28
|
+
- Runtime 通过 pip 安装已发布的 Marivo 0.5.4;版本约束为 `marivo[duckdb,trino,clickhouse]==0.5.4`;
|
|
29
|
+
- 项目自有 Runtime marker 为 `dsh-data-analysis-runtime/v3`;
|
|
26
30
|
- 子进程策略为 `direct-argv-inherited-env-snapshot-overlay-v2`。
|
|
27
31
|
|
|
28
32
|
管理员解释器的 `marivo.__version__` 与 package identity 必须精确匹配;不使用 capability/version matrix。
|
|
@@ -32,32 +36,57 @@ DeepSeek Harness 的 Marivo 集成插件。当前包提供:
|
|
|
32
36
|
```text
|
|
33
37
|
marivo_help({ targets: string[] })
|
|
34
38
|
marivo_datasource_test({ name: string })
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
session_id: string,
|
|
38
|
-
sources: Array<{ artifact_ref: string, finding_id: string }>
|
|
39
|
-
})
|
|
39
|
+
marivo_python({ code: string, datasources: string[] })
|
|
40
|
+
marivo_present({ draft_path: string, report_id?: string, expected_build_id?: string })
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
`marivo_datasource_test` 只拥有缺失 Credentials 的 DSH/Web 闭环和显式连接测试;
|
|
43
|
-
`
|
|
44
|
+
`marivo_python` 在本次调用内等待全部数据源凭证就绪、核验身份并取得 fresh snapshot,再通过 DSH 前台
|
|
45
|
+
执行服务与 Marivo 公开 resolver 启动一次代码。配置齐全时不额外测试;普通 Shell 不获得凭证。
|
|
46
|
+
缺失输入时默认等待 Web 表单,提交测试成功后继续原调用;取消、轮换或 Workspace 变化终止旧准备,
|
|
47
|
+
已启动代码失败不重放。无 Web 场景配置 `credentialInteraction: 'none'`,subagent 同样不等待表单。
|
|
48
|
+
详见[凭证模块](../../docs/modules/datasource-credentials.md)。`md.inspect(...)`、
|
|
44
49
|
Session recovery、Artifact revalidation、Quality、Evidence 读取、Session Graph 与 `to_pandas()` 都直接使用
|
|
45
50
|
Marivo 公共 API,不增加 convenience Tool。
|
|
46
51
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
## 语义层对象浏览器
|
|
53
|
+
|
|
54
|
+
点击 DSH 会话标题旁的“语义层”,默认查看该会话所属 Workspace,也可在面板内显式切换 Workspace。
|
|
55
|
+
可按业务域和类型筛选,搜索名称、引用与业务定义,
|
|
56
|
+
复制对象引用、查看定义位置,并在对象关系中逐层浏览。桌面采用三栏布局,窄屏支持列表与详情切换。
|
|
57
|
+
|
|
58
|
+
页面不要求 live Agent,只读取 Marivo Catalog 元数据,不执行 `observe`、数据预览或连接测试。
|
|
59
|
+
打开和手动刷新重新加载;读取失败时明确标注上次成功内容。数据源连接配置、凭证值与原始源码不展示。
|
|
60
|
+
使用及验收边界见[模块说明](../../docs/modules/semantic-browser.md)。
|
|
61
|
+
|
|
62
|
+
“数据源与凭证”位于同一会话标题旁,打开时同样使用该会话所属 Workspace,面板内保留 Workspace 选择。
|
|
63
|
+
两个入口随 Harness 的会话标题显示,无会话或空会话时不显示,侧栏底部不保留入口。
|
|
64
|
+
|
|
65
|
+
## 分析与展示
|
|
66
|
+
|
|
67
|
+
普通事实问答使用文字。图表、表格、报告、看板和可读来源展示加载 `dsh-data-analysis-presentation`,
|
|
68
|
+
按 Skill 编写 Workspace 相对 Draft,再调用 `marivo_present`。已有 Artifact 或 computed 数据无需先激活
|
|
69
|
+
`marivo-analysis`;需要新分析或语义编写时,按需加载对应 Runtime Skill 与 live Help。
|
|
70
|
+
Skill 只指导内容组织、声明来源和交付,不承担分析计算或来源有效性判断;布局由 reader 自适应。
|
|
71
|
+
完整流程见[展示 Skill](../../docs/modules/presentation-skill.md)。
|
|
72
|
+
|
|
73
|
+
Python helper 的可选 `labels={列名: 展示名}` 设置中文表头、图例和 tooltip,字段绑定仍使用原列名。
|
|
74
|
+
Python helper 只接受 pandas DataFrame,写入 `schemaVersion: 1` 的 typed JSON;int64/Decimal 保留精确字符串,
|
|
75
|
+
null 保留缺失含义,datetime 必须带时区。它不保存来源或转换代码;来源在展示 Draft 中声明。
|
|
76
|
+
固定 projection 从同一 bound Workspace 恢复 persisted Artifact 和可选 Finding,不自动 observe、revalidate 或读取凭据。
|
|
77
|
+
直接 Artifact dataset 缺必要数据会失败,computed 和 source-only 可以保留 unavailable 来源。
|
|
78
|
+
|
|
79
|
+
共享 reader 展示 Markdown、metric、18 类 chart、table 和 source,保留精确值、单位、截断与 unavailable 来源。chart 支持 bar/line 变体与当前页面的字段、图形及过滤探索;统计量由分析阶段准备,探索不改写保存快照。
|
|
80
|
+
`marivo_present` 读取 Workspace 相对 Draft 路径,生成不可变新 build 的 JSON/自包含 HTML。
|
|
81
|
+
默认创建新 Report;修改已有报告时成对传入 `report_id` 与本次修改所基于的 `expected_build_id`,以完整 Draft 更新同一 Report。
|
|
82
|
+
版本冲突时读取当前保存内容并合并需保留的编辑,不强行覆盖,也不回退新建。
|
|
83
|
+
原卡片通过 reportId 打开最近保存结果;阅读器可编辑标题、正文、指标标签、图表和表格配置,以及移动/删除 cell(允许删空),支持撤销/重做和保存/取消。
|
|
84
|
+
保存生成同报告的新 build,保留底层数据、来源和代码,不运行 Agent 或 Python。
|
|
85
|
+
全部 18 种 chart 与同 dataset 的表格和数据预览共享临时筛选;筛选不重算指标、不标记编辑修改、不进入保存、下载或打印。
|
|
86
|
+
文档、receipt 与 delivery 使用 schema v2,Draft 和 typed dataset 保持 v1;旧报告不读取、不迁移,旧文件保留。文本始终包含两份文件的位置、digest 和字节数,headless 也能取得交付物。
|
|
87
|
+
展开来源仅使用保存的快照;文件变化、缺失或 Workspace 归属变化会明确失败。
|
|
88
|
+
实现与验收见[展示数据投影](../../docs/modules/presentation-projection.md)、[展示 reader](../../docs/modules/presentation-reader.md)、
|
|
89
|
+
[展示交付](../../docs/modules/presentation-delivery.md)。
|
|
61
90
|
|
|
62
91
|
## 验证
|
|
63
92
|
|
|
@@ -67,6 +96,26 @@ npm run build
|
|
|
67
96
|
npm run verify:plugin-package
|
|
68
97
|
```
|
|
69
98
|
|
|
70
|
-
当前架构与验收边界见仓库根目录的[总体架构](../../docs/architecture.md)
|
|
71
|
-
[
|
|
72
|
-
|
|
99
|
+
当前架构与验收边界见仓库根目录的[总体架构](../../docs/architecture.md)和
|
|
100
|
+
[展示交付](../../docs/modules/presentation-delivery.md)。
|
|
101
|
+
|
|
102
|
+
## 语义对象引用
|
|
103
|
+
|
|
104
|
+
在 Web 输入框输入 `@` 选择当前 Workspace 的 Marivo 对象;多词检索使用 `@"monthly revenue"`。
|
|
105
|
+
选中后显示完整 `kind:path`,提交只携带精确 ref。最近七个自然日热度保存在 DSH profile;候选缓存为 30 秒。
|
|
106
|
+
对象有效性由执行时 Marivo Catalog 验证,删除对象不会被相似对象自动替换。
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## 草稿静态预检
|
|
110
|
+
|
|
111
|
+
在 Workspace 根目录运行 `dsh-data-analysis-presentation-lint analysis/presentation.draft.json`,
|
|
112
|
+
或通过 `--project-root PATH` 显式指定 Workspace。CLI 复用 present 的草稿与 TypedDataset 校验及文件边界,
|
|
113
|
+
检查草稿结构和引用的 computed JSON;不启动 Runtime、不执行分析、不生成报告文件。
|
|
114
|
+
输出 JSON 的 `ok` 仅表示这些静态检查通过;`deferredChecks` 列出仍由 `marivo_present` 完成的
|
|
115
|
+
Artifact 可用性与数据、codeRefs、投影后文档与渲染检查。退出码为 0(通过)、1(校验失败)、2(参数错误)。
|
|
116
|
+
草稿结构通过后逐份检查 computed 文件,每份保留首个错误,修正后可再次预检。
|
|
117
|
+
|
|
118
|
+
int64 单元格必须是精确整数字符串,例如 `"42"`;JSON number `42` 也不接受。
|
|
119
|
+
诊断保留 JSON pointer,并显示 `column query_count type=int64`、原因与修复建议。
|
|
120
|
+
超过 JS 安全整数范围的 number 可能已舍入,不能转成字符串来恢复精度;应从原始精确数据重写,
|
|
121
|
+
或使用绑定 Runtime 中的 `write_dataset`。预检通过不等于成功交付,仍需 present 的成功 receipt。
|
package/lib/bin/environment.js
CHANGED
|
@@ -7,7 +7,7 @@ import { environmentPayload } from "../environment/summary.js";
|
|
|
7
7
|
function usage() {
|
|
8
8
|
return `Usage: dsh-data-analysis-env [--project-root PATH] [--python PATH] [--runtime-root PATH] [--uv PATH]
|
|
9
9
|
|
|
10
|
-
Ensures the shared pinned Marivo and
|
|
10
|
+
Ensures the shared pinned Marivo and presentation-kit Runtime, initializes the minimal
|
|
11
11
|
Workspace layout, and checks the exact interpreter, import identities, and doctor
|
|
12
12
|
admission used by the DSH plugin. The JSON output contains no credentials or raw
|
|
13
13
|
doctor details.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { lintPresentationDraft } from "../presentation/lint.js";
|
|
5
|
+
const usage = 'Usage: dsh-data-analysis-presentation-lint DRAFT_PATH [--project-root PATH]\n';
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
8
|
+
process.stdout.write(usage);
|
|
9
|
+
}
|
|
10
|
+
else if ((args.length !== 1 && args.length !== 3) ||
|
|
11
|
+
!args[0] ||
|
|
12
|
+
args[0].startsWith('-') ||
|
|
13
|
+
(args.length === 3 && (args[1] !== '--project-root' || !args[2]))) {
|
|
14
|
+
process.stderr.write(usage);
|
|
15
|
+
process.exitCode = 2;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const result = await lintPresentationDraft(path.resolve(args[2] ?? process.cwd()), args[0]);
|
|
19
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
20
|
+
process.exitCode = result.ok ? 0 : 1;
|
|
21
|
+
}
|
package/lib/bridges.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { MarivoDatasourceBridge } from "./datasource/bridge.js";
|
|
2
2
|
import { MarivoHelpBridge } from "./disclosure/bridge.js";
|
|
3
|
-
import {
|
|
3
|
+
import { MarivoPresentationProjection } from "./presentation/projection/index.js";
|
|
4
4
|
/** Compose domain adapters without adding domain methods back to the Environment. */
|
|
5
5
|
export function createMarivoBridgeSet(environment) {
|
|
6
6
|
return Object.freeze({
|
|
7
7
|
help: new MarivoHelpBridge(environment),
|
|
8
8
|
datasource: new MarivoDatasourceBridge(environment),
|
|
9
|
-
|
|
9
|
+
presentation: new MarivoPresentationProjection(environment),
|
|
10
10
|
});
|
|
11
11
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @ts-nocheck -- JSX is bundled by the plugin client build.
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
export function CreateDatasource({ model, workspaceId, close }) {
|
|
5
|
+
const [schema, setSchema] = useState(null);
|
|
6
|
+
const [backend, setBackend] = useState('');
|
|
7
|
+
const [values, setValues] = useState({});
|
|
8
|
+
const [busy, setBusy] = useState(false);
|
|
9
|
+
const [error, setError] = useState('');
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
model
|
|
13
|
+
.authoring(workspaceId, controller.signal)
|
|
14
|
+
.then((value) => {
|
|
15
|
+
if (controller.signal.aborted)
|
|
16
|
+
return;
|
|
17
|
+
setSchema(value);
|
|
18
|
+
setBackend(value.backends[0]?.name ?? '');
|
|
19
|
+
})
|
|
20
|
+
.catch((error) => {
|
|
21
|
+
if (!controller.signal.aborted)
|
|
22
|
+
setError(error.message);
|
|
23
|
+
});
|
|
24
|
+
return () => controller.abort();
|
|
25
|
+
}, [model, workspaceId]);
|
|
26
|
+
const fields = schema?.backends.find((item) => item.name === backend)?.fields ?? [];
|
|
27
|
+
async function submit(event) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
if (busy)
|
|
30
|
+
return;
|
|
31
|
+
setError('');
|
|
32
|
+
const input = {};
|
|
33
|
+
try {
|
|
34
|
+
for (const field of fields) {
|
|
35
|
+
const value = values[field.name];
|
|
36
|
+
if (value === undefined || value === '')
|
|
37
|
+
continue;
|
|
38
|
+
input[field.name] = field.type === 'string' ? value : JSON.parse(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
setError('请检查数字、布尔值或 JSON 字段的格式。');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setBusy(true);
|
|
46
|
+
try {
|
|
47
|
+
await model.createDatasource(workspaceId, schema, { backend, fields: input });
|
|
48
|
+
close();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
setError(error.message);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setBusy(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return (_jsxs("form", { className: "mc-form", "aria-label": "\u65B0\u589E\u6570\u636E\u6E90", onSubmit: submit, children: [_jsxs("div", { className: "mc-form-body", children: [_jsx("div", { className: "mc-detail-heading", children: _jsx("h3", { children: "\u65B0\u589E\u6570\u636E\u6E90" }) }), error && (_jsx("p", { role: "alert", className: "mc-result", "data-tone": "error", children: error })), !schema && !error && _jsx("p", { role: "status", children: "\u6B63\u5728\u8BFB\u53D6\u6570\u636E\u6E90\u914D\u7F6E\u5B57\u6BB5\u2026" }), schema && (_jsxs("fieldset", { className: "mc-field", disabled: busy, children: [_jsxs("label", { className: "mc-secret-input", children: ["\u5F15\u64CE", _jsx("select", { "aria-label": "\u5F15\u64CE", value: backend, onChange: (event) => {
|
|
58
|
+
setBackend(event.target.value);
|
|
59
|
+
setValues({});
|
|
60
|
+
setError('');
|
|
61
|
+
}, children: schema.backends.map((item) => (_jsx("option", { value: item.name, children: item.name }, item.name))) })] }), fields.map((field) => (_jsxs("label", { className: "mc-secret-input", htmlFor: `mc-create-${field.name}`, children: [_jsxs("span", { className: "mc-input-heading", children: [_jsxs("span", { className: "mc-input-name", children: [field.name, field.name.endsWith('_env') ? '(凭证引用名)' : '', field.required ? ' *' : ''] }), (field.description || field.name.endsWith('_env')) && (_jsxs("span", { className: "mc-input-description", children: [field.description, field.name.endsWith('_env') &&
|
|
62
|
+
' 填写引用名,如 MY_DB_PASSWORD;仅使用字母、数字和下划线,不能以数字开头。实际用户名或密码请在创建后的“新增凭证”中填写。'] }))] }), field.type === 'boolean' ? (_jsxs("select", { id: `mc-create-${field.name}`, "aria-label": field.name, value: values[field.name] ?? '', onChange: (event) => setValues({ ...values, [field.name]: event.target.value }), children: [_jsxs("option", { value: "", children: ["\u9ED8\u8BA4", field.default == null ? '' : ` (${field.default})`] }), _jsx("option", { value: "true", children: "true" }), _jsx("option", { value: "false", children: "false" })] })) : (_jsx("input", { id: `mc-create-${field.name}`, "aria-label": field.name, required: field.required, autoComplete: "off", type: field.type === 'number' ? 'number' : 'text', placeholder: field.default == null
|
|
63
|
+
? field.type === 'json'
|
|
64
|
+
? 'JSON'
|
|
65
|
+
: ''
|
|
66
|
+
: String(field.default), value: values[field.name] ?? '', onChange: (event) => setValues({ ...values, [field.name]: event.target.value }) }))] }, field.name)))] }))] }), _jsxs("div", { className: "mc-form-footer", children: [_jsx("button", { className: "mc-primary", type: "submit", disabled: !backend || busy, children: busy ? '正在新增…' : '确认新增数据源' }), _jsx("button", { type: "button", onClick: close, disabled: busy, children: "\u53D6\u6D88" })] })] }));
|
|
67
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
+
import { WorkspaceHeaderAction } from "../workspace-header-action.js";
|
|
4
|
+
import { CreateDatasource } from "./create-datasource.js";
|
|
5
|
+
import { CredentialClientModel, credentialMessage } from "./model.js";
|
|
6
|
+
import { credentialStyles } from "./styles.js";
|
|
7
|
+
const statusLabels = {
|
|
8
|
+
'awaiting-input': '等待填写',
|
|
9
|
+
executing: '正在保存或验证',
|
|
10
|
+
'awaiting-decision': '连接验证失败,等待处理',
|
|
11
|
+
succeeded: '验证完成,原调用继续',
|
|
12
|
+
'handed-off': '已交给助手排查',
|
|
13
|
+
'call-ended': '原调用已结束',
|
|
14
|
+
'context-changed': '上下文已变化',
|
|
15
|
+
};
|
|
16
|
+
function Icon({ name, size = 18 }) {
|
|
17
|
+
return (_jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [name === 'database' && (_jsxs(_Fragment, { children: [_jsx("ellipse", { cx: "12", cy: "5", rx: "8", ry: "3" }), _jsx("path", { d: "M4 5v14c0 4 16 4 16 0V5M4 12c0 4 16 4 16 0" })] })), name === 'lock' && (_jsxs(_Fragment, { children: [_jsx("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }), _jsx("path", { d: "M8 10V7a4 4 0 0 1 8 0v3m-4 5v2" })] })), name === 'close' && _jsx("path", { d: "m6 6 12 12M6 18 18 6" }), name === 'refresh' && (_jsx("path", { d: "M20 7v5h-5M4 17v-5h5M6.1 6a8 8 0 0 1 13.4 3M4.5 15a8 8 0 0 0 13.4 3" })), name === 'check' && _jsx("path", { d: "m5 12 4 4L19 6" }), name === 'info' && (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "12", cy: "12", r: "9" }), _jsx("path", { d: "M12 11v6m0-10v.01" })] }))] }));
|
|
18
|
+
}
|
|
19
|
+
function TestResult({ result, stale = false }) {
|
|
20
|
+
if (!result)
|
|
21
|
+
return null;
|
|
22
|
+
return (_jsxs("div", { className: "mc-result", "data-tone": stale ? 'stale' : result.ok ? 'success' : 'error', role: "status", children: [_jsxs("div", { className: "mc-result-title", children: [_jsx(Icon, { name: stale || !result.ok ? 'info' : 'check', size: 16 }), stale ? '配置已变化,请重新测试' : result.ok ? '连接测试成功' : '连接测试失败'] }), !result.ok && !stale && (_jsxs(_Fragment, { children: [_jsx("p", { children: result.failure?.message }), result.repair?.action && _jsx("p", { children: result.repair.action }), result.failure?.code && (_jsxs("details", { children: [_jsx("summary", { children: "\u67E5\u770B\u9519\u8BEF\u4EE3\u7801" }), _jsx("pre", { children: result.failure.code })] }))] }))] }));
|
|
23
|
+
}
|
|
24
|
+
function OperationOutcome({ entry }) {
|
|
25
|
+
if (!entry)
|
|
26
|
+
return null;
|
|
27
|
+
const operation = entry.operation;
|
|
28
|
+
const errors = operation?.errors ?? [];
|
|
29
|
+
const unsuccessful = entry.error ||
|
|
30
|
+
operation?.status === 'cancelled' ||
|
|
31
|
+
operation?.status === 'failed' ||
|
|
32
|
+
operation?.result?.ok === false;
|
|
33
|
+
if (!unsuccessful && operation?.action !== 'delete')
|
|
34
|
+
return null;
|
|
35
|
+
return (_jsxs("div", { className: "mc-operation-note", "data-error": Boolean(entry.error || errors.length), role: "status", children: [entry.error && _jsx("p", { children: entry.error }), operation?.status === 'cancelled' && _jsx("p", { children: "\u672C\u6B21\u64CD\u4F5C\u5DF2\u53D6\u6D88\u3002" }), operation?.status === 'succeeded' && operation.action === 'delete' && (_jsx("p", { children: "\u5DF2\u5220\u9664\u4FDD\u5B58\u503C\uFF0C\u5F53\u524D\u914D\u7F6E\u72B6\u6001\u5DF2\u66F4\u65B0\u3002" })), errors.length > 0 && _jsx("p", { children: errors.map(credentialMessage).join(' ') }), operation?.status === 'failed' && errors.length === 0 && !operation.result && (_jsx("p", { children: "\u64CD\u4F5C\u672A\u5B8C\u6210\uFF0C\u8BF7\u91CD\u8BD5\u3002" })), unsuccessful && operation?.saved.length > 0 && _jsxs("p", { children: ["\u5DF2\u4FDD\u5B58\uFF1A", operation.saved.join('、'), "\u3002"] })] }));
|
|
36
|
+
}
|
|
37
|
+
function DatasourceProperties({ context }) {
|
|
38
|
+
const properties = Object.entries(context.properties ?? {});
|
|
39
|
+
return (_jsxs("section", { className: "mc-properties", "aria-label": "\u6570\u636E\u6E90\u5C5E\u6027", children: [_jsx("h4", { className: "mc-section-heading", children: "\u6570\u636E\u6E90\u5C5E\u6027" }), _jsxs("dl", { className: "mc-property-list", children: [_jsxs("div", { className: "mc-property", children: [_jsx("dt", { children: "\u5F15\u64CE" }), _jsx("dd", { children: context.backend || '未提供' })] }), properties.map(([field, value]) => (_jsxs("div", { className: "mc-property", children: [_jsx("dt", { children: field }), _jsx("dd", { children: value !== null && typeof value === 'object' ? (_jsx("section", { className: "mc-property-json", tabIndex: 0, "aria-label": `${field} 配置值`, children: _jsx("pre", { children: JSON.stringify(value, null, 2) }) })) : (_jsx("span", { children: typeof value === 'string' && value !== '' ? value : JSON.stringify(value) })) })] }, field)))] })] }));
|
|
40
|
+
}
|
|
41
|
+
function CredentialForm({ context, request, state, model, workspaceId }) {
|
|
42
|
+
const [values, setValues] = useState({});
|
|
43
|
+
const [editing, setEditing] = useState({});
|
|
44
|
+
const [deleting, setDeleting] = useState('');
|
|
45
|
+
const busy = request?.status === 'executing' ||
|
|
46
|
+
state.operations.some((entry) => entry.handle.scope === context.token);
|
|
47
|
+
const ended = request?.endedAt !== undefined;
|
|
48
|
+
const configured = context.refs.filter((ref) => context.credentials[ref]?.configured).length;
|
|
49
|
+
const ready = configured === context.refs.length;
|
|
50
|
+
const outcome = state.outcomes[context.token];
|
|
51
|
+
const latestTest = !outcome?.overviewUpdated && outcome?.operation?.result
|
|
52
|
+
? { result: outcome.operation.result, at: outcome.operation.endedAt, stale: false }
|
|
53
|
+
: context.lastTest;
|
|
54
|
+
const latestResult = request?.failure ?? latestTest?.result;
|
|
55
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Identity or credential revision changes must discard unsubmitted secrets.
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setValues({});
|
|
58
|
+
setEditing({});
|
|
59
|
+
setDeleting('');
|
|
60
|
+
}, [context.token, context.version]);
|
|
61
|
+
const saveReference = (ref) => {
|
|
62
|
+
const changes = { [ref]: values[ref] };
|
|
63
|
+
setValues({ ...values, [ref]: '' });
|
|
64
|
+
setEditing({ ...editing, [ref]: false });
|
|
65
|
+
void model.start(context, 'save', changes);
|
|
66
|
+
};
|
|
67
|
+
const submit = () => {
|
|
68
|
+
const changes = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== ''));
|
|
69
|
+
setValues({});
|
|
70
|
+
setEditing({});
|
|
71
|
+
void model.start(context, request ? 'submit' : 'update', changes);
|
|
72
|
+
};
|
|
73
|
+
return (_jsxs("section", { className: "mc-form", "aria-label": `${context.name} 凭证配置`, children: [_jsxs("div", { className: "mc-form-body", children: [_jsxs("div", { className: "mc-detail-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "mc-eyebrow", children: "\u6570\u636E\u6E90\u914D\u7F6E" }), _jsx("h3", { children: context.name })] }), _jsxs("span", { className: "mc-badge", "data-ready": ready, children: [ready && _jsx(Icon, { name: "check", size: 13 }), context.refs.length === 0
|
|
74
|
+
? '无需凭证'
|
|
75
|
+
: ready
|
|
76
|
+
? '凭证已配齐'
|
|
77
|
+
: `${configured} / ${context.refs.length} 项已配置`] })] }), request && (_jsxs("div", { className: "mc-request-status", role: "status", children: [_jsx("strong", { children: statusLabels[request.status] }), !ended && _jsx("p", { children: "\u9A8C\u8BC1\u6210\u529F\u540E\u7EE7\u7EED\u5F53\u524D\u4EFB\u52A1\u3002" }), (request.status === 'call-ended' || request.status === 'context-changed') && (_jsx("p", { children: "\u8BF7\u91CD\u65B0\u53D1\u8D77\u4EFB\u52A1\u3002" }))] })), _jsx(DatasourceProperties, { context: context }), _jsx("h4", { className: "mc-section-heading", children: "\u51ED\u8BC1" }), context.refs.length === 0 && (_jsx("p", { className: "mc-note", children: "\u8BE5\u6570\u636E\u6E90\u6CA1\u6709\u51ED\u8BC1\u5F15\u7528\uFF0C\u53EF\u76F4\u63A5\u6D4B\u8BD5\u8FDE\u63A5\u3002" })), context.refs.length > 0 && (_jsx("div", { className: "mc-fields", children: context.refs.map((ref) => {
|
|
78
|
+
const info = context.credentials[ref];
|
|
79
|
+
const shared = state.datasources
|
|
80
|
+
.filter((item) => item.refs.includes(ref))
|
|
81
|
+
.map((item) => item.name);
|
|
82
|
+
return (_jsxs("fieldset", { className: "mc-field", disabled: busy || ended, "aria-label": ref, children: [_jsxs("div", { className: "mc-field-header", children: [_jsxs("div", { className: "mc-field-title", children: [_jsx("h4", { children: ref }), _jsxs("p", { children: ["\u5B57\u6BB5\uFF1A", Object.entries(context.fields)
|
|
83
|
+
.filter(([, value]) => value === ref)
|
|
84
|
+
.map(([field]) => field)
|
|
85
|
+
.join('、'), ' ', "\u00B7 \u6765\u6E90\uFF1A", info?.source ?? '无', !info?.writable && ' · 来源只读'] })] }), _jsxs("div", { className: "mc-field-actions", children: [_jsx("span", { className: "mc-badge", "data-ready": Boolean(info?.configured), children: info?.configured ? '已配置' : '未配置' }), info?.writable && info.configured && !editing[ref] && (_jsx("button", { type: "button", onClick: () => setEditing({ ...editing, [ref]: true }), children: "\u66F4\u6362" })), info?.writable && info.configured && editing[ref] && (_jsx("button", { type: "button", onClick: () => {
|
|
86
|
+
setValues({ ...values, [ref]: '' });
|
|
87
|
+
setEditing({ ...editing, [ref]: false });
|
|
88
|
+
}, children: "\u53D6\u6D88\u66F4\u6362" })), info?.writable && !request && info.configured && (_jsx("button", { className: "mc-quiet", type: "button", onClick: () => setDeleting(ref), children: "\u5220\u9664\u5DF2\u4FDD\u5B58\u503C" }))] })] }), info?.writable && (!info.configured || editing[ref]) && (_jsxs("label", { className: "mc-secret-input", children: ["\u65B0\u503C", _jsx("input", { type: "password", autoComplete: "new-password", placeholder: "\u8F93\u5165\u51ED\u8BC1\u503C", value: values[ref] ?? '', onChange: (event) => setValues({ ...values, [ref]: event.target.value }) }), !request && (_jsx("button", { type: "button", className: "mc-primary", disabled: !values[ref], onClick: () => saveReference(ref), children: info.configured ? '确认更换' : '新增凭证' }))] })), shared.length > 1 && (_jsxs("p", { className: "mc-field-note", children: ["\u4E0E ", shared.filter((name) => name !== context.name).join('、'), " \u5171\u4EAB\u6B64\u5F15\u7528"] })), deleting === ref && (_jsxs("div", { className: "mc-confirm", children: [_jsxs("p", { children: ["\u786E\u8BA4\u5220\u9664 ", ref, " \u7684\u5DF2\u4FDD\u5B58\u503C\uFF1F"] }), _jsx("button", { className: "mc-danger", type: "button", onClick: () => {
|
|
89
|
+
setDeleting('');
|
|
90
|
+
void model.start(context, 'delete', {}, ref);
|
|
91
|
+
}, children: "\u786E\u8BA4\u5220\u9664\u5DF2\u4FDD\u5B58\u503C" }), _jsx("button", { type: "button", onClick: () => setDeleting(''), children: "\u4FDD\u7559" })] }))] }, ref));
|
|
92
|
+
}) })), _jsxs("div", { className: "mc-test", children: [_jsxs("div", { className: "mc-test-heading", children: [_jsx("h4", { children: "\u8FDE\u63A5\u72B6\u6001" }), latestTest?.at !== undefined && (_jsxs("time", { dateTime: new Date(latestTest.at).toISOString(), children: ["\u4E0A\u6B21\u6D4B\u8BD5\uFF1A", new Date(latestTest.at).toLocaleString()] }))] }), latestResult ? (_jsx(TestResult, { result: latestResult, stale: !request?.failure && latestTest?.stale })) : (_jsx("p", { className: "mc-empty-test", children: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5\u3002" })), _jsx(OperationOutcome, { entry: outcome })] })] }), !ended && (_jsxs("div", { className: "mc-form-footer", children: [request && (_jsx("button", { className: "mc-primary", type: "button", disabled: busy, onClick: submit, children: "\u63D0\u4EA4\u51ED\u8BC1\u5E76\u7EE7\u7EED" })), _jsx("button", { type: "button", disabled: busy, onClick: () => void model.start(context, request ? 'submit' : 'test'), children: request ? '使用已有配置验证并继续' : '测试连接' }), request?.failure && (_jsx("button", { type: "button", disabled: busy, onClick: () => void model.start(context, 'diagnose'), children: "\u4EA4\u7ED9\u52A9\u624B\u6392\u67E5" })), request ? (_jsx("button", { className: "mc-quiet mc-cancel", type: "button", onClick: () => void model.cancelRequest(request.id), children: "\u53D6\u6D88\u672C\u8F6E" })) : (busy && (_jsx("button", { className: "mc-quiet mc-cancel", type: "button", onClick: () => void model.cancelOperation(context.token), children: "\u53D6\u6D88\u64CD\u4F5C" })))] })), ended && (_jsxs("div", { className: "mc-form-footer", children: [_jsx("button", { type: "button", onClick: () => model.show(workspaceId), children: "\u8FD4\u56DE\u6570\u636E\u6E90\u7BA1\u7406" }), _jsx("button", { className: "mc-quiet", type: "button", onClick: () => model.close(), children: "\u5B8C\u6210" })] }))] }));
|
|
93
|
+
}
|
|
94
|
+
function CredentialPanel({ model, workspaces }) {
|
|
95
|
+
const state = useSyncExternalStore(model.subscribe, model.getSnapshot);
|
|
96
|
+
const dialog = useRef(null);
|
|
97
|
+
const columns = useRef(null);
|
|
98
|
+
const main = useRef(null);
|
|
99
|
+
const [creating, setCreating] = useState(false);
|
|
100
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Switching context discards the creation form.
|
|
101
|
+
useEffect(() => setCreating(false), [state.open, state.workspaceId, state.requestId]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (state.open)
|
|
104
|
+
dialog.current?.showModal();
|
|
105
|
+
else
|
|
106
|
+
dialog.current?.close();
|
|
107
|
+
}, [state.open]);
|
|
108
|
+
const request = state.requests.find((item) => item.id === state.requestId);
|
|
109
|
+
const pending = state.requests.filter((item) => item.endedAt === undefined && item.sessionId === state.sessionId);
|
|
110
|
+
const context = request?.context ?? state.datasources.find((item) => item.token === state.selected);
|
|
111
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: A different detail or creation page starts at the top of its content.
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (main.current)
|
|
114
|
+
main.current.scrollTop = 0;
|
|
115
|
+
if (columns.current)
|
|
116
|
+
columns.current.scrollTop = 0;
|
|
117
|
+
}, [creating, context?.token]);
|
|
118
|
+
const workspaceId = request
|
|
119
|
+
? (workspaces.find((item) => item.sessionIds.includes(request.sessionId))?.workspaceId ?? '')
|
|
120
|
+
: state.workspaceId;
|
|
121
|
+
const datasources = request ? [request.context] : state.datasources;
|
|
122
|
+
const operations = state.operations.filter((entry) => entry.workspaceId === workspaceId);
|
|
123
|
+
const activeOperation = operations.find((entry) => entry.handle.id === state.handle?.id)?.operation;
|
|
124
|
+
if (!state.open)
|
|
125
|
+
return null;
|
|
126
|
+
return (_jsxs("dialog", { ref: dialog, className: "mc-dialog", onCancel: (event) => {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
model.close();
|
|
129
|
+
}, "aria-labelledby": "marivo-credentials-title", children: [_jsx("style", { children: credentialStyles }), _jsxs("div", { className: "mc-shell", children: [_jsxs("header", { className: "mc-header", children: [_jsx("div", { className: "mc-header-mark", children: _jsx(Icon, { name: "lock", size: 22 }) }), _jsxs("div", { className: "mc-header-copy", children: [_jsx("h2", { id: "marivo-credentials-title", children: "\u6570\u636E\u6E90\u4E0E\u51ED\u8BC1" }), _jsx("p", { children: "\u914D\u7F6E\u8BBF\u95EE\u51ED\u8BC1\uFF0C\u6D4B\u8BD5\u6570\u636E\u6E90\u8FDE\u63A5\u3002" })] }), _jsx("button", { className: "mc-close", type: "button", "aria-label": "\u6536\u8D77", title: "\u6536\u8D77", onClick: () => model.close(), children: _jsx(Icon, { name: "close", size: 20 }) })] }), pending.length > 0 && (_jsxs("div", { className: "mc-requests", children: [_jsx("span", { children: "\u672C\u4F1A\u8BDD\u5F85\u529E" }), pending.map((item) => (_jsxs("button", { type: "button", onClick: () => model.openRequest(item.id), children: [item.context.name, " \u00B7 ", statusLabels[item.status]] }, item.id))), request && (_jsx("button", { className: "mc-quiet", type: "button", onClick: () => model.show(workspaceId), children: "\u6570\u636E\u6E90\u7BA1\u7406" }))] })), _jsxs("div", { className: "mc-columns", ref: columns, children: [_jsxs("aside", { className: "mc-nav", "aria-label": "\u6570\u636E\u6E90\u5BFC\u822A", children: [!request && (_jsx("button", { type: "button", disabled: !workspaceId || state.loading, onClick: () => setCreating(true), children: "\u65B0\u589E\u6570\u636E\u6E90" })), _jsxs("div", { children: [_jsxs("div", { className: "mc-nav-heading", children: [_jsx("h3", { children: request ? '请求的数据源' : `数据源 · ${datasources.length}` }), _jsx("button", { className: "mc-quiet", type: "button", "aria-label": "\u5237\u65B0\u72B6\u6001", title: "\u5237\u65B0\u72B6\u6001", disabled: state.loading || !workspaceId, onClick: () => void model.selectWorkspace(workspaceId), children: _jsx(Icon, { name: "refresh", size: 16 }) })] }), _jsx("ul", { className: "mc-datasources", children: datasources.map((item) => {
|
|
130
|
+
const count = item.refs.filter((ref) => item.credentials[ref]?.configured).length;
|
|
131
|
+
const ready = count === item.refs.length;
|
|
132
|
+
const running = state.operations.some((entry) => entry.handle.scope === item.token);
|
|
133
|
+
return (_jsx("li", { children: _jsxs("button", { className: "mc-datasource", type: "button", "aria-label": `选择数据源 ${item.name}`, "aria-pressed": context?.token === item.token, onClick: () => {
|
|
134
|
+
if (request)
|
|
135
|
+
model.openRequest(request.id);
|
|
136
|
+
else {
|
|
137
|
+
setCreating(false);
|
|
138
|
+
model.select(item.token);
|
|
139
|
+
}
|
|
140
|
+
}, children: [_jsx(Icon, { name: "database" }), _jsxs("span", { className: "mc-datasource-copy", children: [_jsx("span", { className: "mc-datasource-name", children: item.name }), _jsxs("span", { className: "mc-datasource-status", children: [_jsx("span", { className: "mc-dot", "data-ready": ready }), running
|
|
141
|
+
? '正在处理…'
|
|
142
|
+
: item.refs.length === 0
|
|
143
|
+
? '无需凭证'
|
|
144
|
+
: ready
|
|
145
|
+
? '凭证已配齐'
|
|
146
|
+
: `${count} / ${item.refs.length} 项已配置`] })] })] }) }, item.token));
|
|
147
|
+
}) })] })] }), _jsxs("main", { className: "mc-main", ref: main, children: [state.error && (_jsx("p", { role: "alert", className: "mc-alert", children: state.error })), state.loading && (_jsx("p", { className: "mc-loading", role: "status", children: "\u6B63\u5728\u8BFB\u53D6\u6570\u636E\u6E90\u4E0E\u51ED\u8BC1\u72B6\u6001\u2026" })), !creating && !state.loading && !context && !state.error && (_jsxs("div", { className: "mc-empty", children: [_jsx(Icon, { name: "database", size: 32 }), _jsx("h3", { children: !state.workspaceId
|
|
148
|
+
? '当前会话未绑定 Workspace'
|
|
149
|
+
: state.datasources.length === 0
|
|
150
|
+
? '暂无数据源'
|
|
151
|
+
: '选择一个数据源' }), _jsx("p", { children: !state.workspaceId
|
|
152
|
+
? '请返回会话后重试。'
|
|
153
|
+
: state.datasources.length === 0
|
|
154
|
+
? '该 Workspace 没有已定义的数据源。'
|
|
155
|
+
: '查看凭证配置与最近一次连接测试。' })] })), creating && (_jsx(CreateDatasource, { model: model, workspaceId: workspaceId, close: () => setCreating(false) }, workspaceId)), !creating && context && (_jsx(CredentialForm, { context: context, request: request, state: state, model: model, workspaceId: workspaceId }, context.token)), operations.length > 0 && (_jsxs("section", { className: "mc-activity", "aria-label": "\u8FDB\u884C\u4E2D\u7684\u51ED\u8BC1\u64CD\u4F5C", children: [_jsxs("p", { className: "mc-activity-heading", children: ["\u8FDB\u884C\u4E2D \u00B7 ", operations.length] }), _jsx("div", { className: "mc-activity-list", children: operations.map((entry) => (_jsxs("button", { type: "button", "aria-pressed": state.handle?.id === entry.handle.id, onClick: () => model.selectOperation(entry.handle.id), children: [entry.name, " \u00B7 \u5904\u7406\u4E2D"] }, entry.handle.id))) }), activeOperation?.status === 'running' && (_jsxs("div", { className: "mc-progress", children: [_jsxs("p", { children: [state.operations.find((entry) => entry.handle.id === state.handle?.id)?.name, ' ', "\u00B7 ", activeOperation.phase === 'saving' ? '正在保存' : '正在验证连接'] }), _jsx("button", { type: "button", onClick: () => void model.cancelOperation(), children: "\u53D6\u6D88\u6B64\u64CD\u4F5C" }), activeOperation.saved.length > 0 && (_jsxs("p", { children: ["\u5DF2\u4FDD\u5B58\uFF1A", activeOperation.saved.join('、'), "\u3002"] }))] }))] }))] })] })] })] }));
|
|
156
|
+
}
|
|
157
|
+
export function installCredentials(ctx, rpc) {
|
|
158
|
+
let storage;
|
|
159
|
+
try {
|
|
160
|
+
storage = window.sessionStorage;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
/* optional query-handle persistence */
|
|
164
|
+
}
|
|
165
|
+
const model = new CredentialClientModel(rpc, storage);
|
|
166
|
+
model.recover();
|
|
167
|
+
ctx.effect(() => () => model.dispose(), 'dsh-data-analysis: credential client lifecycle');
|
|
168
|
+
ctx.on('connection/reset', () => model.reset());
|
|
169
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({ name: 'conversation.session.header.actions', id: 'marivo-credentials', order: 100 }, function Entry({ sessionId, useWorkspaces }) {
|
|
170
|
+
const workspaces = useWorkspaces((state) => state.items);
|
|
171
|
+
const selected = workspaces.find((item) => item.sessionIds.includes(sessionId))?.workspaceId ?? '';
|
|
172
|
+
return (_jsx(WorkspaceHeaderAction, { label: "\u6570\u636E\u6E90\u4E0E\u51ED\u8BC1", icon: "credentials", disabled: !selected, title: selected ? '数据源与凭证' : '当前会话未绑定工作区,无法打开数据源与凭证', onClick: () => {
|
|
173
|
+
if (selected)
|
|
174
|
+
model.show(selected);
|
|
175
|
+
} }));
|
|
176
|
+
}));
|
|
177
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({ name: 'conversation.session.header.actions', id: 'marivo-credential-requests', order: 120 }, function Pending({ sessionId }) {
|
|
178
|
+
const state = useSyncExternalStore(model.subscribe, model.getSnapshot);
|
|
179
|
+
const request = state.requests.find((item) => item.sessionId === sessionId && item.endedAt === undefined);
|
|
180
|
+
return request ? (_jsx("button", { className: "mc-pending-entry", type: "button", onClick: () => model.openRequest(request.id), children: "\u7B49\u5F85\u914D\u7F6E\u51ED\u8BC1" })) : null;
|
|
181
|
+
}));
|
|
182
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'marivo-credentials' }, function Overlay({ useSessions, useWorkspaces }) {
|
|
183
|
+
const sessionId = useSessions((state) => state.current) ?? '';
|
|
184
|
+
const workspaces = useWorkspaces((state) => state.items);
|
|
185
|
+
const currentWorkspace = workspaces.find((item) => item.sessionIds.includes(sessionId))?.workspaceId ?? '';
|
|
186
|
+
useEffect(() => model.session(sessionId), [sessionId]);
|
|
187
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Workspace reassignment must dismiss old content.
|
|
188
|
+
useEffect(() => model.close(), [currentWorkspace]);
|
|
189
|
+
return _jsx(CredentialPanel, { model: model, workspaces: workspaces });
|
|
190
|
+
}));
|
|
191
|
+
}
|