dsh-pictor 0.1.0 → 0.2.1
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/NOTICE.md +18 -0
- package/README.md +56 -50
- package/README.zh-cn.md +87 -0
- package/docs/screenshot-empty.png +0 -0
- package/docs/screenshot-workbench.png +0 -0
- package/lib/client.js +121 -19
- package/lib/image-providers.js +1 -1
- package/lib/index.js +46 -0
- package/package.json +6 -2
- package/references/domain/base-prompt.md +11 -13
- package/references/domain/styles/bandung-circuit.md +9 -16
package/NOTICE.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# NOTICE — 第三方内容声明
|
|
2
|
+
|
|
3
|
+
本仓库包含以下源自第三方的内容,请在分发或再使用时留意相应许可:
|
|
4
|
+
|
|
5
|
+
## references/paper/
|
|
6
|
+
|
|
7
|
+
- `2601.23265v1.md`:arXiv 预印本 2601.23265v1 的正文提取(仅文本,不含 PDF),
|
|
8
|
+
作为研究背景资料随源码仓库分发。
|
|
9
|
+
- `paperbanana-prompts.md`:同一 arXiv 预印本附录 G、H 中提示词原文的忠实提取与中文翻译。
|
|
10
|
+
|
|
11
|
+
两者均**不随发布包分发**(package.json 的 files 仅含 `references/domain`),
|
|
12
|
+
只存在于源码仓库。再分发请以 arXiv 原文及其作者声明的许可为准。
|
|
13
|
+
|
|
14
|
+
## 免责声明
|
|
15
|
+
|
|
16
|
+
除上述文件外,本仓库其余内容(agents/ 蓝图、references/domain/ 模板与规范、
|
|
17
|
+
src/、fixtures/ 示例文档)均为作者原创。样式与布局模板的早期版本
|
|
18
|
+
曾参考 PaperBanana(arXiv:2601.23265v1)的设计思路,但经重写已不包含其原文。
|
package/README.md
CHANGED
|
@@ -1,81 +1,87 @@
|
|
|
1
|
-
# Pictor —
|
|
1
|
+
# Pictor — Document-to-Infographic Workbench
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[ EN | [中文](./README.zh-cn.md) ]
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
Pictor turns a document into a set of infographics. It is a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin published to npm as [`dsh-pictor`](https://www.npmjs.com/package/dsh-pictor), with source at [github.com/bandung-circuits/pictor](https://github.com/bandung-circuits/pictor). Each project is one coherent agent session that carries the whole extract → advise → render flow; the GUI is a thin layer that only shows file-fact state and shapes your input. Design decisions live in [docs/DESIGN.zh.md](docs/DESIGN.zh.md) (Chinese).
|
|
6
6
|
|
|
7
|
-
Pictor
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- 唯一入口是 dsh 左下角 footer 按钮「Pictor」,打开 shell.overlay 工作台:左栏列项目,右栏显示当前项目。
|
|
13
|
-
- 项目步骤固定三步:提取结构 → 方案设计 → 渲染出图。每步由你做决定后,会话在同一会话里接着执行(agent-loop 的 resume 语义),不重跑已有阶段。
|
|
14
|
-
- 项目名取文档首行,信息条内随时可改名,只动 index.json。
|
|
15
|
-
- 状态只由文件事实驱动;运行中判定以 dsh agent 注册表为权威;把你对 orchestration 的关心都留给会话。
|
|
11
|
+
Pictor is published to npm as `dsh-pictor`:
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
```bash
|
|
14
|
+
dsh plugin --profile <profile> add dsh-pictor
|
|
15
|
+
```
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
- 画图模型:在工作台「设置」里配置(seedream / gemini / openai-compatible / mock)。密钥经 dsh 凭据子系统保存,配置只留引用。
|
|
17
|
+
Replace `<profile>` with the target profile name (e.g. `desktop`, `web`) and start that profile. A **Pictor** button appears at the bottom-left of the dsh footer. On first use a `~/.pictor` home is provisioned and registered as a DSH workspace; project sessions group under "Pictor" in the sidebar. For development installs (local link, live-reload while editing source) see [docs/DESIGN.zh.md](docs/DESIGN.zh.md).
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
**For non-technical users**: install [DSH Desktop](https://dshdesktop.com/en/) (a community project, not an official DeepSeek product; it bundles the DeepSeek Harness runtime as a ready-to-run desktop app) and let the agent install the plugin for you — just send this one message in dsh:
|
|
23
20
|
|
|
24
|
-
Pictor
|
|
21
|
+
> Install the Pictor plugin from the npm package `dsh-pictor` into the current profile, restart if needed, and confirm when a Pictor button appears at the bottom left.
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
## Supported dsh version
|
|
24
|
+
|
|
25
|
+
Pictor is built and tested against **DSH Desktop 0.7.2** (bundled Harness **0.1.2-alpha.1**). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
1. Open the workbench via the **Pictor** footer button and click **New project**: upload a file (`md` / `txt` / `docx` / `pdf` / image) or paste content (from Word or the web; formatting is kept, stored as sanitised HTML).
|
|
30
|
+
2. The session reads and normalises the source (`docx` / `pdf` / image / HTML → `document.md` via DSH tooling) and proposes candidate structures.
|
|
31
|
+
3. Tick the structures and confirm; the session moves to proposal design. Tick a proposal, pick an aspect ratio, and click **Render selected**.
|
|
32
|
+
4. Preview and download from the result grid; open the discussion panel to talk to the session at any point, or use the step bar to go back and redo existing artifacts.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+

|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
2. 会话读取并规整来源(docx/pdf/图片/HTML → document.md 由会话用 dsh 工具完成),提取候选结构。
|
|
36
|
-
3. 勾选结构,确认,会话进入方案设计;勾选方案,设画面比例,点「渲染所选方案」。
|
|
37
|
-
4. 结果网格里预览、下载;随时可展开讨论面板对会话说话,或点步骤条回到已有产物的步骤重做。
|
|
36
|
+
## Form
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
- A `~/.pictor` home is created on first use (registered as a DSH workspace; sidebar shows "Pictor").
|
|
39
|
+
- The only entry point is the bottom-left **Pictor** footer button, which toggles a `shell.overlay` workbench: projects on the left, the selected project on the right.
|
|
40
|
+
- Every project has three fixed steps: extract structures → design proposals → render images. After each human decision the **same session resumes** (agent-loop resume semantics); existing artifacts are never re-derived.
|
|
41
|
+
- The project name is taken from the document's first line and can be renamed any time in the info bar (touches only `index.json`).
|
|
42
|
+
- State is driven purely by file facts; running status is authoritative via the DSH agent registry; orchestration concerns are left to the session.
|
|
43
|
+
|
|
44
|
+
## Two models
|
|
45
|
+
|
|
46
|
+
- **Reasoner**: extract and advise run on DSH's current default model — no Pictor configuration needed.
|
|
47
|
+
- **Image model**: configured in the workbench Settings (`seedream` / `gemini` / `openai-compatible` / `mock`). API keys go through DSH's credential subsystem; the configuration stores only a reference.
|
|
48
|
+
|
|
49
|
+
## Development & verification
|
|
40
50
|
|
|
41
51
|
```bash
|
|
42
|
-
npm run build
|
|
43
|
-
npm run verify
|
|
44
|
-
npm run verify:integration # L3 transport
|
|
45
|
-
npm run test:
|
|
52
|
+
npm run build # esbuild build of host + bundled client
|
|
53
|
+
npm run verify # L1 unit + L2 host integration (mock ctx, 15 items)
|
|
54
|
+
npm run verify:integration # L3 transport smoke against a real dsh web (hermetic temp home)
|
|
55
|
+
npm run test:pack # package integrity: tarball carries lib/, agents/, references/, assets/
|
|
56
|
+
npm run test:install # L5: README install path (dsh plugin add) → real dsh web boot
|
|
57
|
+
npm run test:e2e # L4a browser e2e (fixture data, deterministic)
|
|
58
|
+
npm run hooks:install # one-time: enable the pre-push hook (git config core.hooksPath .githooks)
|
|
46
59
|
```
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
The pre-push hook runs `npm run verify` + `npm run test:install` before every push; all smoke layers are hermetic (temp `DSH_HOME`, never the live `~/.dsh`).
|
|
62
|
+
|
|
63
|
+
Test-layer definitions and rationale: [docs/DESIGN.zh.md](docs/DESIGN.zh.md) §7.
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
### Layout
|
|
51
66
|
|
|
52
67
|
```
|
|
53
68
|
pictor/
|
|
54
|
-
├── agents/ #
|
|
69
|
+
├── agents/ # declarative blueprints (orchestrator/extractor/advisor/renderer)
|
|
55
70
|
├── references/domain/ # base-prompt + layouts/ + styles/ + diagram-types/ + visual-principles
|
|
56
|
-
├── src/host/ #
|
|
57
|
-
├── src/client/ #
|
|
71
|
+
├── src/host/ # plugin host: ~/.pictor, project sessions, /pictor RPC, image generation
|
|
72
|
+
├── src/client/ # workbench UI (React.createElement + theme variables)
|
|
58
73
|
├── e2e/ # L4a Playwright
|
|
59
|
-
├── verify.mjs # L1+L2
|
|
74
|
+
├── verify.mjs # L1+L2 offline smoke
|
|
60
75
|
└── scripts/ # build + transport-smoke
|
|
61
76
|
```
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
## 许可证
|
|
66
|
-
|
|
67
|
-
MIT,见 [LICENSE](LICENSE)。
|
|
68
|
-
|
|
69
|
-
---
|
|
78
|
+
Data home `~/.pictor/`: `index.json` (project index), `pictor-config.json` (image-model configuration), `<project-id>/` (self-contained: agents/references snapshot + `10.input` + `11.extraction` + `12.advice` + `output`).
|
|
70
79
|
|
|
71
|
-
##
|
|
80
|
+
## Documents
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
- Design decisions: [docs/DESIGN.zh.md](docs/DESIGN.zh.md) (Chinese)
|
|
83
|
+
- Verification checklist: [docs/verification-checklist.zh.md](docs/verification-checklist.zh.md) (Chinese)
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
- The only entry point is the bottom-left "Pictor" footer button, which toggles a `shell.overlay` workbench: projects on the left, the selected project on the right.
|
|
77
|
-
- Every project has three fixed steps: extract structures → design proposals → render images. After each human decision the **same session resumes** to keep working; existing artifacts are never re-derived (file facts are the source of truth).
|
|
78
|
-
- Reasoner: DSH's default model (no Pictor config). Image model: configured in the workbench Settings (seedream / gemini / openai-compatible / mock); the API key goes through DSH's credentials subsystem.
|
|
79
|
-
- Tests: L1 unit + L2 host integration (`npm run verify`), L3 real-dsh transport smoke (`npm run verify:integration`), L4a browser e2e against a seeded fixture (`npm run test:e2e`).
|
|
85
|
+
## License
|
|
80
86
|
|
|
81
|
-
MIT
|
|
87
|
+
MIT — see [LICENSE](LICENSE). Third-party content notices: [NOTICE.md](NOTICE.md).
|
package/README.zh-cn.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Pictor — 文档转信息图工作台
|
|
2
|
+
|
|
3
|
+
[ [English](./README.md) | 中文 ]
|
|
4
|
+
|
|
5
|
+
Pictor 把一份文档变成一组信息图。它是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的插件,已发布到 npm 包 [`dsh-pictor`](https://www.npmjs.com/package/dsh-pictor),源码仓库在 [github.com/bandung-circuits/pictor](https://github.com/bandung-circuits/pictor)。每新建一个项目,宿主创建一个连贯的会话作为项目会话,整个「提取 → 方案 → 渲染」流程都在这个会话里推进,GUI 只做两件事:查看信息、规整输入。设计文档见 [docs/DESIGN.zh.md](docs/DESIGN.zh.md)。
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
已发布到 npm,安装即一行命令:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
dsh plugin --profile <profile> add dsh-pictor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
把 `<profile>` 换成目标 profile 名(如 `desktop`、`web`),然后启动该 profile,dsh 左下角会出现「Pictor」按钮。首次使用自动生成 `~/.pictor` 工作区(注册为 dsh 工作区,侧栏归组见「Pictor」)。开发安装(改动源码实时联调)见 [docs/DESIGN.zh.md](docs/DESIGN.zh.md)。
|
|
18
|
+
|
|
19
|
+
非技术用户可安装 [DSH Desktop](https://dshdesktop.com/en/)(社区维护项目,非 DeepSeek 官方产品,把 DeepSeek Harness 运行封装成可直接打开的桌面应用),然后让 dsh 里的 AI 帮装,发这一句即可:
|
|
20
|
+
|
|
21
|
+
> 帮我把 Pictor 插件装上,从 npm 包 dsh-pictor 装到当前 profile,需要的话重启,装好告诉我左下角有没有 Pictor 入口。
|
|
22
|
+
|
|
23
|
+
## 适配的 DSH 版本
|
|
24
|
+
|
|
25
|
+
Pictor 以 **DSH Desktop 0.7.2**(内置 Harness **0.1.2-alpha.1**)为适配与测试基准。DeepSeek Harness 的客户端 API(工作区/会话服务)演进很快,本月两个版本之间可能就面目全非,所以本插件只跟住一个 harness 世代。其他版本——无论更旧更新——可能能跑、也可能无预告地坏掉,都不在我们的支持范围内。DSH 升级后插件不工作,请先回到上面这个版本,而不是去排查版本差异。
|
|
26
|
+
|
|
27
|
+
## 使用
|
|
28
|
+
|
|
29
|
+
1. 打开工作台,点「新建项目」:上传文件(md/txt/docx/pdf/图片)或粘贴内容(从 Word/网页粘贴,格式保留,落盘为消毒 HTML)。
|
|
30
|
+
2. 会话读取并规整来源(docx/pdf/图片/HTML → document.md 由会话用 dsh 工具完成),提取候选结构。
|
|
31
|
+
3. 勾选结构,确认,会话进入方案设计;勾选方案,设画面比例,点「渲染所选方案」。
|
|
32
|
+
4. 结果网格里预览、下载;随时可展开讨论面板对会话说话,或点步骤条回到已有产物的步骤重做。
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
## 形态
|
|
37
|
+
|
|
38
|
+
- 安装后首次使用自动生成 `~/.pictor` 工作区(注册为 dsh 工作区,侧栏归组见「Pictor」)。
|
|
39
|
+
- 唯一入口是 dsh 左下角 footer 按钮「Pictor」,打开 shell.overlay 工作台:左栏列项目,右栏显示当前项目。
|
|
40
|
+
- 项目步骤固定三步:提取结构 → 方案设计 → 渲染出图。每步由你做决定后,会话在同一会话里接着执行(agent-loop 的 resume 语义),不重跑已有阶段。
|
|
41
|
+
- 项目名取文档首行,信息条内随时可改名,只动 index.json。
|
|
42
|
+
- 状态只由文件事实驱动;运行中判定以 dsh agent 注册表为权威;把你对 orchestration 的关心都留给会话。
|
|
43
|
+
|
|
44
|
+
## 两个模型
|
|
45
|
+
|
|
46
|
+
- 推理模型:extract 与 advise 用 dsh 当前默认模型,无需在 Pictor 配置。
|
|
47
|
+
- 画图模型:在工作台「设置」里配置(seedream / gemini / openai-compatible / mock)。密钥经 dsh 凭据子系统保存,配置只留引用。
|
|
48
|
+
|
|
49
|
+
## 开发与验证
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm run build # esbuild 构建 host + 拼接 client
|
|
53
|
+
npm run verify # L1 单元 + L2 host 集成(mock ctx,15 项)
|
|
54
|
+
npm run verify:integration # L3 transport 冒烟(真实 dsh web,封闭临时 DSH_HOME)
|
|
55
|
+
npm run test:pack # 打包完整性:tarball 必带 lib/、agents/、references/、assets/
|
|
56
|
+
npm run test:install # L5:README 安装路径(dsh plugin add)→ 真实 dsh web 启动
|
|
57
|
+
npm run test:e2e # L4a 浏览器 e2e(fixture 数据,确定性)
|
|
58
|
+
npm run hooks:install # 一次性启用 pre-push 钩子(git config core.hooksPath .githooks)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
pre-push 钩子在每次 push 前依次跑 `npm run verify` 与 `npm run test:install`;所有冒烟层都是封闭式(临时 `DSH_HOME`,不碰真实 `~/.dsh`)。
|
|
62
|
+
|
|
63
|
+
测试分层的定义与理由见 [docs/DESIGN.zh.md](docs/DESIGN.zh.md) 第 7 节。
|
|
64
|
+
|
|
65
|
+
## 目录
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
pictor/
|
|
69
|
+
├── agents/ # 声明式蓝图(orchestrator/extractor/advisor/renderer)
|
|
70
|
+
├── references/domain/ # base-prompt + layouts/ + styles/ + diagram-types/ + visual-principles
|
|
71
|
+
├── src/host/ # 插件宿主:~/.pictor、项目会话、/pictor RPC、生图
|
|
72
|
+
├── src/client/ # 工作台 UI(React.createElement + 主题变量)
|
|
73
|
+
├── e2e/ # L4a Playwright
|
|
74
|
+
├── verify.mjs # L1+L2 离线冒烟
|
|
75
|
+
└── scripts/ # build + transport-smoke
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
数据目录 `~/.pictor/`:`index.json`(项目索引)、`pictor-config.json`(画图模型配置)、`<项目 id>/`(自包含:agents/references 快照 + 10.input + 11.extraction + 12.advice + output)。
|
|
79
|
+
|
|
80
|
+
## 文档
|
|
81
|
+
|
|
82
|
+
- 设计与决策:[docs/DESIGN.zh.md](docs/DESIGN.zh.md)
|
|
83
|
+
- 验证清单:[docs/verification-checklist.zh.md](docs/verification-checklist.zh.md)
|
|
84
|
+
|
|
85
|
+
## 许可证
|
|
86
|
+
|
|
87
|
+
MIT,见 [LICENSE](LICENSE)。第三方内容声明见 [NOTICE.md](NOTICE.md)。
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -303,6 +303,7 @@ const I18N = {
|
|
|
303
303
|
layoutLabel: '布局', styleLabel: '风格', metaConfirmedHint: '确认后作为项目名与提取依据,可编辑。',
|
|
304
304
|
chooseLayout: '选择布局', chooseStyle: '选择风格', resetToAi: '回到 AI 推荐',
|
|
305
305
|
layoutChanged: '·已改', aiSuggestion: 'AI 推荐',
|
|
306
|
+
batchStyleLabel: '批量设置风格', batchStyleHint: '为所选方案套用同一风格(未选则全部)',
|
|
306
307
|
close: '关闭',
|
|
307
308
|
discussion: '讨论', discussHint: '对项目会话自由说话:补充参考、要求搜索、调整方向。你的决定会在同一会话里接着执行。',
|
|
308
309
|
discussPlaceholder: '例如:先不要继续,帮我查一下这篇文章提到的背景…',
|
|
@@ -360,6 +361,7 @@ const I18N = {
|
|
|
360
361
|
layoutLabel: 'Layout', styleLabel: 'Style', metaConfirmedHint: 'Confirmed as the project name and extraction basis; editable.',
|
|
361
362
|
chooseLayout: 'Choose layout', chooseStyle: 'Choose style', resetToAi: 'Reset to AI',
|
|
362
363
|
layoutChanged: '· edited', aiSuggestion: 'AI',
|
|
364
|
+
batchStyleLabel: 'Batch style', batchStyleHint: 'Apply one style to all selected proposals (all if none selected)',
|
|
363
365
|
close: 'Close',
|
|
364
366
|
discussion: 'Discussion', discussHint: 'Talk to the project session: add context, ask for research, adjust direction. Your decisions continue it in the same session.',
|
|
365
367
|
discussPlaceholder: 'e.g. Hold on — research the background this article mentions…',
|
|
@@ -485,10 +487,23 @@ function sanitizePaste(html) {
|
|
|
485
487
|
// 会话创建与驱动走 dsh 的 workspace 流程(照 pomasa 的 driveSession):
|
|
486
488
|
// 宿主只出 prompt,客户端经 workspaces.connectWorkspace 建会话(自然归入
|
|
487
489
|
// 「Pictor」工作区、继承 profile 默认模型),再经 sessions.prompt 驱动。
|
|
490
|
+
// dsh ≥ 0.1.2-alpha.1(DSH Desktop 0.7.2)把 connectWorkspace 从
|
|
491
|
+
// `workspaces` 服务移到了 `uiWorkspace` 服务上:这里两个形态都尝试,哪个
|
|
492
|
+
// 在就调哪个,老版本前端不受影响。
|
|
493
|
+
function sessionConnectFn(ctx, ws) {
|
|
494
|
+
let uiWs = null
|
|
495
|
+
try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
|
|
496
|
+
return (uiWs && typeof uiWs.connectWorkspace === 'function')
|
|
497
|
+
? uiWs.connectWorkspace.bind(uiWs)
|
|
498
|
+
: (ws && typeof ws.connectWorkspace === 'function') ? ws.connectWorkspace.bind(ws) : null
|
|
499
|
+
}
|
|
500
|
+
|
|
488
501
|
async function pictorWorkspace(ctx) {
|
|
489
502
|
const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
|
|
490
503
|
const base = await rpc(ctx, 'config.get').then((c) => (c && c.dataRoot) || '')
|
|
491
|
-
|
|
504
|
+
// 这里只用 list/create/delete/rename,新旧 services 都有;connectWorkspace
|
|
505
|
+
// 的可用性由 driveProjectSession 检查。
|
|
506
|
+
if (!ws) throw new Error('会话服务不可用(workspaces)')
|
|
492
507
|
const items = () => {
|
|
493
508
|
try {
|
|
494
509
|
const snap = ws.list && typeof ws.list.getSnapshot === 'function' ? ws.list.getSnapshot() : null
|
|
@@ -529,14 +544,29 @@ async function pictorWorkspace(ctx) {
|
|
|
529
544
|
async function driveProjectSession(ctx, prompt) {
|
|
530
545
|
const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
|
|
531
546
|
const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
|
|
547
|
+
const connect = sessionConnectFn(ctx, ws)
|
|
548
|
+
const canCreate = !!(sessionsSvc && typeof sessionsSvc.create === 'function')
|
|
549
|
+
if (!(connect || canCreate) || !(sessionsSvc && typeof sessionsSvc.binding === 'function')) {
|
|
550
|
+
let uiWs = null
|
|
551
|
+
try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
|
|
552
|
+
const diag = { ws: !!ws, ses: !!sessionsSvc && typeof sessionsSvc.binding === 'function', uiws: !!uiWs, conn: !!connect, create: canCreate }
|
|
553
|
+
console.warn('[pictor] session services unavailable', diag)
|
|
554
|
+
try { window.__pictor_diag = diag } catch { /* ignore */ }
|
|
555
|
+
try { pictorDiag(ctx, 'drive:fail') } catch { /* ignore */ }
|
|
556
|
+
throw new Error(`会话服务不可用(workspaces) [ws:${diag.ws},ses:${diag.ses},uiws:${diag.uiws},conn:${diag.conn},create:${diag.create}]`)
|
|
557
|
+
}
|
|
532
558
|
const wid = await pictorWorkspace(ctx)
|
|
533
559
|
let sessionId
|
|
534
|
-
try {
|
|
560
|
+
try {
|
|
561
|
+
const created = connect ? await connect(wid) : await sessionsSvc.create({ workspaceId: wid })
|
|
562
|
+
sessionId = created && typeof created === 'object' && created.id ? created.id : created
|
|
563
|
+
}
|
|
535
564
|
catch (e) { throw new Error('创建会话失败:' + String((e && e.message) || e)) }
|
|
536
565
|
const bound = sessionsSvc && typeof sessionsSvc.binding === 'function' ? sessionsSvc.binding(sessionId) : null
|
|
537
566
|
const sess = bound && bound.session
|
|
538
567
|
if (!sess || typeof sess.prompt !== 'function') throw new Error('会话无 prompt 通道')
|
|
539
568
|
await sess.prompt([{ type: 'text', text: String(prompt || '') }], 'queue')
|
|
569
|
+
try { pictorDiag(ctx, 'drive:ok') } catch { /* ignore */ }
|
|
540
570
|
return sessionId
|
|
541
571
|
}
|
|
542
572
|
|
|
@@ -793,9 +823,17 @@ function ProposalsPanel({ stage, proposals, suggestedStyle, steps, selected, onT
|
|
|
793
823
|
const ratioFor = (id) => ratios[id] || '16:9'
|
|
794
824
|
const activePrompt = promptId ? proposals.find((x) => x.id === promptId) || null : null
|
|
795
825
|
|
|
826
|
+
function batchTargetIds() {
|
|
827
|
+
return selected.length ? selected : proposals.map((p) => p.id)
|
|
828
|
+
}
|
|
796
829
|
function pickOption(name) {
|
|
797
830
|
const cur = pick
|
|
798
831
|
if (!cur) return
|
|
832
|
+
if (cur.batch) {
|
|
833
|
+
batchTargetIds().forEach((id) => setSty(id, name))
|
|
834
|
+
setPick(null)
|
|
835
|
+
return
|
|
836
|
+
}
|
|
799
837
|
if (cur.kind === 'layout') setLay(cur.id, name)
|
|
800
838
|
else setSty(cur.id, name)
|
|
801
839
|
setPick(null)
|
|
@@ -803,6 +841,11 @@ function ProposalsPanel({ stage, proposals, suggestedStyle, steps, selected, onT
|
|
|
803
841
|
function resetCurrent() {
|
|
804
842
|
const cur = pick
|
|
805
843
|
if (!cur) return
|
|
844
|
+
if (cur.batch) {
|
|
845
|
+
batchTargetIds().forEach((id) => setSty(id, undefined))
|
|
846
|
+
setPick(null)
|
|
847
|
+
return
|
|
848
|
+
}
|
|
806
849
|
if (cur.kind === 'layout') setLay(cur.id, undefined)
|
|
807
850
|
else setSty(cur.id, undefined)
|
|
808
851
|
setPick(null)
|
|
@@ -859,34 +902,60 @@ function ProposalsPanel({ stage, proposals, suggestedStyle, steps, selected, onT
|
|
|
859
902
|
: null
|
|
860
903
|
|
|
861
904
|
const chooserModal = pick ? (function chooser() {
|
|
862
|
-
const p = proposals.find((x) => x.id === pick.id)
|
|
863
|
-
if (!p) return null
|
|
864
905
|
const kind = pick.kind
|
|
906
|
+
const batch = Boolean(pick.batch)
|
|
907
|
+
const p = batch ? null : proposals.find((x) => x.id === pick.id)
|
|
908
|
+
if (!batch && !p) return null
|
|
865
909
|
const list = kind === 'layout'
|
|
866
|
-
? (layouts && layouts.length ? layouts : [p.suggested_layout])
|
|
867
|
-
: (styles && styles.length ? styles : [p.suggested_style || suggestedStyle])
|
|
868
|
-
const base = kind === 'layout' ? p.suggested_layout : (p.suggested_style || suggestedStyle)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
910
|
+
? (layouts && layouts.length ? layouts : (p ? [p.suggested_layout] : []))
|
|
911
|
+
: (styles && styles.length ? styles : (p ? [p.suggested_style || suggestedStyle] : (suggestedStyle ? [suggestedStyle] : [])))
|
|
912
|
+
const base = batch ? '' : (kind === 'layout' ? p.suggested_layout : (p.suggested_style || suggestedStyle))
|
|
913
|
+
// batch:高亮所有目标方案共享的同一风格(若有),否则不高亮
|
|
914
|
+
let current
|
|
915
|
+
if (batch) {
|
|
916
|
+
const ids = batchTargetIds()
|
|
917
|
+
const eff = ids.map((id) => {
|
|
918
|
+
const pp = proposals.find((x) => x.id === id)
|
|
919
|
+
return (pp ? (sty[id] || pp.suggested_style || suggestedStyle || '') : (sty[id] || ''))
|
|
920
|
+
})
|
|
921
|
+
current = eff.length && eff.every((s) => s === eff[0]) ? eff[0] : ''
|
|
922
|
+
} else {
|
|
923
|
+
current = kind === 'layout' ? (lay[pick.id] || p.suggested_layout) : (sty[pick.id] || p.suggested_style || suggestedStyle)
|
|
924
|
+
}
|
|
925
|
+
const changed = batch
|
|
926
|
+
? ids_haveAnyOverrideStyle()
|
|
927
|
+
: (kind === 'layout' ? Boolean(lay[pick.id] && lay[pick.id] !== base) : Boolean(sty[pick.id] && sty[pick.id] !== base))
|
|
928
|
+
function ids_haveAnyOverrideStyle() {
|
|
929
|
+
return batchTargetIds().some((id) => Boolean(sty[id]))
|
|
930
|
+
}
|
|
873
931
|
const items = list.map((n) => h('div', { key: n, className: 'pt-preview-item' + (n === current ? ' sel' : ''), onClick: () => pickOption(n) },
|
|
874
|
-
n === base ? h('span', { className: 'ai-chip' }, t('aiSuggestion')) : null,
|
|
932
|
+
(!batch && n === base) ? h('span', { className: 'ai-chip' }, t('aiSuggestion')) : null,
|
|
875
933
|
h('img', { src: '/pictor/preview/' + (kind === 'layout' ? 'layouts' : 'styles') + '/' + encodeURIComponent(n) + '.webp', alt: kind === 'layout' ? layoutLabel(n) : n }),
|
|
876
934
|
h('div', { className: 'nm' }, kind === 'layout' ? layoutLabel(n) : n)))
|
|
935
|
+
const title = batch ? t('batchStyleLabel') : (kind === 'layout' ? t('chooseLayout') : t('chooseStyle'))
|
|
936
|
+
const batchSub = batch ? h('p', { className: 'pt-meta', style: { margin: '0 0 10px' }, key: 'sub' },
|
|
937
|
+
t('batchStyleHint') + ' · ' + t('selectCount', { s: selected.length, t: proposals.length })) : null
|
|
877
938
|
return h('div', { className: 'pt-modal-backdrop', onClick: () => setPick(null) },
|
|
878
939
|
h('div', { className: 'pt-modal pt-modal-wide', onClick: (e) => e.stopPropagation() },
|
|
879
940
|
h('div', { className: 'pt-modal-head' },
|
|
880
|
-
h('h3', { className: 'pt-option-title', style: { margin: 0 } },
|
|
941
|
+
h('h3', { className: 'pt-option-title', style: { margin: 0 } }, title),
|
|
881
942
|
h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
882
943
|
changed ? h('button', { className: 'pt-btn', style: { padding: '6px 12px', fontSize: 13 }, onClick: resetCurrent }, t('resetToAi')) : null,
|
|
883
944
|
h('button', { className: 'pt-modal-close', onClick: () => setPick(null), title: t('close'), 'aria-label': t('close') }, '×'))),
|
|
884
|
-
h('div', { className: 'pt-modal-body' }, h('div', { className: 'pt-preview-grid' }, items))))
|
|
945
|
+
h('div', { className: 'pt-modal-body' }, batchSub, h('div', { className: 'pt-preview-grid' }, items))))
|
|
885
946
|
})() : null
|
|
886
947
|
|
|
948
|
+
const styleList = (styles && styles.length ? styles : (suggestedStyle ? [suggestedStyle] : []))
|
|
949
|
+
const batchStyleBtn = styleList.length ? h('button', {
|
|
950
|
+
className: 'pt-btn', style: { padding: '4px 10px', fontSize: 13 }, disabled: proposals.length === 0,
|
|
951
|
+
onClick: () => setPick({ id: null, kind: 'style', batch: true }),
|
|
952
|
+
title: t('batchStyleHint'),
|
|
953
|
+
}, t('batchStyleLabel')) : null
|
|
954
|
+
|
|
887
955
|
const actionRow = h('div', { className: 'pt-card-row', style: { marginTop: 18 } },
|
|
888
|
-
h('div', { style: { display: 'flex', gap: 10, alignItems: 'center' } },
|
|
956
|
+
h('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
|
|
889
957
|
h('button', { className: 'pt-btn', onClick: onToggleAll }, allSelected ? t('deselectAll') : t('selectAll')),
|
|
958
|
+
batchStyleBtn,
|
|
890
959
|
h('span', { className: 'pt-meta' }, t('selectCount', { s: selected.length, t: proposals.length }))),
|
|
891
960
|
hasImageKey === false
|
|
892
961
|
? h('p', { className: 'pt-warn', style: { margin: 0 } }, t('imgKeyHint'))
|
|
@@ -904,7 +973,17 @@ function fileSlug(s) {
|
|
|
904
973
|
return String(s || '').toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'infographic'
|
|
905
974
|
}
|
|
906
975
|
|
|
907
|
-
|
|
976
|
+
// 结果图直出走同源 HTTP(data URL 在新版 DSH Desktop WebView 可能被拦),
|
|
977
|
+
// 失败时回退到 RPC 返回的 data URL。
|
|
978
|
+
const imgUrlFor = (projectId, name) => {
|
|
979
|
+
const base = '/pictor/asset/project/' + encodeURIComponent(projectId) + '/' + encodeURIComponent(name)
|
|
980
|
+
try {
|
|
981
|
+
const tok = new URLSearchParams(window.location.search || '').get('token')
|
|
982
|
+
return tok ? base + '?token=' + encodeURIComponent(tok) : base
|
|
983
|
+
} catch { return base }
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function ResultsPanel({ projectId, outputs, images, renderErrors, proposals, lay, renderBatches }) {
|
|
908
987
|
const [big, setBig] = React.useState(null)
|
|
909
988
|
const [collapsed, setCollapsed] = React.useState({})
|
|
910
989
|
if (!outputs.length && !renderErrors.length) {
|
|
@@ -926,7 +1005,7 @@ function ResultsPanel({ outputs, images, renderErrors, proposals, lay, renderBat
|
|
|
926
1005
|
h('div', null, h('h3', { className: 'pt-option-title', style: { margin: 0 } }, bigInfo.title)),
|
|
927
1006
|
h('button', { className: 'pt-modal-close', onClick: () => setBig(null), title: t('close'), 'aria-label': t('close') }, '×')),
|
|
928
1007
|
h('div', { className: 'pt-modal-body', style: { textAlign: 'center' } },
|
|
929
|
-
h('img', { src:
|
|
1008
|
+
h('img', { src: imgUrlFor(projectId, big), alt: bigInfo.title, style: { maxWidth: '100%', maxHeight: '70vh', borderRadius: 10 }, onError: (e) => { if (e && e.currentTarget) e.currentTarget.src = images[big] } }),
|
|
930
1009
|
bigInfo.actualLayout ? h('p', { className: 'pt-meta', style: { marginTop: 10 } }, t('layoutLabel') + ': ' + layoutLabel(bigInfo.actualLayout)) : null)))
|
|
931
1010
|
: null
|
|
932
1011
|
|
|
@@ -950,7 +1029,7 @@ function ResultsPanel({ outputs, images, renderErrors, proposals, lay, renderBat
|
|
|
950
1029
|
const dlName = info.prop ? (fileSlug(info.prop.title || layoutLabel(info.actualLayout || info.prop.suggested_layout)) + '-' + info.pid + '.png') : name
|
|
951
1030
|
return h('figure', { key: name, className: 'pt-img-card' },
|
|
952
1031
|
images[name]
|
|
953
|
-
? h('img', { className: 'pt-img', src:
|
|
1032
|
+
? h('img', { className: 'pt-img', src: imgUrlFor(projectId, name), alt: info.title, style: { cursor: 'zoom-in' }, onClick: () => setBig(name), onError: (e) => { if (e && e.currentTarget) e.currentTarget.src = images[name] } })
|
|
954
1033
|
: h('div', { className: 'thumb', style: { width: '100%', height: 140 } }),
|
|
955
1034
|
h('figcaption', null,
|
|
956
1035
|
h('div', { className: 'pt-fig-title' }, info.title),
|
|
@@ -1364,7 +1443,7 @@ function DetailPane(props) {
|
|
|
1364
1443
|
ratios, setRatio, onRender: renderSelected, renderBusy,
|
|
1365
1444
|
hasImageKey: Boolean(imageCfg && imageCfg.hasKey), running: Boolean(data.running),
|
|
1366
1445
|
renderState: data.renderState, renderRunning: Boolean(data.renderRunning) }),
|
|
1367
|
-
render: h(ResultsPanel, { outputs: data.outputs, images, renderErrors, proposals, lay: layOv, renderBatches: data.renderBatches }),
|
|
1446
|
+
render: h(ResultsPanel, { projectId, outputs: data.outputs, images, renderErrors, proposals, lay: layOv, renderBatches: data.renderBatches }),
|
|
1368
1447
|
}[step]
|
|
1369
1448
|
|
|
1370
1449
|
return h('div', null,
|
|
@@ -1629,7 +1708,30 @@ function selectedLabel(ids) {
|
|
|
1629
1708
|
return ids.join('、')
|
|
1630
1709
|
}
|
|
1631
1710
|
|
|
1711
|
+
// 最佳努力诊断:上报此 ctx 上工作区/会话服务的暴露情况(DSH Desktop 0.7.2
|
|
1712
|
+
// 调整了客户端 API),host 落盘 ~/.pictor/diag.jsonl,无需 DevTools 即可排查。
|
|
1713
|
+
function pictorDiag(ctx, phase) {
|
|
1714
|
+
let ws = null, ses = null, ui = null
|
|
1715
|
+
try { ws = ctx && (ctx.workspaces || (ctx.get && ctx.get('workspaces'))) } catch { /* ignore */ }
|
|
1716
|
+
try { ses = ctx && (ctx.sessions || (ctx.get && ctx.get('sessions'))) } catch { /* ignore */ }
|
|
1717
|
+
try { ui = ctx && (ctx.uiWorkspace || (ctx.get && ctx.get('uiWorkspace'))) } catch { /* ignore */ }
|
|
1718
|
+
const body = {
|
|
1719
|
+
phase,
|
|
1720
|
+
ws: !!ws, ses: !!ses, ui: !!ui,
|
|
1721
|
+
wsMethods: ws ? Object.keys(ws).slice(0, 40) : null,
|
|
1722
|
+
sesMethods: ses ? Object.keys(ses).slice(0, 40) : null,
|
|
1723
|
+
uiMethods: ui ? Object.keys(ui).slice(0, 40) : null,
|
|
1724
|
+
connectWs: !!(ws && typeof ws.connectWorkspace === 'function'),
|
|
1725
|
+
connectUi: !!(ui && typeof ui.connectWorkspace === 'function'),
|
|
1726
|
+
bind: !!(ses && typeof ses.binding === 'function'),
|
|
1727
|
+
href: typeof location !== 'undefined' ? location.href.slice(0, 80) : '',
|
|
1728
|
+
}
|
|
1729
|
+
try { fetch('/pictor/diag', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }).catch(() => {}) } catch { /* ignore */ }
|
|
1730
|
+
console.warn('[pictor] diag', body)
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1632
1733
|
function apply(ctx) {
|
|
1734
|
+
try { pictorDiag(ctx, 'apply') } catch { /* ignore */ }
|
|
1633
1735
|
console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx && ctx.slots) })
|
|
1634
1736
|
if (typeof document !== 'undefined') {
|
|
1635
1737
|
const styleId = 'dsh-pictor-style'
|
package/lib/image-providers.js
CHANGED
|
@@ -124,7 +124,7 @@ class DashScopeProvider {
|
|
|
124
124
|
const base = this.cfg.baseUrl || "https://dashscope.aliyuncs.com/api/v1";
|
|
125
125
|
const size = DASHSCOPE_SIZE_MAP[aspectRatio] || "1024*1024";
|
|
126
126
|
const url = base.replace(/\/$/, "") + "/services/aigc/multimodal-generation/generation";
|
|
127
|
-
const parameters = { size,
|
|
127
|
+
const parameters = { size, watermark: false };
|
|
128
128
|
const body = {
|
|
129
129
|
model,
|
|
130
130
|
input: { messages: [{ role: "user", content: [{ type: "text", text: prompt }] }] },
|
package/lib/index.js
CHANGED
|
@@ -702,6 +702,52 @@ function apply(ctx, config = {}) {
|
|
|
702
702
|
} });
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
|
+
const PREFIX = "/pictor/asset/project";
|
|
706
|
+
wsA.register({ kind: "prefix", path: PREFIX, handler: (req, res) => {
|
|
707
|
+
try {
|
|
708
|
+
const pathname = new URL(req.url || "/", "http://x").pathname;
|
|
709
|
+
const rest = pathname.slice(PREFIX.length + 1);
|
|
710
|
+
const slash = rest.indexOf("/");
|
|
711
|
+
if (slash <= 0) {
|
|
712
|
+
res.writeHead(404);
|
|
713
|
+
res.end("not found");
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const id = rest.slice(0, slash);
|
|
717
|
+
const file = rest.slice(slash + 1);
|
|
718
|
+
if (id.includes("..") || /[\\/]/.test(id) || !/^[0-9a-zA-Z_.-]+$/.test(file) || !file.endsWith(".png")) {
|
|
719
|
+
res.writeHead(400);
|
|
720
|
+
res.end("bad request");
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const buf = readFileSync(join(projectDir(id), "output", file));
|
|
724
|
+
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=300" });
|
|
725
|
+
res.end(buf);
|
|
726
|
+
} catch {
|
|
727
|
+
res.writeHead(404);
|
|
728
|
+
res.end("not found");
|
|
729
|
+
}
|
|
730
|
+
} });
|
|
731
|
+
wsA.register({ kind: "exact", path: "/pictor/diag", handler: (req, res) => {
|
|
732
|
+
let body = "";
|
|
733
|
+
try {
|
|
734
|
+
req.on("data", (c) => {
|
|
735
|
+
body += c;
|
|
736
|
+
});
|
|
737
|
+
req.on("end", () => {
|
|
738
|
+
try {
|
|
739
|
+
mkdirSync(base, { recursive: true });
|
|
740
|
+
appendFileSync(join(base, "diag.jsonl"), JSON.stringify({ t: Date.now(), ...JSON.parse(body || "{}") }) + "\n");
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
res.writeHead(204);
|
|
744
|
+
res.end();
|
|
745
|
+
});
|
|
746
|
+
} catch {
|
|
747
|
+
res.writeHead(204);
|
|
748
|
+
res.end();
|
|
749
|
+
}
|
|
750
|
+
} });
|
|
705
751
|
}
|
|
706
752
|
return { dataRoot: base };
|
|
707
753
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pictor",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pictor:文档转信息图工作台。每项目一个连贯 dsh 会话,GUI 为 shell.overlay 工作台(左栏项目、右栏三步:提取/方案/渲染),行为全部交给 DSH 智能运行时。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -46,12 +46,16 @@
|
|
|
46
46
|
"docs",
|
|
47
47
|
"README.md",
|
|
48
48
|
"LICENSE",
|
|
49
|
-
"assets"
|
|
49
|
+
"assets",
|
|
50
|
+
"NOTICE.md"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "node scripts/build.mjs",
|
|
53
54
|
"verify": "node scripts/build.mjs && node verify.mjs",
|
|
54
55
|
"verify:integration": "bash scripts/transport-smoke.sh",
|
|
56
|
+
"test:pack": "bash scripts/package-integrity.sh",
|
|
57
|
+
"test:install": "bash scripts/install-smoke.sh",
|
|
58
|
+
"hooks:install": "git config core.hooksPath .githooks",
|
|
55
59
|
"test:e2e": "playwright test --config e2e/playwright.config.ts --project chromium",
|
|
56
60
|
"verify:live": "playwright test --config e2e/playwright.config.ts --project live",
|
|
57
61
|
"prepare": "node scripts/build.mjs"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Create a professional infographic
|
|
1
|
+
Create a professional infographic. This prompt separates what to render from how to render it: the Content section is the subject matter, data, and labels to put in the infographic; the Layout and Style sections are visual specifications (structure and aesthetic — they describe the look, not the subject). Put the content in first, then apply the layout and style to present it.
|
|
2
2
|
|
|
3
3
|
## Image Specifications
|
|
4
4
|
|
|
@@ -8,6 +8,13 @@ Create a professional infographic following these specifications:
|
|
|
8
8
|
- **Aspect Ratio**: {{ASPECT_RATIO}}
|
|
9
9
|
- **Language**: {{LANGUAGE}}
|
|
10
10
|
|
|
11
|
+
## Content — the subject matter, data, and labels to render
|
|
12
|
+
|
|
13
|
+
{{CONTENT}}
|
|
14
|
+
|
|
15
|
+
Text labels (in {{LANGUAGE}}):
|
|
16
|
+
{{TEXT_LABELS}}
|
|
17
|
+
|
|
11
18
|
## Core Principles
|
|
12
19
|
|
|
13
20
|
- Follow the layout structure precisely for information architecture
|
|
@@ -26,19 +33,10 @@ Create a professional infographic following these specifications:
|
|
|
26
33
|
- Labels should be clear and appropriately sized
|
|
27
34
|
- Use the specified language for all text content
|
|
28
35
|
|
|
29
|
-
## Layout Guidelines
|
|
36
|
+
## Layout Guidelines — visual structure (how to arrange; not content)
|
|
30
37
|
|
|
31
38
|
{{LAYOUT_GUIDELINES}}
|
|
32
39
|
|
|
33
|
-
## Style Guidelines
|
|
34
|
-
|
|
35
|
-
{{STYLE_GUIDELINES}}
|
|
40
|
+
## Style Guidelines — visual aesthetic (how it looks; not subject matter)
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Generate the infographic based on the content below:
|
|
40
|
-
|
|
41
|
-
{{CONTENT}}
|
|
42
|
-
|
|
43
|
-
Text labels (in {{LANGUAGE}}):
|
|
44
|
-
{{TEXT_LABELS}}
|
|
42
|
+
{{STYLE_GUIDELINES}}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# bandung-circuit
|
|
2
2
|
|
|
3
|
-
Editorial illustration style
|
|
3
|
+
Editorial illustration style — a muted, paper-printed aesthetic. Cream stock, duotone restraint, thin-line hand-drawn icons, and typography-led hierarchy. Reads as an interior print spread, not a marketing poster.
|
|
4
4
|
|
|
5
5
|
## Color Palette
|
|
6
6
|
|
|
@@ -25,10 +25,10 @@ Editorial illustration style of the Bandung Circuit column — a muted, paper-pr
|
|
|
25
25
|
|
|
26
26
|
## Typography
|
|
27
27
|
|
|
28
|
-
- **Font family: Poppins** — used as the single typeface across the entire composition (headlines, body, labels, captions, numerals). Poppins is a geometric sans with humanist warmth, which suits
|
|
28
|
+
- **Font family: Poppins** — used as the single typeface across the entire composition (headlines, body, labels, captions, numerals). Poppins is a geometric sans with humanist warmth, which suits an editorial-but-warm register; do not pair it with a second family.
|
|
29
29
|
- Headlines: Poppins SemiBold or Bold — uppercase or title case acceptable, tracking slightly opened
|
|
30
30
|
- Body text and labels: Poppins Regular or Medium — comfortable reading weight
|
|
31
|
-
- All-caps short labels for block headers
|
|
31
|
+
- All-caps short labels for block headers — Poppins Medium/SemiBold, set in cream on solid duotone bar
|
|
32
32
|
- Sub-captions and explanatory phrases beneath block titles: Poppins Italic or Light
|
|
33
33
|
- Numerals match the body face (Poppins lining figures) — no oversized statement numerals (this style trusts the layout, not hero stats)
|
|
34
34
|
|
|
@@ -49,13 +49,9 @@ Editorial illustration style of the Bandung Circuit column — a muted, paper-pr
|
|
|
49
49
|
- Use filled / solid icons (Material-style) — icons are line-art only
|
|
50
50
|
- Introduce a fourth or fifth color — composition must feel like a duotone with a single accent
|
|
51
51
|
- Use glossy or "tech product" iconography — keep icons modest and editorial
|
|
52
|
-
- Use heavy textures, parchment overlays, ink splatter, or cross-hatching (that is `aged-academia`, not
|
|
52
|
+
- Use heavy textures, parchment overlays, ink splatter, or cross-hatching (that is `aged-academia`, not this style)
|
|
53
53
|
- Use bold red blocks or full-bleed red panels (that is `tricon-infographic`, the poster sibling — not this)
|
|
54
|
-
- Crowd the canvas —
|
|
55
|
-
|
|
56
|
-
## Best For
|
|
57
|
-
|
|
58
|
-
Editorial illustrations for left-publishing journal articles, conceptual frameworks, multi-pillar architectures, theoretical model diagrams, methodology overviews, Global South research publications, long-form essay infographics, in-text figures (not standalone posters)
|
|
54
|
+
- Crowd the canvas — these pages always breathe
|
|
59
55
|
|
|
60
56
|
## Recommended Pairings
|
|
61
57
|
|
|
@@ -67,16 +63,13 @@ Editorial illustrations for left-publishing journal articles, conceptual framewo
|
|
|
67
63
|
|
|
68
64
|
## Distinction from Sibling Styles
|
|
69
65
|
|
|
70
|
-
- **vs `tricon-infographic`**: That style is
|
|
71
|
-
- **vs `aged-academia`**: That style is sepia parchment with cross-hatched scientific illustration.
|
|
72
|
-
- **vs `mckinsey-report`**: That style is white-background institutional consulting with serif/sans dual typography.
|
|
66
|
+
- **vs `tricon-infographic`**: That style is a poster voice — bold red on white, authoritative, full-bleed. `bandung-circuit` is an interior/essay voice — cream stock, duotone, quiet.
|
|
67
|
+
- **vs `aged-academia`**: That style is sepia parchment with cross-hatched scientific illustration. `bandung-circuit` is contemporary editorial print, not historical specimen plate.
|
|
68
|
+
- **vs `mckinsey-report`**: That style is white-background institutional consulting with serif/sans dual typography. `bandung-circuit` is a warm-paper aesthetic with a single sans family.
|
|
73
69
|
|
|
74
70
|
## Register
|
|
75
71
|
|
|
76
72
|
- formal-casual: formal but warm
|
|
77
73
|
- abstract-representational: slightly abstract
|
|
78
74
|
- bright-dark: bright (warm-bright, not white-bright)
|
|
79
|
-
- digital-handcrafted: leans handcrafted (digital execution with hand-drawn iconography)
|
|
80
|
-
- cultural-anchor: left publishing-house interior page design (Tricontinental, Verso, *New Left Review*, *Monthly Review*) — editorial register of the international left intellectual tradition
|
|
81
|
-
- best-for: in-text editorial illustrations for journal articles, conceptual diagrams in long-form essays, Global South research publications
|
|
82
|
-
- avoid-for: marketing posters, product UI, children's content, entertainment, corporate consulting decks, social-media graphics requiring stopping power
|
|
75
|
+
- digital-handcrafted: leans handcrafted (digital execution with hand-drawn iconography)
|