@chengxianglibra/dsh-data-analysis 0.2.0 → 0.2.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.en.md +138 -0
- package/README.md +55 -82
- package/lib/client/credentials/install.js +3 -3
- package/lib/client/i18n/copy.js +4 -1
- package/lib/client/i18n/host.js +1 -0
- package/lib/client/i18n/settings.js +30 -0
- package/lib/client/input-context.js +1 -1
- package/lib/client/python-tool/card.js +2 -2
- package/lib/client/python-tool/model.js +1 -1
- package/lib/client/right-tabs/install.js +10 -7
- package/lib/client/settings/card.js +90 -0
- package/lib/client/settings/install.js +14 -0
- package/lib/client.js +104 -70
- package/lib/datasource/changes-contract.js +6 -2
- package/lib/datasource/configure.js +1 -1
- package/lib/datasource/python-options.js +1 -7
- package/lib/datasource/python.js +12 -7
- package/lib/datasource/test.js +1 -1
- package/lib/disclosure/execution-guidance.js +5 -8
- package/lib/plugin-agents.js +2 -2
- package/lib/plugin.js +14 -4
- package/lib/presentation/assets/portable.js +1 -1
- package/lib/presentation/assets/static.js +47 -47
- package/lib/presentation/execution-policy.js +5 -0
- package/lib/presentation/projection/index.js +27 -10
- package/lib/presentation/tool.js +2 -1
- package/lib/settings.js +17 -0
- package/lib/types/client/i18n/copy.d.ts +15 -0
- package/lib/types/client/i18n/host.d.ts +1 -0
- package/lib/types/client/i18n/settings.d.ts +16 -0
- package/lib/types/client/settings/card.d.ts +9 -0
- package/lib/types/client/settings/install.d.ts +2 -0
- package/lib/types/client.d.ts +2 -2
- package/lib/types/datasource/python-options.d.ts +1 -2
- package/lib/types/datasource/python.d.ts +2 -2
- package/lib/types/disclosure/execution-guidance.d.ts +8 -0
- package/lib/types/plugin-agents.d.ts +2 -0
- package/lib/types/plugin.d.ts +23 -3
- package/lib/types/presentation/execution-policy.d.ts +5 -0
- package/lib/types/rpc.d.ts +2 -2
- package/lib/types/semantic-reference/rpc.d.ts +2 -2
- package/lib/types/settings.d.ts +14 -0
- package/package.json +85 -65
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-files/SKILL.md +22 -10
- package/skills/dsh-data-analysis-files/references/delivery.md +15 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +15 -19
- package/skills/dsh-data-analysis-presentation/references/schema.md +4 -1
package/README.en.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @chengxianglibra/dsh-data-analysis
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
Analyze data with natural language in [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
6
|
+
Explore and reuse business metric definitions, then turn your findings into interactive charts, reports,
|
|
7
|
+
and dashboards that you can also read offline. Semantic analysis is powered by [Marivo](https://github.com/chengxianglibra/marivo).
|
|
8
|
+
|
|
9
|
+
This is an independently maintained community plugin. It is not an official DeepSeek product.
|
|
10
|
+
|
|
11
|
+
## Capabilities and features
|
|
12
|
+
|
|
13
|
+
| What you want to do | What the plugin offers |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Answer business questions | Analyze metric trends, compare periods or business segments, and explore reasons for changes using natural language |
|
|
16
|
+
| Analyze local files | Upload files or use files in your workspace to analyze data and create charts and reports |
|
|
17
|
+
| Connect to data | Configure data sources, manage credentials, and test connections; DuckDB, Trino, and ClickHouse are supported by default |
|
|
18
|
+
| Keep definitions consistent | Work with the assistant to define and reuse metrics and dimensions, and explore their relationships |
|
|
19
|
+
| Specify what to analyze | Search for and reference existing semantic objects with `@` in the chat input |
|
|
20
|
+
| Present findings | Create reports and dashboards with conclusions, metric cards, charts, tables, and source information |
|
|
21
|
+
| Explore further | Filter interactively, adjust the presentation, and reference report content in follow-up questions |
|
|
22
|
+
| Read and share | Save and browse previous versions, and download HTML for offline reading and sharing |
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
|
|
28
|
+
- Node.js `^22.19.0 || >=24.0.0`: version 22.19.0 or later in the 22.x series, or version 24.0.0 and above. Version 23.x is not supported.
|
|
29
|
+
- DeepSeek Harness `>=0.1.7-rc.1`, launched with `npx @deepseek-ai/dsh web` and configured.
|
|
30
|
+
- `pnpm` available on the command line for Harness to install the plugin.
|
|
31
|
+
- Python 3.10+, available as `python3` (`python` on Windows), with `venv`/`ensurepip` support. Some Linux distributions require the additional `python3-venv` package.
|
|
32
|
+
|
|
33
|
+
You do not need to install Marivo beforehand. The analysis environment is prepared automatically on first launch.
|
|
34
|
+
An internet connection is required to download dependencies; wait for setup to finish.
|
|
35
|
+
|
|
36
|
+
### Install and launch
|
|
37
|
+
|
|
38
|
+
The commands below run Harness through `npx`; no global installation or `dsh` command on PATH is required.
|
|
39
|
+
|
|
40
|
+
Install the plugin into the Web Profile:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx @deepseek-ai/dsh plugin --profile web add @chengxianglibra/dsh-data-analysis
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Start the Web Profile after installation. If it is already running, exit and restart it:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @deepseek-ai/dsh web
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
In the Web interface, select a Workspace, create a session, and send a message.
|
|
53
|
+
Data Sources (数据源), Semantic Layer (语义层), and Reports (报告) appear beside the session title and open in tabs on the right.
|
|
54
|
+
The examples below use the Web interface; Chinese labels are included to help you locate the controls.
|
|
55
|
+
|
|
56
|
+
## How to use
|
|
57
|
+
|
|
58
|
+
### 1. Prepare your data
|
|
59
|
+
|
|
60
|
+
**Local files**: Upload a file and ask a question, or specify a file already in your workspace.
|
|
61
|
+
No data source configuration or business definitions are required first.
|
|
62
|
+
CSV, JSON, Parquet, and Excel `.xlsx` files are supported. Reading `.xlsx` for the first time requires an internet connection to download an extension.
|
|
63
|
+
Convert older `.xls` files to `.xlsx`, CSV, or Parquet first.
|
|
64
|
+
To view or deliver a CSV, TSV, or XLSX file, ask the assistant to use native `present`, then open the read-only DSH spreadsheet preview on the right. Tables with report filtering and linked interactions remain in the report reader. The plugin does not write XLSX or analyze older `.xls` files.
|
|
65
|
+
|
|
66
|
+
**Databases**: Open Data Sources (数据源) beside the session title to view connections and their configuration status in the current workspace:
|
|
67
|
+
|
|
68
|
+
1. Select Add Data Source (新增数据源), choose an engine, and enter a name and connection details.
|
|
69
|
+
2. If authentication is required, enter the username, password, or token under Connection Credentials (连接凭证).
|
|
70
|
+
3. Save and test the connection. Use the result to check the address, credentials, or access permissions.
|
|
71
|
+
|
|
72
|
+
Use Edit Configuration (编辑配置) to update an existing connection. Its name and engine stay fixed; test the connection again after saving.
|
|
73
|
+
Saved credential values are not displayed; leave them blank to keep using them. Submit passwords and tokens through the credential form, not in chat messages.
|
|
74
|
+
|
|
75
|
+
If a data source needs to be added or repaired during analysis, the assistant can open the configuration page.
|
|
76
|
+
Enter a new connection or choose Use Existing Data Source (使用已有数据源), then select Save and Test, Continue on Success (保存并测试,成功后继续).
|
|
77
|
+
The assistant resumes analysis after verifying the connection and access to the target data.
|
|
78
|
+
|
|
79
|
+
### 2. Ask questions in natural language
|
|
80
|
+
|
|
81
|
+
Describe the data, time range, metric definitions, and results you want. For example:
|
|
82
|
+
|
|
83
|
+
> Using the order data, analyze revenue and order count for August 2026, compare them with July, break them down by channel, and explain the sources and metric definitions.
|
|
84
|
+
|
|
85
|
+
> Check query duration over the last seven days. Identify the slowest query types and provide a trend chart and a detailed table.
|
|
86
|
+
|
|
87
|
+
If you have not yet defined your business metrics, start by asking the assistant to help:
|
|
88
|
+
|
|
89
|
+
> First, find order-related tables in the current data source. Confirm the definitions of revenue and paid order count with me, then save them as reusable business definitions.
|
|
90
|
+
|
|
91
|
+
When you need a report, describe its contents:
|
|
92
|
+
|
|
93
|
+
> Turn this analysis into a report with conclusions, key metrics, trend charts, a channel comparison table, and source information. Provide a downloadable HTML file.
|
|
94
|
+
|
|
95
|
+
### 3. Browse and reference business definitions
|
|
96
|
+
|
|
97
|
+
Open Semantic Layer (语义层) to find metrics, dimensions, and other objects by category or keyword,
|
|
98
|
+
and inspect their definitions and relationships. Open objects separately to compare them side by side.
|
|
99
|
+
|
|
100
|
+
Type `@` in the chat input to search for and select objects in the current workspace.
|
|
101
|
+
For a multiword search, use `@"monthly revenue"`. Ask questions about the selected objects to keep the analysis aligned with your business definitions.
|
|
102
|
+
|
|
103
|
+
### 4. Read, edit, and ask follow-up questions about reports
|
|
104
|
+
|
|
105
|
+
When the current session delivers a new report, it opens automatically on the right.
|
|
106
|
+
Use the Reports (报告) catalog to find reports by title and read different reports or versions side by side.
|
|
107
|
+
The page notifies you when a new version is available, so you can choose when to switch.
|
|
108
|
+
You can also read workspace reports from the Reports sidebar entry without an active session.
|
|
109
|
+
|
|
110
|
+
- **Explore data**: Filter charts and tables, and adjust supported chart types and displayed fields. Interactions use data already saved in the report; ask in chat to fetch fresh data or recalculate results.
|
|
111
|
+
- **Check sources**: Inspect the source information attached to report content to review the basis of the analysis.
|
|
112
|
+
- **Edit the presentation**: On the latest version page, select Edit Report (编辑报告) to change titles, text, metric labels, and chart or table settings. Move or remove content, undo or redo changes, and save your edits.
|
|
113
|
+
- **Ask follow-up questions**: Choose Add to Question (加入提问) from a supported report content menu to insert a reference into the chat input, then add your question and send it.
|
|
114
|
+
- **Browse history**: After saving edits, use the history sidebar to view and download saved versions. Historical versions are read-only.
|
|
115
|
+
|
|
116
|
+
Page filters do not recalculate metric cards or change the saved report. To preserve the current filtered view, use Export Current View (导出当前视图).
|
|
117
|
+
|
|
118
|
+
### 5. Download and share
|
|
119
|
+
|
|
120
|
+
The export menu at the top right of a report offers two options:
|
|
121
|
+
|
|
122
|
+
| Option | Use it to |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| Download Full Report (下载完整报告) | Download the complete HTML report for the version you are viewing and read it offline |
|
|
125
|
+
| Export Current View (导出当前视图) | Capture the current filters, charts, and table sorting in HTML, with the source version and export time, to share what you see |
|
|
126
|
+
|
|
127
|
+
You can also ask the assistant to export a report as an HTML file. Open downloaded files in a browser to read and interact with them offline, without connecting to the original data source.
|
|
128
|
+
If you are editing a report, save or cancel your edits before exporting.
|
|
129
|
+
|
|
130
|
+
## Analysis timeout
|
|
131
|
+
|
|
132
|
+
Go to Settings → Plugins → Plugin Configuration → Data Analysis (设置 → 插件 → 插件配置 → 数据分析)
|
|
133
|
+
to adjust the default timeout for Python analysis. The default is 120 seconds, and changes apply to the next analysis call after saving.
|
|
134
|
+
The actual execution limit is still subject to Harness execution policies.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
This project is licensed under the [MIT License](LICENSE).
|
package/README.md
CHANGED
|
@@ -1,100 +1,76 @@
|
|
|
1
1
|
# @chengxianglibra/dsh-data-analysis
|
|
2
2
|
|
|
3
|
+
简体中文 | [English](README.en.md)
|
|
4
|
+
|
|
3
5
|
在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 中,用自然语言完成数据分析,
|
|
4
6
|
浏览和复用业务指标定义,并将结果整理为可交互、可离线阅读的图表、报告和看板。
|
|
5
|
-
语义分析能力由 [Marivo](https://github.com/chengxianglibra/marivo)
|
|
7
|
+
语义分析能力由 [Marivo](https://github.com/chengxianglibra/marivo) 提供。
|
|
6
8
|
|
|
7
9
|
本项目是个人维护的社区插件,并非 DeepSeek 官方发行或维护的软件。
|
|
8
10
|
|
|
11
|
+
## 能力与特性
|
|
12
|
+
|
|
13
|
+
| 你想做什么 | 插件提供的能力 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 回答业务问题 | 用自然语言分析指标趋势、比较不同时间或业务分组、探索变化原因 |
|
|
16
|
+
| 分析本地文件 | 上传文件或指定工作区中的文件,直接分析并生成图表、报告 |
|
|
17
|
+
| 连接数据 | 配置数据源、管理连接凭证并测试连接;默认支持 DuckDB、Trino、ClickHouse |
|
|
18
|
+
| 统一分析口径 | 与助手一起整理和复用指标、维度等业务定义,浏览对象及其关系 |
|
|
19
|
+
| 明确分析对象 | 在输入框通过 `@` 搜索并引用已有语义对象 |
|
|
20
|
+
| 展示分析结果 | 生成包含结论、指标卡、图表、表格和来源说明的报告或看板 |
|
|
21
|
+
| 继续探索 | 交互筛选、调整呈现,并引用报告内容继续提问 |
|
|
22
|
+
| 阅读与分享 | 保存和查看历史版本,下载 HTML 供离线阅读与分享 |
|
|
23
|
+
|
|
9
24
|
## 安装
|
|
10
25
|
|
|
11
26
|
### 环境准备
|
|
12
27
|
|
|
13
|
-
- Node.js `^22.19.0 || >=24.0.0
|
|
14
|
-
-
|
|
15
|
-
- `pnpm
|
|
16
|
-
-
|
|
28
|
+
- Node.js `^22.19.0 || >=24.0.0`:22.x 需至少 22.19.0,或使用 24.0.0 及以上版本;不支持 23.x。
|
|
29
|
+
- 已通过 `npx @deepseek-ai/dsh web` 启动并配置好 DeepSeek Harness `>=0.1.7-rc.1`。
|
|
30
|
+
- 命令行中可使用 `pnpm`,供 Harness 安装插件。
|
|
31
|
+
- Python 3.10+,命令行可运行 `python3`(Windows 为 `python`),且支持 `venv`/`ensurepip`;部分 Linux 发行版需额外安装 `python3-venv`。
|
|
17
32
|
|
|
18
|
-
|
|
19
|
-
首次启动需要联网下载依赖,请等待环境准备完成。
|
|
33
|
+
无需预装 Marivo。首次启动会自动准备分析环境,需要联网下载依赖,请等待准备完成。
|
|
20
34
|
|
|
21
35
|
### 安装并启动
|
|
22
36
|
|
|
37
|
+
以下命令均通过 `npx` 运行 Harness,无需全局安装或将 `dsh` 添加到 PATH。
|
|
38
|
+
|
|
23
39
|
将插件安装到 Web Profile:
|
|
24
40
|
|
|
25
41
|
```bash
|
|
26
|
-
dsh plugin --profile web add @chengxianglibra/dsh-data-analysis
|
|
42
|
+
npx @deepseek-ai/dsh plugin --profile web add @chengxianglibra/dsh-data-analysis
|
|
27
43
|
```
|
|
28
44
|
|
|
29
45
|
安装后启动 Web Profile;如果已在运行,请先退出再重新启动:
|
|
30
46
|
|
|
31
47
|
```bash
|
|
32
|
-
dsh
|
|
48
|
+
npx @deepseek-ai/dsh web
|
|
33
49
|
```
|
|
34
50
|
|
|
35
51
|
在 Web 界面选择分析使用的工作区(Workspace),新建会话并发送一条消息。
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### 本地文件分析
|
|
39
|
-
|
|
40
|
-
上传文件后直接提问,或指定 Workspace 中已有文件。助手可在插件绑定的 Python 环境中使用 pandas
|
|
41
|
-
或原生 DuckDB 分析 CSV、JSON、Parquet,无需先配置数据源或建立语义模型;需要时可继续生成报告。
|
|
42
|
-
默认安装包含 Marivo 的 `duckdb` extra(同时保留 `trino`、`clickhouse`)。Excel `.xlsx` 通过 DuckDB 的
|
|
43
|
-
[excel 扩展](https://duckdb.org/docs/stable/core_extensions/excel.html)读取;首次使用需要联网下载扩展,
|
|
44
|
-
并允许写入 DuckDB 扩展缓存。它不是额外的 Python 包,无需为此安装 `openpyxl`。
|
|
45
|
-
旧版 `.xls` 不在该扩展支持范围内,需要先转换为 `.xlsx`、CSV 或 Parquet。
|
|
46
|
-
|
|
47
|
-
纯文件分析无需经过 Marivo `md.raw_sql`。下面的数据源配置流程适用于数据库连接和需要复用的 Marivo 数据源。
|
|
48
|
-
|
|
49
|
-
## 能力
|
|
50
|
-
|
|
51
|
-
| 你想做什么 | 插件提供的能力 |
|
|
52
|
-
| --- | --- |
|
|
53
|
-
| 回答业务问题 | 用自然语言提出问题,分析指标趋势、比较不同时间或业务分组、探索变化原因 |
|
|
54
|
-
| 分析本地文件 | 复用原生附件路径,以 pandas 或 DuckDB 直接计算,并按需交付图表或报告 |
|
|
55
|
-
| 连接数据 | 配置数据源、管理连接凭证并测试连接;默认包含 DuckDB、Trino、ClickHouse 支持 |
|
|
56
|
-
| 统一分析口径 | 与 Agent 一起整理和复用指标、维度等业务定义,浏览语义对象及其关系 |
|
|
57
|
-
| 明确分析对象 | 在输入框通过 `@` 搜索并引用已有语义对象 |
|
|
58
|
-
| 展示分析结果 | 生成包含文字、指标卡、图表、表格和来源说明的报告或看板 |
|
|
59
|
-
| 阅读与分享 | 交互筛选、调整呈现、保存版本,并下载 HTML 离线阅读 |
|
|
52
|
+
会话标题旁会显示“数据源”“语义层”“报告”三个入口,相关页面在右侧标签页打开。以下以 Web 界面为例。
|
|
60
53
|
|
|
61
54
|
## 如何使用
|
|
62
55
|
|
|
63
|
-
### 1.
|
|
64
|
-
|
|
65
|
-
在会话标题旁打开“数据源”,查看所属工作区的连接属性与配置状态:
|
|
66
|
-
|
|
67
|
-
1. 点击“新增数据源”,选择引擎,按表单填写名称及连接信息。
|
|
68
|
-
2. 如果需要身份认证,在同一表单的“连接凭证”中直接填写用户名、密码或令牌。
|
|
69
|
-
3. 测试连接,根据结果检查地址、凭证或访问权限。
|
|
70
|
-
|
|
71
|
-
凭证引用名自动生成,可展开“凭证引用名 · 确认或修改”查看和调整;也可填写已有引用,并将值留空以复用凭证。
|
|
72
|
-
编辑时已保存的凭证值不回显,留空沿用;填写新值默认使用新的引用,不删除旧的共享凭证。
|
|
73
|
-
手动指定已存在的引用并填写新值时,不会自动覆盖,需到凭证页确认更新。
|
|
74
|
-
已有数据源可点击“编辑配置”修改连接信息,名称和引擎保持不变;保存后重新测试。
|
|
56
|
+
### 1. 准备数据
|
|
75
57
|
|
|
76
|
-
|
|
58
|
+
**本地文件**:上传文件后直接提问,或指定工作区中已有文件,无需先配置数据源或建立业务定义。
|
|
59
|
+
支持 CSV、JSON、Parquet 和 Excel `.xlsx`;首次读取 `.xlsx` 需要联网下载扩展。
|
|
60
|
+
旧版 `.xls` 请先转换为 `.xlsx`、CSV 或 Parquet。
|
|
61
|
+
需要查看或交付 CSV、TSV、XLSX 文件时,可让助手通过原生 `present` 交付,随后在右侧打开 DSH 的只读表格预览;报告中可筛选联动的表格仍使用报告阅读器。插件不提供 XLSX 写入或旧 `.xls` 分析。
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
datasourceDefaults:
|
|
80
|
-
trino:
|
|
81
|
-
host: trino.internal.example
|
|
82
|
-
port: 443
|
|
83
|
-
```
|
|
63
|
+
**数据库**:在会话标题旁打开“数据源”,查看当前工作区的连接与配置状态:
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
打开新建表单时按当前 Runtime 校验引擎、字段和类型,错误会明确显示且不回显配置值;配置错误不阻止已有数据源编辑。
|
|
65
|
+
1. 点击“新增数据源”,选择引擎,填写名称及连接信息。
|
|
66
|
+
2. 如果需要身份认证,在“连接凭证”中填写用户名、密码或令牌。
|
|
67
|
+
3. 保存并测试连接,根据结果检查地址、凭证或访问权限。
|
|
89
68
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
请求页默认显示新增表单;切换“使用已有数据源”后,可选择已有连接并验证,无需重复创建。测试失败保留已保存的配置,允许修正后重试;取消请求不会作为成功继续。
|
|
69
|
+
已有数据源可通过“编辑配置”修改连接信息,名称和引擎保持不变;保存后重新测试。
|
|
70
|
+
已保存的凭证值不会回显,留空可沿用。请通过凭证表单提交密码或令牌,不要粘贴到聊天消息中。
|
|
93
71
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
若原调用已取消或 Host 已重启,需要重新发起配置请求。
|
|
97
|
-
请通过凭证表单提交密码或令牌,不要粘贴到聊天消息中。
|
|
72
|
+
分析过程中需要新增或修复数据源时,助手可打开配置页。你可以填写新连接,或选择“使用已有数据源”,
|
|
73
|
+
点击“保存并测试,成功后继续”,连接与目标数据访问验证通过后,助手会继续分析。
|
|
98
74
|
|
|
99
75
|
### 2. 用自然语言提出分析问题
|
|
100
76
|
|
|
@@ -104,7 +80,7 @@ datasourceDefaults:
|
|
|
104
80
|
|
|
105
81
|
> 检查最近七天的查询耗时,找出耗时最高的查询类型,给出趋势图和明细表。
|
|
106
82
|
|
|
107
|
-
|
|
83
|
+
如果还没有整理业务定义,可以先让助手帮助梳理:
|
|
108
84
|
|
|
109
85
|
> 先查看当前数据源中有哪些订单相关表,和我确认销售额、支付订单数的统计口径,再保存为可复用的业务定义。
|
|
110
86
|
|
|
@@ -114,26 +90,21 @@ datasourceDefaults:
|
|
|
114
90
|
|
|
115
91
|
### 3. 浏览和引用业务定义
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
|
|
93
|
+
点击“语义层”,按分类或关键词查找指标、维度等对象,查看业务定义和对象关系。
|
|
94
|
+
支持独立打开对象,方便对照阅读。
|
|
119
95
|
|
|
120
|
-
在聊天输入框输入
|
|
121
|
-
|
|
96
|
+
在聊天输入框输入 `@`,搜索并选择当前工作区的对象;多词搜索可输入 `@"monthly revenue"`。
|
|
97
|
+
选中后即可围绕指定对象提问,让助手使用一致的业务口径开展分析。
|
|
122
98
|
|
|
123
99
|
### 4. 阅读、调整和追问报告
|
|
124
100
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
报告生成后自动在右侧 tab 打开固定版本,主对话不附加报告 block。默认只保存 JSON,点击下载或显式调用导出工具时才生成 HTML。“报告”目录按标题查找并打开 current;可以并排阅读不同报告和版本。
|
|
128
|
-
|
|
129
|
-
用户要求离线 HTML 文件时,DSH 可直接调用 `marivo_export_html({ report_id, build_id?, output_path })`,例如输出到 `reports/analysis.html`。省略 `build_id` 时固定调用开始时的 current;指定时导出该历史版本。输出是完整、自包含且可交互的报告,不含未保存编辑或临时筛选。路径必须位于当前 Workspace,已有文件不会覆盖;导出不需要 Python、数据源或发布配置。成功返回实际路径、Build ID、字节数和 SHA-256;DSH 随后通过原生 `present({ files: [{ path, description }] })` 交付该文件。
|
|
130
|
-
发现新版本时 current 先提示,点击刷新或重新打开才切换,旧版本筛选随切换重置。
|
|
131
|
-
没有活动会话时,侧栏的“报告”入口仍可阅读 Workspace 中的报告。
|
|
101
|
+
当前会话交付新报告后,会自动在右侧打开。通过“报告”目录可按标题查找报告,并排阅读不同报告和版本。
|
|
102
|
+
发现新版本时,页面会提示更新,你可以选择何时切换。没有活动会话时,也可通过侧栏的“报告”入口阅读工作区中的报告。
|
|
132
103
|
|
|
133
104
|
- **探索数据**:筛选图表和表格,调整支持的图形及展示字段。交互使用报告中已保存的数据;获取新数据或重新计算时,在会话中提出要求。
|
|
134
105
|
- **查看来源**:查看报告内容附带的来源信息,核对分析依据。
|
|
135
|
-
-
|
|
136
|
-
-
|
|
106
|
+
- **调整呈现**:在最新版本页面点击“编辑报告”,编辑标题、正文、指标标签和图表/表格配置,移动或删除内容,支持撤销、重做和保存。
|
|
107
|
+
- **继续追问**:在支持的报告内容菜单中选择“加入提问”,将相关内容引用到输入框,补充问题后发送。
|
|
137
108
|
- **查看历史**:保存编辑后可在历史侧栏查看和下载已保存的版本,历史版本只读。
|
|
138
109
|
|
|
139
110
|
页面筛选不会重新计算指标卡,也不会写入保存的报告。希望保留当前筛选结果时,使用“导出当前视图”。
|
|
@@ -147,12 +118,14 @@ datasourceDefaults:
|
|
|
147
118
|
| 下载完整报告 | 下载所查看版本的完整 HTML 报告,供离线阅读 |
|
|
148
119
|
| 导出当前视图 | 将当前筛选、图形和表格排序固定为 HTML,附带来源版本及导出时间,分享当前看到的内容 |
|
|
149
120
|
|
|
150
|
-
|
|
121
|
+
也可以直接让助手将报告导出为 HTML 文件。下载的文件可用浏览器打开,离线阅读和交互,无需连接原始数据源。
|
|
122
|
+
编辑模式下,请先保存或取消编辑再导出。
|
|
151
123
|
|
|
152
|
-
##
|
|
124
|
+
## 分析超时设置
|
|
153
125
|
|
|
154
|
-
|
|
126
|
+
在“设置 → 插件 → 插件配置 → 数据分析”中可调整 Python 分析的默认超时时间,默认为 120 秒,
|
|
127
|
+
保存后对下一次分析调用生效。实际执行时限仍受 Harness 执行策略约束。
|
|
155
128
|
|
|
156
|
-
##
|
|
129
|
+
## 许可证
|
|
157
130
|
|
|
158
|
-
|
|
131
|
+
本项目采用 [MIT License](LICENSE)。
|
|
@@ -23,7 +23,7 @@ export function TestResult({ result, stale = false }) {
|
|
|
23
23
|
const t = useCopy();
|
|
24
24
|
if (!result)
|
|
25
25
|
return null;
|
|
26
|
-
return (_jsxs("div", { className: "mc-result", "data-tone":
|
|
26
|
+
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(CredentialIcon, { name: stale || !result.ok ? 'info' : 'check', size: 16 }), stale
|
|
27
27
|
? t('marivo.credentials.configuration-changed-test-again')
|
|
28
28
|
: result.ok
|
|
29
29
|
? t('marivo.credentials.connection-test-succeeded')
|
|
@@ -47,7 +47,7 @@ function OperationOutcome({ entry }) {
|
|
|
47
47
|
operation?.result?.ok === false;
|
|
48
48
|
if (!unsuccessful && operation?.action !== 'delete')
|
|
49
49
|
return null;
|
|
50
|
-
return (_jsxs("div", { className: "mc-operation-note", "data-error":
|
|
50
|
+
return (_jsxs("div", { className: "mc-operation-note", "data-error": Boolean(entry.error || errors.length), role: "status", children: [entry.error && _jsx("p", { children: t(entry.error) }), operation?.status === 'cancelled' && (_jsx("p", { children: t('marivo.credentials.this-operation-was-cancelled') })), operation?.status === 'succeeded' && operation.action === 'delete' && (_jsx("p", { children: t('marivo.credentials.saved-value-deleted-configuration-status-updated') })), errors.length > 0 && _jsx("p", { children: errors.map(credentialMessage).join(' ') }), operation?.status === 'failed' && errors.length === 0 && !operation.result && (_jsx("p", { children: t('marivo.credentials.operation-incomplete-please-retry') })), unsuccessful && operation?.saved.length > 0 && (_jsxs("p", { children: [t('marivo.credentials.saved'), operation.saved.join('、'), "\u3002"] }))] }));
|
|
51
51
|
}
|
|
52
52
|
export function DatasourceProperties({ context }) {
|
|
53
53
|
const t = useCopy();
|
|
@@ -307,7 +307,7 @@ export function installCredentials(ctx, rpc, changes) {
|
|
|
307
307
|
: t('marivo.credentials.awaiting-credentials'), type: "button", onClick: () => model.openRequest(request.id), children: t('marivo.credentials.configuration-needed') })) : null;
|
|
308
308
|
})));
|
|
309
309
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ locale: 'marivo.navigation', name: 'shell.overlay', id: 'marivo-credential-observer' }, localized(ctx, function Observer({ useSessions, useWorkspaces }) {
|
|
310
|
-
const sessionId = useSessions((state) => state.
|
|
310
|
+
const sessionId = useSessions((state) => state.ids.find((id) => state.byId[id]?.retainedBy.mainView)) ?? '';
|
|
311
311
|
const workspaces = useWorkspaces((state) => state.items);
|
|
312
312
|
const currentWorkspace = workspaces.find((item) => item.sessionIds.includes(sessionId))?.workspaceId ?? '';
|
|
313
313
|
useEffect(() => model.session(sessionId), [sessionId]);
|
package/lib/client/i18n/copy.js
CHANGED
|
@@ -3,13 +3,15 @@ import * as navigation from "./navigation.js";
|
|
|
3
3
|
import * as presentation from "./presentation.js";
|
|
4
4
|
import * as python from "./python.js";
|
|
5
5
|
import * as semantic from "./semantic.js";
|
|
6
|
-
|
|
6
|
+
import * as settings from "./settings.js";
|
|
7
|
+
export const dictionaries = { credentials, navigation, presentation, semantic, python, settings };
|
|
7
8
|
export const zh = {
|
|
8
9
|
...credentials.zh,
|
|
9
10
|
...navigation.zh,
|
|
10
11
|
...presentation.zh,
|
|
11
12
|
...semantic.zh,
|
|
12
13
|
...python.zh,
|
|
14
|
+
...settings.zh,
|
|
13
15
|
};
|
|
14
16
|
export const en = {
|
|
15
17
|
...credentials.en,
|
|
@@ -17,6 +19,7 @@ export const en = {
|
|
|
17
19
|
...presentation.en,
|
|
18
20
|
...semantic.en,
|
|
19
21
|
...python.en,
|
|
22
|
+
...settings.en,
|
|
20
23
|
};
|
|
21
24
|
export function message(key, params = {}) {
|
|
22
25
|
return { key, params };
|
package/lib/client/i18n/host.js
CHANGED
|
@@ -8,6 +8,7 @@ export function installCopy(ctx) {
|
|
|
8
8
|
ctx.effect(() => ctx.locale.register('marivo.presentation', dictionaries.presentation));
|
|
9
9
|
ctx.effect(() => ctx.locale.register('marivo.semantic', dictionaries.semantic));
|
|
10
10
|
ctx.effect(() => ctx.locale.register('marivo.python', dictionaries.python));
|
|
11
|
+
ctx.effect(() => ctx.locale.register('marivo.settings', dictionaries.settings));
|
|
11
12
|
}
|
|
12
13
|
export function hostCopy(ctx) {
|
|
13
14
|
const active = ctx.locale.getSnapshot().active;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const zh = {
|
|
2
|
+
'marivo.settings.title': '数据分析',
|
|
3
|
+
'marivo.settings.timeout': 'Python 默认执行超时(秒)',
|
|
4
|
+
'marivo.settings.hint': '默认 120 秒。保存后对所有 Workspace 的下一次 Python 调用生效,不改变已开始调用的时限。单次调用可指定其他时限;仍受 Harness 执行限制约束。',
|
|
5
|
+
'marivo.settings.description': 'Python 执行设置。',
|
|
6
|
+
'marivo.settings.expand': '展开设置',
|
|
7
|
+
'marivo.settings.collapse': '收起设置',
|
|
8
|
+
'marivo.settings.save': '保存',
|
|
9
|
+
'marivo.settings.saving': '保存中…',
|
|
10
|
+
'marivo.settings.discard': '放弃修改',
|
|
11
|
+
'marivo.settings.unsaved': '未保存',
|
|
12
|
+
'marivo.settings.invalid': '请输入 0.001 至 2147483.647 秒,最多三位小数。',
|
|
13
|
+
'marivo.settings.read-only': '当前连接的设置只读。',
|
|
14
|
+
'marivo.settings.failed': '保存失败或设置已被其他页面修改。草稿已保留;可放弃修改以读取最新值,再重试。',
|
|
15
|
+
};
|
|
16
|
+
export const en = {
|
|
17
|
+
'marivo.settings.title': 'Data analysis',
|
|
18
|
+
'marivo.settings.timeout': 'Default Python execution timeout (seconds)',
|
|
19
|
+
'marivo.settings.hint': 'Defaults to 120 seconds. Saved changes apply to the next Python call in every Workspace; calls already started keep their timeout. Individual calls may override it. Harness execution limits still apply.',
|
|
20
|
+
'marivo.settings.description': 'Python execution settings.',
|
|
21
|
+
'marivo.settings.expand': 'Expand settings',
|
|
22
|
+
'marivo.settings.collapse': 'Collapse settings',
|
|
23
|
+
'marivo.settings.save': 'Save',
|
|
24
|
+
'marivo.settings.saving': 'Saving…',
|
|
25
|
+
'marivo.settings.discard': 'Discard changes',
|
|
26
|
+
'marivo.settings.unsaved': 'Unsaved',
|
|
27
|
+
'marivo.settings.invalid': 'Enter 0.001–2147483.647 seconds with at most three decimal places.',
|
|
28
|
+
'marivo.settings.read-only': 'Settings are read-only on this connection.',
|
|
29
|
+
'marivo.settings.failed': 'Saving failed or another page changed these settings. Your draft is preserved. Discard it to read the latest value, then retry.',
|
|
30
|
+
};
|
|
@@ -3,7 +3,7 @@ export function ownedInput(host, sessionId, workspaceId) {
|
|
|
3
3
|
const owners = workspaces.items.filter((item) => item.sessionIds.includes(sessionId));
|
|
4
4
|
if (!sessionId ||
|
|
5
5
|
!workspaceId ||
|
|
6
|
-
host.sessions.list.getSnapshot().
|
|
6
|
+
!host.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ||
|
|
7
7
|
workspaces.phase !== 'ready' ||
|
|
8
8
|
workspaces.state === 'error' ||
|
|
9
9
|
owners.length !== 1 ||
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { CodeBlock, DisclosureRow,
|
|
2
|
+
import { CodeBlock, DisclosureRow, IconCodeOutlineRegular, writeClipboard, } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
3
|
import { useMemo, useState } from 'react';
|
|
4
4
|
import { useCopy } from "../i18n/context.js";
|
|
5
5
|
import { pythonToolModel } from "./model.js";
|
|
@@ -29,7 +29,7 @@ export function PythonToolCard({ block, inspect }) {
|
|
|
29
29
|
const ok = await writeClipboard(code).catch(() => false);
|
|
30
30
|
setCopy({ code, ok });
|
|
31
31
|
};
|
|
32
|
-
return (_jsxs("div", { className: "mp-tool", "data-state": model.state, children: [_jsx("style", { children: styles }), _jsx(DisclosureRow, { icon: _jsx(
|
|
32
|
+
return (_jsxs("div", { className: "mp-tool", "data-state": model.state, children: [_jsx("style", { children: styles }), _jsx(DisclosureRow, { icon: _jsx(IconCodeOutlineRegular, { size: 16 }), title: "marivo_python", open: open, expandable: true, expandOnRowClick: true, keepContentWhenOpen: true, onToggle: () => setOpen((value) => !value), collapsedContent: _jsxs(_Fragment, { children: [_jsx("span", { className: "mp-summary", children: model.summary }), _jsx("span", { className: "mp-state", children: t(`marivo.python.${model.state}`) })] }), children: open && (_jsxs("div", { className: "mp-body", children: [_jsxs("section", { "aria-label": t('marivo.python.input'), children: [_jsxs("div", { className: "mp-input-heading", children: [_jsx("h4", { children: t('marivo.python.input') }), model.code !== undefined && (_jsx("button", { type: "button", className: "mp-inspect", onClick: () => void copySource(), children: t(copy?.code === model.code && copy.ok
|
|
33
33
|
? 'marivo.python.copied'
|
|
34
34
|
: 'marivo.python.copy') }))] }), copy && copy.code === model.code && !copy.ok && (_jsx("p", { role: "status", className: "mp-notice", children: t('marivo.python.copy-failed') })), _jsx("div", { className: "mp-scroll", children: model.code === undefined ? (_jsx("pre", { children: model.input })) : (_jsx(CodeBlock, { code: `${model.code}\n`, lang: "python", className: "mp-code", copyLabel: t('marivo.python.copy'), copiedLabel: t('marivo.python.copied') })) })] }), _jsxs("dl", { className: "mp-facts", children: [model.datasources !== undefined && (_jsxs("div", { children: [_jsx("dt", { children: "datasources" }), _jsx("dd", { children: JSON.stringify(model.datasources) })] })), model.timeoutMs !== undefined && (_jsxs("div", { children: [_jsx("dt", { children: "timeoutMs" }), _jsxs("dd", { children: [model.timeoutMs, " ms"] })] })), model.facts.map(({ name, value }) => (_jsxs("div", { children: [_jsx("dt", { children: name }), _jsx("dd", { children: value })] }, name)))] }), model.notices.map((notice) => (_jsx("p", { className: "mp-notice", children: notice }, notice))), model.facts.some((fact) => fact.name === 'truncated' && fact.value === 'true') && (_jsx("p", { className: "mp-notice", children: t('marivo.python.truncated') })), ['stdout', 'stderr'].map((name) => model[name] === undefined ? null : (_jsxs("section", { "aria-label": name, children: [_jsx("h4", { children: name }), _jsx("pre", { className: "mp-scroll", children: model[name] })] }, name))), model.fallback !== undefined && (_jsxs("section", { "aria-label": t('marivo.python.output'), children: [_jsx("h4", { children: t('marivo.python.output') }), _jsx("pre", { className: "mp-scroll", children: model.fallback })] })), _jsxs("details", { children: [_jsx("summary", { children: t('marivo.python.raw') }), _jsx("h4", { children: t('marivo.python.input') }), _jsx("pre", { className: "mp-scroll", children: model.input }), model.output !== undefined && (_jsxs(_Fragment, { children: [_jsx("h4", { children: t('marivo.python.output') }), _jsx("pre", { className: "mp-scroll", children: model.output })] }))] }), inspect && (_jsx("div", { children: _jsx("button", { type: "button", className: "mp-inspect", onClick: inspect, children: t('marivo.python.inspect') }) }))] })) })] }));
|
|
35
35
|
}
|
|
@@ -15,7 +15,7 @@ function parse(text) {
|
|
|
15
15
|
/** Read only the frozen Harness call; never fetch results or infer execution from settlement. */
|
|
16
16
|
export function pythonToolModel(block) {
|
|
17
17
|
const done = 'kind' in block;
|
|
18
|
-
const input = (done ? block.call?.argsRaw : block.argsRaw) ?? '';
|
|
18
|
+
const input = (done ? block.call?.argsRaw : block.phase === 'start' ? block.argsRaw : undefined) ?? '';
|
|
19
19
|
const args = parse(input);
|
|
20
20
|
const code = typeof args?.code === 'string' ? args.code : undefined;
|
|
21
21
|
const output = done
|
|
@@ -31,6 +31,7 @@ export const inject = [
|
|
|
31
31
|
'conversation',
|
|
32
32
|
'sidebarRight',
|
|
33
33
|
'sidebarRightTabs',
|
|
34
|
+
'uiWorkspace',
|
|
34
35
|
];
|
|
35
36
|
const labels = workspaceDirectories;
|
|
36
37
|
/** Default browser integration. Layout belongs to Harness; mutable content belongs to occurrences. */
|
|
@@ -78,7 +79,7 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
78
79
|
state.phase !== 'ready' ||
|
|
79
80
|
state.state === 'error' ||
|
|
80
81
|
workspaceFor(sessionId) !== workspaceId ||
|
|
81
|
-
(foreground && ctx.sessions.list.getSnapshot().
|
|
82
|
+
(foreground && !ctx.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView))
|
|
82
83
|
throw new Error('marivo.navigation.the-owning-session-or-workspace-changed-return-to-its');
|
|
83
84
|
};
|
|
84
85
|
const changed = (workspaceId, reportId) => {
|
|
@@ -213,7 +214,7 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
213
214
|
}),
|
|
214
215
|
};
|
|
215
216
|
return (_jsxs("section", { className: "rt-page", "data-rt-tab": tab.id, "data-rt-panel": panelId, "data-rt-kind": page.target.kind, "data-rt-workspace": page.target.workspaceId, "data-rt-build": report.document?.buildId, "data-rt-current": page.target.kind === 'report' && !page.target.buildId ? 'true' : undefined, children: [_jsx("style", { children: catalogStyles + credentialStyles + rightTabStyles }), state.error ? (_jsx("p", { role: "alert", children: t(state.error) })) : (_jsxs(_Fragment, { children: [page.target.kind === 'reports' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rt-heading-row", children: [_jsx("h2", { className: "rt-heading", children: t(labels[page.target.kind]) }), page.target.kind === 'reports' && (_jsx("button", { type: "button", disabled: catalog.loading, onClick: () => void page.refresh(), children: t('marivo.presentation.refresh') }))] }), _jsx("p", { className: "rt-caption", children: workspaces.find((w) => w.workspaceId === page.target.workspaceId)?.title ??
|
|
216
|
-
page.target.workspaceId })] })), state.newer && (_jsxs("p", { role: "status", className: "rt-notice", children: [t('marivo.navigation.a-newer-version-exists-the-current-view-is-unchanged'), _jsx("button", { type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.view-new-version') })] })), state.notice && _jsx("p", { role: "status", children: t(state.notice) }), page.target.kind === 'reports' && (_jsx(ReportCatalogView, { model: page.catalog, reader: catalogReader, sessions: sessions, onOpenSession: (id) => ctx.
|
|
217
|
+
page.target.workspaceId })] })), state.newer && (_jsxs("p", { role: "status", className: "rt-notice", children: [t('marivo.navigation.a-newer-version-exists-the-current-view-is-unchanged'), _jsx("button", { type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.view-new-version') })] })), state.notice && _jsx("p", { role: "status", children: t(state.notice) }), page.target.kind === 'reports' && (_jsx(ReportCatalogView, { model: page.catalog, reader: catalogReader, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id) })), page.target.kind === 'semantic' && (_jsx(SemanticBrowserPanel, { model: page.semantic, onAsk: canAsk ? () => page.semantic.addToQuestion(ctx, page.sessionId) : undefined, workspaces: workspaces })), page.target.kind === 'datasources' && (_jsx(CredentialPanel, { model: page.datasources, workspaces: workspaces, onRefresh: () => void page.refresh() })), page.target.kind === 'report' && (_jsxs(_Fragment, { children: [report.editing && (_jsxs("div", { className: "rt-toolbar", role: "toolbar", "aria-label": t('marivo.presentation.report-editing-actions'), children: [_jsx("button", { type: "button", disabled: report.saving || !report.editing.undo.length, onClick: () => page.reader.undoEdit(), children: t('marivo.presentation.undo') }), _jsx("button", { type: "button", disabled: report.saving || !report.editing.redo.length, onClick: () => page.reader.redoEdit(), children: t('marivo.presentation.redo') }), _jsx("button", { type: "button", disabled: report.saving, onClick: () => act(page, () => save(page, tab)), children: report.saving
|
|
217
218
|
? t('marivo.presentation.saving')
|
|
218
219
|
: t('marivo.presentation.save-edits') }), _jsx("button", { type: "button", disabled: report.saving, onClick: () => {
|
|
219
220
|
if (!page.reader.dirty ||
|
|
@@ -221,7 +222,7 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
221
222
|
page.reader.cancelEdit();
|
|
222
223
|
}, children: t('marivo.presentation.cancel-edits') }), _jsx("span", { children: page.reader.dirty
|
|
223
224
|
? t('marivo.presentation.unsaved-edits')
|
|
224
|
-
: t('marivo.presentation.edit-mode') })] })), report.editError && _jsx("p", { role: "alert", children: t(report.editError) }), report.notice && _jsx("p", { role: "status", children: t(report.notice) }), report.publicationUrl && (_jsx("p", { children: _jsx("a", { href: report.publicationUrl, target: "_blank", rel: "noopener noreferrer", children: t('marivo.presentation.open-published-report') }) })), report.loading && _jsx("p", { role: "status", children: t('marivo.navigation.loading-report') }), report.error && (_jsxs("div", { role: "alert", children: [_jsx("p", { children: t(report.error) }), _jsx("button", { type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.reload-report') })] })), report.historyOpen && (_jsx(ReportHistoryPanel, { state: report, model: historyModel, sessions: sessions, onOpenSession: (id) => ctx.
|
|
225
|
+
: t('marivo.presentation.edit-mode') })] })), report.editError && _jsx("p", { role: "alert", children: t(report.editError) }), report.notice && _jsx("p", { role: "status", children: t(report.notice) }), report.publicationUrl && (_jsx("p", { children: _jsx("a", { href: report.publicationUrl, target: "_blank", rel: "noopener noreferrer", children: t('marivo.presentation.open-published-report') }) })), report.loading && _jsx("p", { role: "status", children: t('marivo.navigation.loading-report') }), report.error && (_jsxs("div", { role: "alert", children: [_jsx("p", { children: t(report.error) }), _jsx("button", { type: "button", onClick: () => void page.refresh(), children: t('marivo.navigation.reload-report') })] })), report.historyOpen && (_jsx(ReportHistoryPanel, { state: report, model: historyModel, sessions: sessions, onOpenSession: (id) => ctx.uiWorkspace.openSession(id) })), report.document && (_jsx(HostPresentationReader, { document: report.document, closeSourceOnNavigate: true, viewMemory: page.viewMemory, exportActions: {
|
|
225
226
|
downloadFullReport: () => void page.reader.downloadDisplayed(),
|
|
226
227
|
publishing: report.publishingName
|
|
227
228
|
? {
|
|
@@ -257,7 +258,7 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
257
258
|
function Body({ sessionId, useTabInfo, useWorkspaces, useSessions }) {
|
|
258
259
|
const t = useCopy();
|
|
259
260
|
const { tab, panel } = useTabInfo();
|
|
260
|
-
const workspaces = useWorkspaces((s) => s.items), sessions = useSessions((s) => s.byId), currentSession = useSessions((s) => s.
|
|
261
|
+
const workspaces = useWorkspaces((s) => s.items), sessions = useSessions((s) => s.byId), currentSession = useSessions((s) => s.ids.find((id) => s.byId[id]?.retainedBy.mainView)), workspaceReady = useWorkspaces((s) => s.phase === 'ready' && s.state !== 'error');
|
|
261
262
|
const [page, setPage] = useState(), [error, setError] = useState('');
|
|
262
263
|
useEffect(() => {
|
|
263
264
|
if (tab.signal.aborted)
|
|
@@ -383,7 +384,9 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
383
384
|
const bind = () => {
|
|
384
385
|
if (!ctx.connection.generation.getSnapshot())
|
|
385
386
|
return stopFeeds();
|
|
386
|
-
const current = ctx.sessions.list
|
|
387
|
+
const current = ctx.sessions.list
|
|
388
|
+
.getSnapshot()
|
|
389
|
+
.ids.find((id) => ctx.sessions.list.getSnapshot().byId[id]?.retainedBy.mainView);
|
|
387
390
|
const workspaces = ctx.workspaces.list.getSnapshot();
|
|
388
391
|
if (workspaces.phase !== 'ready' || workspaces.state === 'error')
|
|
389
392
|
return stopFeeds();
|
|
@@ -414,14 +417,14 @@ export function installRightTabs(ctx, { diagnostics = false } = {}) {
|
|
|
414
417
|
check(id, workspaceId);
|
|
415
418
|
}
|
|
416
419
|
catch (error) {
|
|
417
|
-
if (ctx.sessions.list.getSnapshot().
|
|
420
|
+
if (ctx.sessions.list.getSnapshot().byId[id]?.retainedBy.mainView)
|
|
418
421
|
fail(error);
|
|
419
422
|
return;
|
|
420
423
|
}
|
|
421
424
|
changed(workspaceId, reportId);
|
|
422
425
|
// Publication invalidation is Workspace-wide. Foreground navigation
|
|
423
426
|
// alone is gated, and background deliveries are never queued.
|
|
424
|
-
if (ctx.sessions.list.getSnapshot().
|
|
427
|
+
if (!ctx.sessions.list.getSnapshot().byId[id]?.retainedBy.mainView)
|
|
425
428
|
return;
|
|
426
429
|
try {
|
|
427
430
|
navigate(id, { kind: 'report', workspaceId, reportId, buildId }, ctx.sidebarRight, true);
|