@aiquants/enrichment-react 0.10.0 → 0.11.0
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 +342 -56
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +252 -89
- package/dist/index.d.ts +252 -89
- package/dist/index.js +3 -3
- package/dist/work-copies.cjs +1 -0
- package/dist/work-copies.d.cts +4 -0
- package/dist/work-copies.d.ts +4 -0
- package/dist/work-copies.js +1 -0
- package/package.json +18 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aiquants/enrichment-react
|
|
2
2
|
|
|
3
|
-
React UI components for storage metadata AI enrichment: the ingest console, the pipeline flow viewer, the document change history inspector and the recent changes card, the search and model-comparison workbenches, the strategy board, the embedding variant board, and the status, health, storage, and ontology cards.
|
|
3
|
+
React UI components for storage metadata AI enrichment: the ingest console, the pipeline flow viewer and the run flow viewer, the document change history inspector and the recent changes card, the search and model-comparison workbenches, the strategy board, the embedding variant board, and the status, health, storage, and ontology cards.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
@@ -19,8 +19,9 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
19
19
|
|
|
20
20
|
- **Host owns routes and lists.** No component names an application route or ships a domain list; the host passes `onSearch`, `onCompare`, `domainOptions`, and the like.
|
|
21
21
|
- **DOM hooks are `data-*` / `role` / element only.** Class names are styling and change freely.
|
|
22
|
-
- **Rendered inside the console root.** `InfrastructureHealthMonitor`, `EnrichmentStrategyBoard`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `ProcessingObjectBrowser`, `QdrantStorageCard`, `SyncStatusMonitor`, `OntologyStatusCard`, `DocumentIngestConsole`, `EmbeddingVariantBoard`, the search tab's `DegradedSearchBanner`, `MetadataSearchWorkbench` and `ModelComparisonWorkbench`,
|
|
23
|
-
|
|
22
|
+
- **Rendered inside the console root.** `InfrastructureHealthMonitor`, `EnrichmentStrategyBoard`, `PipelineFlowViewer`, `PipelineRunFlowViewer`, `CustomFlowList`, `FlowEditor`, `BindingRulesEditor`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `ProcessingObjectBrowser`, `QdrantStorageCard`, `SyncStatusMonitor`, `OntologyStatusCard`, `DocumentIngestConsole`, `ChildRunList`, `IngestionQueueHealthCard`, `IngestionJobBoard`, `EmbeddingVariantBoard`, `RetrievalEvaluationWorkbench`, the search tab's `DegradedSearchBanner`, `MetadataSearchWorkbench` and `ModelComparisonWorkbench`,
|
|
23
|
+
and the instant slots `RecordedInstant` and `FinishedInstant` read `@aiquants/console-ui`'s `ConsoleUiRoot` and throw outside it.
|
|
24
|
+
No exported component renders outside the root: `tests/HeadingOutline.spec.tsx` lists every one of them among the components that refuse another root (`refusesOtherRoots`).
|
|
24
25
|
Every component that reads the root checks it before drawing anything (the root requirement below), so none of them reads the root, draws the root's label or writes in the root's zone under a root it was not written for.
|
|
25
26
|
This package ships no stylesheet and paints nothing itself (no class and no inline style; `tests/migratedSources.spec.ts` checks every source), so the host loads `@aiquants/console-ui/styles/console-ui.css` once, with its `cui` cascade layer ranked above the host's reset, as `@aiquants/console-ui`'s README describes under "Loading the stylesheet" (for a Tailwind v4 host, `@import` it in the CSS entry after the layer statement `@layer theme, base, components, utilities;`).
|
|
26
27
|
Without it the root's `--cui-*` token variables are unset, every `Measured` value (a count, a byte size, an instant) loses its tabular figures and a text that moves whole wraps like plain text,
|
|
@@ -37,20 +38,25 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
37
38
|
</ConsoleUiRoot>
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
- **Unmeasured is not zero.** A figure the source could not measure renders as the root's unmeasured label (`未測定` under `locale="ja"`), never as `0`. In `InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `QdrantStorageCard`, `SyncStatusMonitor` and `OntologyStatusCard`, every value slot that holds one is `@aiquants/console-ui`'s `Measured` (a count, or a formatted text with its `keep`), so `data-measured="false"` marks every unmeasured value slot there.
|
|
41
|
+
- **Unmeasured is not zero.** A figure the source could not measure renders as the root's unmeasured label (`未測定` under `locale="ja"`), never as `0`. In `InfrastructureHealthMonitor`, `PipelineFlowViewer`, `PipelineRunFlowViewer`, `DocumentHistoryInspector`, `QdrantStorageCard`, `SyncStatusMonitor` and `OntologyStatusCard`, every value slot that holds one is `@aiquants/console-ui`'s `Measured` (a count, or a formatted text with its `keep`), so `data-measured="false"` marks every unmeasured value slot there.
|
|
41
42
|
A run's step report in the history inspector is the one place an unmeasured figure has no slot of its own: a report field that was not measured is named in the step's `未測定:` line instead (see `DocumentHistoryInspector`), and only a page pair with one half measured keeps both `Measured` slots.
|
|
42
43
|
The other components keep their own words for a missing value: `DocumentIngestConsole` writes a missing revision as `N/A` (it reads the root for its instants and its byte format only), `MetadataSearchWorkbench` a document absent from one of the ranked lists as `ベクトルの列に無い` / `語句の列に無い` / `グラフの列に無い` and an empty column as `無し`.
|
|
43
44
|
A count drawn as a value — the Qdrant totals, segment count, collection count, each collection's points and each named vector's dimensions (`次元`), the sync monitor's tiles, the ontology card's schema and vocabulary counts, each class's count and the counted thresholds (`回`), the health probe's counted details, every probe latency in ms (応答時間 in both `InfrastructureHealthMonitor` and `QdrantStorageCard`), the flow metric's sample, succeeded, failed and skipped counts,
|
|
45
|
+
the run timeline's group counts (`run-step-group-count-value-<group key>`, `run-step-group-statuses-<group key>-<status>`, `run-step-group-remaining-value-<group key>`), the counts in a run node's inputs and outputs and in their preview trees,
|
|
44
46
|
and every count the core record types as a measured count (the step duration and the report's `duration_ms` in ms, characters, pages, chunks, points, graph nodes, and the flow's p50 / p95 latencies in ms) — is `Measured`, written in the root's `formatLocale` with tabular figures and `data-measured="true"`.
|
|
45
47
|
The search workbenches write their durations inside a sentence, grouped by the root's count rule (`12,345 ms`): `MetadataSearchWorkbench`'s search time in the results card's description and `ModelComparisonWorkbench`'s execution time in each column heading; a duration that is not a non-negative safe integer throws `RangeError` naming it.
|
|
46
48
|
A measured value that is not a count is written by its own format into the same `Measured` as a text (`text` with `keep`), in tabular figures with `data-measured="true"`, and when it was not measured the same `Measured` writes the label (`data-measured="false"`); both states carry the text's `data-cui-keep`.
|
|
47
|
-
A value that reads as one unit — a byte size (`1.0 MiB`), an instant, a probe's port or boolean, the diff's similarity (`87.5%`), the ontology card's schema version and phase and its confidence threshold — is a text that moves whole (`keep="whole"`): when it does not fit on the line it starts on, it moves to the next line whole, so a number does not part from its unit or a date from its time, and it breaks inside only in a column narrower than itself, so it never overflows the column.
|
|
49
|
+
A value that reads as one unit — a byte size (`1.0 MiB`), an instant, a probe's port or boolean, the diff's similarity (`87.5%`), the ontology card's schema version and phase and its confidence threshold, a run node's elapsed time (`formatElapsed`, `run-flow-step-elapsed-value`) — is a text that moves whole (`keep="whole"`): when it does not fit on the line it starts on, it moves to the next line whole, so a number does not part from its unit or a date from its time, and it breaks inside only in a column narrower than itself, so it never overflows the column.
|
|
48
50
|
A tile's value is the one exception: `@aiquants/console-ui`'s `MetricTile` draws its text (the Qdrant card's RAM and disk, the sync monitor's completion rate) alone on its line with `keep="wrap"`, in a tile at least as wide as the widest value it writes (`tileMinInlinePx`), so the value stays on its line.
|
|
49
51
|
A text — a probe's text, a service's version, the report's `prompt_version` and its other texts — wraps with its container (`keep="wrap"`).
|
|
50
|
-
Every recorded instant, in each of the components that write one (`InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `SyncStatusMonitor`, `DocumentIngestConsole`, `ChildRunList`, `IngestionQueueHealthCard`, `IngestionJobBoard`), is written by the exported `RecordedInstant` (or `FinishedInstant`, for a finish time that may be open) into a `Measured` text that moves whole (`data-cui-keep="whole"`) under a `-value` hook:
|
|
51
|
-
`health-checked-at-value`, `flow-metric-last-started-at-value`, `run-
|
|
52
|
+
Every recorded instant, in each of the components that write one under a `-value` hook (`InfrastructureHealthMonitor`, `PipelineFlowViewer`, `PipelineRunFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `SyncStatusMonitor`, `DocumentIngestConsole`, `MetadataSearchWorkbench`, `ChildRunList`, `IngestionQueueHealthCard`, `IngestionJobBoard`), is written by the exported `RecordedInstant` (or `FinishedInstant`, for a finish time that may be open) into a `Measured` text that moves whole (`data-cui-keep="whole"`) under a `-value` hook:
|
|
53
|
+
`health-checked-at-value`, `flow-metric-last-started-at-value`, `run-flow-started-at-value`, `run-flow-finished-at-value`, `run-flow-step-started-at-value` and `run-flow-step-finished-at-value` (`PipelineRunFlowViewer`), `history-run-started-at-value`, `history-run-finished-at-value`, `history-state-settled-at-value`, `history-pin-settled-at-value`, `history-document-last-change-value`,
|
|
54
|
+
a timeline row's `history-row-<item key>-time-value` (the key `run:<id>` or `state:<id>`), a restricted mark's `history-<key>-settled-at-value` or `history-<key>-started-at-value` / `history-<key>-finished-at-value` and the earlier erasure's `history-erased-before-at-value` (`DocumentHistoryInspector`),
|
|
55
|
+
`change-feed-time-value-<position>` (`DocumentChangeFeed`), `failed-item-time-value-${id}` and `failed-item-restricted-time-value-<position>` (`SyncStatusMonitor`), `ingest-last-run-finished-at-value-${id}`, `ingest-created-at-value-${id}` and `recent-item-restricted-created-at-value-<position>` (`DocumentIngestConsole`),
|
|
56
|
+
`search-hit-mtime-value` (`MetadataSearchWorkbench`), `<testId>-started-<id>-value` / `<testId>-finished-<id>-value` (`ChildRunList`) and `ingestion-queue-measured-at-value`, `ingestion-queue-oldest-since-value` and `ingestion-queue-holder-heartbeat-value-<owner>` (`IngestionQueueHealthCard`),
|
|
52
57
|
and `<section>-created-<jobId>-value`, `<section>-created-mark-<position>-value` and `<section>-next-attempt-<jobId>-value` (`IngestionJobBoard`), so a column of instants lines up digit by digit.
|
|
53
|
-
|
|
58
|
+
`ProcessingObjectBrowser` (`processing-object-created-<position>`, `processing-object-updated-<position>`) and the retrieval evaluation panels (`evaluation-set-created-at`, `evaluation-run-created-at-<run uid>`) write theirs with `RecordedInstant` too, under hooks without the `-value` suffix.
|
|
59
|
+
A state shown in place of an instant is a word, not a slot: `未完了` for a run or a step that has not finished (`FinishedInstant` writes it in `history-run-summary-finished-at`, `run-flow-summary-finished-at`, `run-flow-step-facts-finished-at`, `ingest-last-run-finished-at-${id}` and `<testId>-finished-<id>`, and a running node's elapsed time is the same word, `UNFINISHED_LABEL`, in `run-flow-step-facts-elapsed`) and `記録なし` for a failure the record holds no time for (`failed-item-time-${id}`).
|
|
54
60
|
A text with nothing to read is refused like a broken count, with a `RangeError` that names the slot's hook: drawn, it would be an empty slot that claims a measurement. Pass `null` for a value that was not measured.
|
|
55
61
|
Nothing to read is `@aiquants/console-ui`'s one rule `isReadableText` (from `@aiquants/console-ui/core`): a text with no character outside Unicode's white space, control characters, format characters and default-ignorable code points. That holds the white space of both languages the records are written in, JavaScript's and Python's `\s` (U+3000 the ideographic space, U+00A0, U+FEFF, U+001C–U+001F and U+0085 among them), and the invisible characters neither counts as space (U+200B, U+00AD, U+2060, the Hangul fillers U+3164 and U+FFA0).
|
|
56
62
|
The core record schemas still accept such a text, so the refusal is a backstop: a host refuses the value where it reads it, with the same `isReadableText`.
|
|
@@ -69,7 +75,8 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
69
75
|
The three monitoring cards are drawn with `@aiquants/console-ui`'s parts only (`Card`, `CardSection`, `TileGrid` and `MetricTile`, `DataTable` or `SelectableList`, `DescriptionList`, `Pill`, `Notice`, `Text`), with no class, inline style or colour of their own; a tile grid's minimum tile width is measured from its tiles' words and widest values by `@aiquants/console-ui/core`'s `tileMinInlinePx` (every count with `countSampleText`, the largest seven-digit count in the root's format locale).
|
|
70
76
|
Every table column is sized by the same core (`fixedColumnPx` over what its cells hold, `FLEXIBLE_COLUMN_MIN_PX` for a flexible column): a column of recorded instants is measured with the instant `RecordedInstant` writes (`recordedInstantContent`), and a column of row IDs or revisions with the one sample of each (`identifierSamples.ts`: a 10-digit ID, a 5-digit revision; a longer one breaks inside its cell).
|
|
71
77
|
So are the strategy board (`Card`, `DataTable` with the domain as its row header and each stage's gate state as its column header's note, `Stack`, `Text`, `Switch`) and the health monitor (`Card`, one `CardSection` with one `DescriptionList` per service, `Pill`, `Text`, `Measured`, `Button`); the strategy board's stage columns are sized with `@aiquants/console-ui/core`'s `fixedColumnPx` over `CellContent` and its domain column with `FLEXIBLE_COLUMN_MIN_PX`.
|
|
72
|
-
So is the pipeline flow viewer (`Card`, `PanelTabs`, the figure system `Figure`, `FigureNode` and `FigureEdge`, a `CardSection` region holding an `h4` `CardSection` for the metrics, `DescriptionList`, `Measured`, `DataTable`, `Pill`, `Stack`, `Cluster`, `Text`, `Button`): its figure's geometry comes from `@aiquants/console-ui/core`'s figure formula and tokens (`flowGeometry`), and its gate list
|
|
78
|
+
So is the pipeline flow viewer (`Card`, `PanelTabs`, `Select`, the figure system `Figure`, `FigureNode` and `FigureEdge`, a `CardSection` region holding an `h4` `CardSection` for the metrics, `DescriptionList`, `Measured`, `DataTable`, `Pill`, `Notice`, `AlertMessage`, `Link`, `Stack`, `Cluster`, `Text`, `Button`): its figure's geometry comes from `@aiquants/console-ui/core`'s figure formula and tokens (`flowGeometry`), and its gate list and child flow list are the foundation's `List`.
|
|
79
|
+
So is the run flow viewer (`Card`, `CardSection` at levels 3 and 4, the same figure parts, `DescriptionList`, `Measured`, `Pill`, `Notice`, `AlertMessage`, `Link`, `Button`, `List`, `Stack`, `Cluster`, `Text`, a `SegmentedControl`, a `DataTable` and a `LoadMore` for a node's child records, and the step timeline's `Disclosure`, `Meter`, `CodeBlock` and `LoadMore`): its figure takes the flow viewer's geometry (`useFlowFigureLayout`).
|
|
73
80
|
So are the history inspector, its comparison view and its text diff (`Card`, `CardSection` at levels 3 and 4, `Stack`, `Cluster`, `DescriptionList`, `DataTable`, `MasterDetail`, `SelectableList`, `Pill`, `Text` with `space="preserve"` for the diff lines and JSON values, `InlineChange` for the diff's pieces); they write no `data-cui-*` attribute themselves, so every row-focus hook of their tables is the one `DataTable` draws.
|
|
74
81
|
- **One card-title scale.** Every card title (`h2`) is 16 px bold (`@aiquants/console-ui`'s `Card` title, the `title` type step) — the same scale `@aiquants/enrichment-react-router` and `@aiquants/enrichment-governance-react` use, so sibling cards on one tab read as one heading level. `tests/HeadingOutline.spec.tsx` checks every panel's `h2`.
|
|
75
82
|
- **Nothing is clipped without a way to read it, and nothing that reads as one unit breaks inside where the layout holds it.** Badges (`@aiquants/console-ui`'s `Pill`, the health badges among them) move to the next line whole instead of shrinking, and unbroken identifiers (URIs, versions, endpoints, a probe's error text, the run inspector's report texts) wrap anywhere inside their card instead of overflowing it or ending in an ellipsis.
|
|
@@ -159,16 +166,18 @@ The host owns the route and the transport: `onSearch` answers a `DocumentSearchA
|
|
|
159
166
|
|
|
160
167
|
- **Query card** (`search-query-card`, with `data-mode` and `data-running`): the query (`search-query`), the strategy (`search-mode`, a segmented control of the core's `SEARCH_MODES` worded by `SEARCH_MODE_LABELS`, `HYBRID` unless `initialParams` names another, its description in `search-mode-description`) and the page size (`search-limit`).
|
|
161
168
|
Only the controls the chosen strategy reads are drawn and sent: the similarity floor for `DENSE` (`search-threshold`, starting at the midpoint of its bounds, since no floor is the request's own default), the fusion (`search-fusion`, `rrf` or `linear` by `SEARCH_FUSION_LABELS`) and its `k` (`search-rrf-k`) for `HYBRID`, and the vector weight `α` (`search-alpha`) for a linear fusion. The numeric bounds and defaults come from `SEARCH_PARAM_LIMITS`.
|
|
169
|
+
Beside the fusion, `HYBRID` also draws who fuses the dense and sparse rankings and where (`search-dense-sparse-fusion`, the core's `DENSE_SPARSE_FUSIONS` worded by `DENSE_SPARSE_FUSION_LABELS`): `機関 (組の全体で融合・同点はチャンクの順)` (`engine`, the API's default `DEFAULT_DENSE_SPARSE_FUSION`, never sent) or `ストア (コレクションごとに融合・同点の順は要求ごと)` (`store`, sent as `dense_sparse_fusion`).
|
|
170
|
+
The engine merges each kind across the searched collections and fuses the two merged rankings once, deciding an exact tie by the chunk, so the same request always answers the same; the store fuses each collection's rankings itself, the collections are merged by rank, and exact ties fall anew on every request. The run trace names it on the fusion line (`融合: RRF (k=60) / 密と疎の融合: 機関 (組の全体で融合・同点はチャンクの順)`), the API's default when the search sent none.
|
|
162
171
|
For a strategy with a vector tier (`SPARSE`, `DENSE`, `HYBRID`; `MODE_TIERS`), the embedding variant set the vector tiers read (`search-variant`): `本番 (default)` first, then the active sets in key order, each named with its projected share (`variantSetOptionLabel`: `eg2-v1 (投影済み 70.0%)`, or `(対象の文書なし)` for a set whose rows count no document). Production is the API's default and is never sent; any other set travels as `variant_key`. A chosen set that leaves the active list reads as production again.
|
|
163
172
|
A set that holds less than every document says so in a note (`search-variant-partial`: `組 <key> はテナントの文書の 70.0% (7 / 10 件) を投影しています。…`), since a document it lacks cannot be found through its vector tiers. The run trace names the set (`変種の組: …`), and so does the results card's description (`· 組: …`).
|
|
164
173
|
- **Filters** behind the `詳しい条件` disclosure (`search-filters`): the confidentiality level (`search-confidentiality`, `全ての区分` or one of the core's `FILTERABLE_CONFIDENTIALITY_LEVELS` worded by `CONFIDENTIALITY_LEVEL_LABELS`, so `RESTRICTED` is never offered), the department id (`search-department`), the MIME type (`search-mime-type`, a contains match) and the tag (`search-tag`, an exact match). A filter is sent only when given, trimmed.
|
|
165
|
-
- **Developer presets** behind the `開発者のプリセット` disclosure (`search-presets`): each of `SEARCH_PRESETS` (`search-preset-${id}-apply`) sets the strategy and the page size, and the floor or the
|
|
174
|
+
- **Developer presets** behind the `開発者のプリセット` disclosure (`search-presets`): each of `SEARCH_PRESETS` (`search-preset-${id}-apply`) sets the strategy and the page size, and the floor, the fusion or who fuses the dense and sparse rankings only where the preset names them (`balanced-hybrid` names the engine); applying one is announced.
|
|
166
175
|
- **Checked before sending:** the query (1–500 characters after trimming), the department (empty, or a positive integer) and the text filters (at most 100 characters after trimming). Nothing is sent while a field is unacceptable, and each field says why only once a send has been attempted.
|
|
167
176
|
- **One search at a time:** the submit button (`search-submit`) is busy while a search runs and a second submit is not taken. A search the workbench leaves behind (unmounted) is aborted through its signal, and its late answer or failure is not drawn.
|
|
168
177
|
- **Results** (`search-results`, described `<n> 件 · <ms> ms · 品質: <quality>`): the run (`search-run`: a pill per tier, `search-tier-${tier}` with `data-ran`; the answering worker's breakers `search-breakers` by `BREAKER_STATE_LABELS`; the row scope `search-row-scope`; the column scope with the names of the columns it hid, `search-column-scope`) and a master–detail (336 px master, 544 px detail minimum).
|
|
169
178
|
The master is the documents (`search-hits`, rows `search-hit-row` with `data-rank`): the rank and the title, and the fused value as a meter (`search-hit-${rank}-fused`). The first row is selected when an answer arrives, and the number found is announced.
|
|
170
179
|
The detail (`search-hit-detail`) shows the selected document's values (`search-hit-scores`: the fused value as a meter; the vector value by `vectorScoreText`, a cosine as a percentage of 1 and BM25 or a mixed value as a plain number; the lexical and graph rank values), its fields (`search-hit-fields`, the update time in the root's zone under `search-hit-mtime-value`, the tags as pills), its graph relations (`search-hit-relations`) and its structured data (`search-hit-structured`, a scrolling code block).
|
|
171
|
-
- **Scopes:** a column the caller's scope hides reads `HIDDEN_FIELD_TEXT` (`非表示 (列のスコープ)`), and a document's relations are withheld while a row scope applies (their targets have not passed the row check).
|
|
180
|
+
- **Scopes:** a column the caller's scope hides reads `HIDDEN_FIELD_TEXT` (`非表示 (列のスコープ)`), and a document's relations are withheld while a row scope applies (their targets have not passed the row check) or the column scope hides the body (a heading path is part of the body and an entity is named from it); the detail and the graph trace name whichever reason holds.
|
|
172
181
|
- **Degraded answers** draw `DegradedSearchBanner` (`search-degraded`) between the card and the results.
|
|
173
182
|
- **Developer inspector** behind the `開発者の点検` disclosure (`search-inspector`): a segmented control (`search-inspector-view`) of the request as sent, the response, the run trace and the graph trace, drawn in a code block (`search-inspector-code`). Before any search every view says `まだ検索していません。`; after one, the request view keeps what was sent even when the controls move.
|
|
174
183
|
The copy button (`search-inspector-copy`) writes the shown view to the clipboard and announces it; a copy that fails says why (`search-inspector-copy-failure`).
|
|
@@ -212,54 +221,82 @@ Every value the source returned as `null` — a count, a threshold, a vocabulary
|
|
|
212
221
|
|
|
213
222
|
### 8. `PipelineFlowViewer`
|
|
214
223
|
|
|
215
|
-
The ingestion pipeline as the
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
- **
|
|
221
|
-
|
|
224
|
+
The ingestion pipeline as the flows the executor runs, each drawn at one of its versions, from the flow catalogue the API answers (`GET /ai/pipeline/flows`, `@aiquants/enrichment-core`'s `PipelineFlowCatalog`, required as `catalog`) and, for an older version, the shape the host reads (`PipelineFlowShapeEntry`). The flows, their versions, nodes and gates and the MIME rules live only in the Python core's flow catalogue and step registry; the viewer keeps no table of its own. What the answer does not carry is not drawn: no node has an endpoint, since the executor runs every step in process.
|
|
225
|
+
The card (`pipeline-flow-viewer`, `data-flow-key`, `data-flow-version`) is titled `取り込みパイプライン` (its `h2` is `pipeline-flow-viewer-heading`, `tabIndex={-1}`, the host's `headingRef`).
|
|
226
|
+
|
|
227
|
+
- **Flow tabs**: the console's `PanelTabs` (named `流れ`, `pipeline-flow`, tabs `pipeline-flow-tab-<key>`, panel `pipeline-flow-panel`), one tab per flow of the catalogue, labelled by its `title.ja`: first the document flows that name document kinds, ordered by their first kind in the document-kind vocabulary's order (not the catalogue's), then the document flows that name none, then the child flows, both in catalogue order. `defaultFlowKeyOf(catalog)` names the leftmost tab.
|
|
228
|
+
The ARIA tabs pattern: only the selected tab is in the Tab order; ArrowLeft / ArrowRight (wrapping), Home and End move focus and select, and other keys do nothing; every tab is a 44 px target. Choosing a tab calls `onSelectFlow(flowKey)` and nothing else.
|
|
229
|
+
- **Version**: a `Select` named `版` (`pipeline-flow-version`) over the selected flow's `versions`, newest first, the current one written `版 <v> (今の版)`, drawn even for a flow with one version; choosing one calls `onSelectVersion(version)`, `null` for the current one. The panel names what it draws as `流れ: <title> (<key> 版 <v>)` (`flow-identity`).
|
|
230
|
+
`figure` (`FlowVersionFigure`) carries what the host holds for that choice: `{ kind: "current" }` draws the catalogue's nodes, `{ kind: "version", shape }` the shape the host read for an older version, and `{ kind: "version_unread", version, failure }` draws no figure and no hint and looks up no node, only why (`pipeline-flow-version-unread`): `この版の図を読む権限がありません。` (a `Notice`) for `{ kind: "forbidden" }`,
|
|
231
|
+
or `この版の図を読めませんでした (HTTP <status>)。` (an `AlertMessage`; without a status, no parentheses) for `{ kind: "unavailable", status }` (`FlowShapeReadFailure`, the shape of `@aiquants/enrichment-react-router`'s `PipelineFlowCatalogFailure`).
|
|
232
|
+
A `version` shape of another flow, and a `version` or `version_unread` figure naming the current version or a version the flow's `versions` does not list, throws `RangeError` (the host gives the current version as `{ kind: "current" }`).
|
|
233
|
+
- **Figure** (`flow-svg`, inside `flow-figure` with its hint): `@aiquants/console-ui`'s figure system, a `group` `Figure` (`role="group"` named `<flow title> 版 <v> の図`, so its node buttons stay in the accessibility tree) drawn at its natural size inside the figure's horizontal scroll frame (`flow-svg-frame`, which takes no Tab stop), under the visible hint `flow-scroll-hint`, which is read before the figure and describes the tab panel: `図は実寸 (幅 <width> px) で描きます。表示幅に収まらないときは横にスクロールしてください。節の間は Tab のほか、矢印の鍵 (→・←・↓・↑) と Home・End でも移れます (移った節が見える位置まで動きます)。`
|
|
234
|
+
The figure is never scaled to fit (its CSS box is in rem, so it grows with the root font size as a picture). Nodes `flow-node-<id>` are the drawn shape's nodes in the executor's order, each a `FigureNode` box and a disclosure button for the detail panel (`role="button"` with `aria-expanded`, and `aria-controls` naming the panel while it is open): click, Enter or Space selects the node (`onSelectNode(id)`), or clears the selection when it is the selected node (`onSelectNode(null)`); Escape pressed on a node clears the selection (the figure's `onEscape`).
|
|
235
|
+
The figure's keys (`flowKeyboard.ts`) move the focus without selecting: → along a drawn edge out of the node, ← along one into it, ↓ / ↑ to the next / previous node of its layer, Home / End to the first / last node in the executor's order. The figure takes them only when pressed on a node with no modifier (Alt, Ctrl, Meta or Shift) and prevents their default action whether or not they lead anywhere, so neither the frame nor the page scrolls under them; any other press is left to the browser.
|
|
222
236
|
A node carries `data-node-id`, `data-step-key` (its step), `data-gate-state` = `open` / `closed-skip` / `closed-fail` / `unmeasured` and `data-gate-policy` = `none` / `skip` / `fail` / `mixed`, and the foundation's `data-selected`, `data-cui-tone` and `data-cui-outline`: its tone and outline follow the gate state (`GATE_STATE_PRESENTATION`: open `success`, closed and skipping `warning`, closed and failing `danger`, unmeasured a `dashed` `neutral`), and a selected node is drawn on `info-tint` with a line twice as wide.
|
|
223
|
-
It draws its title (`title.ja`, `flow-node
|
|
237
|
+
It draws its title (`title.ja`, `flow-node-<id>-name`), its phase (`flow-node-<id>-note-0`) and its gate line (`flow-node-<id>-note-1`) in `fg`, on its plate (`flow-node-<id>-plate`), and the foundation's focus ring (`flow-node-<id>-ring`, `focus`, 2 px wide and 2 px outside the node, shown only on `:focus-visible`).
|
|
224
238
|
- **Edges: the order, drawn as the transitive reduction.** A node's `depends_on` holds every edge the executor waits on, an edge another path already implies included (the projection waits for the body it also reaches through the enrichment).
|
|
225
|
-
The figure's job is to show the order, so it draws the transitive reduction (`drawnDependencies(
|
|
226
|
-
Each node is also described (`aria-describedby` → its `<desc>`, `flow-node
|
|
239
|
+
The figure's job is to show the order, so it draws the transitive reduction (`drawnDependencies(nodes)`): edges `flow-edge-<from>-<to>` (`FigureEdge`, `border-control`, weight 2, ending in the figure's arrowhead `flow-svg-arrow-normal`), one per drawn dependency.
|
|
240
|
+
Each node is also described (`aria-describedby` → its `<desc>`, `flow-node-<id>-description`) by what it waits for in the drawing — `依存: <titles joined by 、>`, or `依存なし (流れの最初に走る)` — so a screen reader can tell a branch and a join from a chain.
|
|
227
241
|
- **Gates as the answer gives them.** A node's `gates` carry the flow's override already, so a flow that overrides a gate (the image flow's `ocr_document` and the media flow's `analyze_media`, their only body source, `gate_policy`) is drawn with the meaning it has there; a gate's state is read from the tenant's `featureFlags` (a flag the host did not pass is unmeasured).
|
|
228
|
-
A node's gate line (`flow-node
|
|
242
|
+
A node's gate line (`flow-node-<id>-note-1`, also in its accessible name `<title> (<line>)`) is `gateCaption(state, policy)`: a closed state names its consequence (`門: 閉 (省略)` / `門: 閉 (失敗)`), and an open or unmeasured gated node adds its policy from `GATE_POLICY_LABELS` (e.g. `門: 開・閉じたら失敗`). These words (`GATE_POLICY_LABELS`, `GATE_STATE_PRESENTATION`, `gateCaption`, `PHASE_LABELS`, `StepGatePolicy`, `StepGateState`) live in `flowBoxVocabulary.ts`, and the entry point exports them.
|
|
229
243
|
Nothing else is drawn on a node: the search path's circuit breaker is neither read nor fed by the pipeline.
|
|
230
244
|
- **Detail panel** (`flow-step-detail`, `data-node-id`, `data-step-key`): a `CardSection` region named by the node's title (`h3`, `flow-step-detail-heading`) with the step's description under it (`flow-step-description`), the close button `flow-step-detail-close`, and a `DescriptionList` (`flow-step-definition`) of: the step and its version (`flow-step-key`, `<step> (版 <n>)`), the phase (`flow-step-phase`),
|
|
231
245
|
the capabilities the classification governs for the step (`flow-step-capabilities`: their keys in the step's declaration order joined by `・`, or `モデルへ送らない` for a step that sends nothing to a model),
|
|
232
|
-
the parameters in the step's words, one line each (`flow-step-parameters`, `flow-step-parameter
|
|
233
|
-
every edge as the executor keeps it — every node of its `depends_on` in order (`flow-step-depends-on`), the ones the figure does not draw named apart (`flow-step-indirect-dependencies`) — and every node whose `depends_on` names it (`flow-step-dependents`),
|
|
234
|
-
and its gates (the foundation's bulleted `List`, `flow-step-gate-list`, of `flow-gate
|
|
235
|
-
A
|
|
236
|
-
When
|
|
237
|
-
It closes with its close button and with Escape pressed inside it (the region's `onEscape`, the rule the history panel follows; Escape on a node clears the selection and leaves focus on that node), and either returns focus to that opener while it is still in the document; with no opener
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
the parameters in the step's words, one line each (`flow-step-parameters`, `flow-step-parameter-<key>`: `<title>: <label> (<value>)` for a closed field, `<title>: <value>` for an open one, a string value as itself and any other as its JSON; or `引数なし`, `flow-step-no-parameters`),
|
|
247
|
+
every edge as the executor keeps it — every node of its `depends_on` in order (`flow-step-depends-on`), the ones the figure does not draw named apart (`flow-step-indirect-dependencies`) — and every node whose `depends_on` names it (`flow-step-dependents`, or `後続なし (流れの最後に走る)`),
|
|
248
|
+
and its gates (the foundation's bulleted `List`, `flow-step-gate-list`, of `flow-gate-<flag>` with `data-gate-state` and `data-when-closed`; a failing gate names its failure status, e.g. `閉じたときは FAILED_EXTRACT で失敗`; `門なし (常に走る)`, `flow-step-no-gates`, for a node without one), plus `flow-gate-policy-override` when the flow overrides the step's declaration (`gate_policy`).
|
|
249
|
+
A node that runs child flows (`child_flows`, whatever its step) also lists the catalogue's child flows (`flow-step-child-flows`, `flow-step-child-flow-list`, `flow-child-flow-<key>` with `data-flow-key`: `<title> (<key> 版 <version>): <its nodes' titles joined by 、>`), each with the link `<title> 版 <version> を見る` (`pipeline-flow-child-flow-<key>`), which calls `onOpenFlow(key, null)`, and notes that the deployment's bindings, which the catalogue does not answer, decide which child takes which flow (`flow-step-child-flow-note`); without one it says `目録に子の流れがありません` (`flow-step-no-child-flows`).
|
|
250
|
+
When the described node changes, the focused element outside the panel is recorded as its opener: the pressed node (pressing a node — click, Enter, or Space — leaves focus on it) or the host control that held focus when `selectedNodeId` changed; nothing is recorded while focus is on nothing or inside the panel.
|
|
251
|
+
It closes with its close button and with Escape pressed inside it (the region's `onEscape`, the rule the history panel follows; Escape on a node clears the selection and leaves focus on that node), and either returns focus to that opener while it is still in the document; with no opener or one that has left the document, focus goes to the node it described.
|
|
252
|
+
A `selectedNodeId` the drawn shape lacks draws, instead of the panel, the warning `選んだ節「<id>」はこの版の形にありません。` (`pipeline-flow-node-missing`) with `節の選びを外す` (`pipeline-flow-node-clear`), which clears the selection and moves focus to the card's heading.
|
|
253
|
+
- **Metrics per flow version** (`flow-step-metric`, an `h4` `CardSection` in the detail): only the `PipelineStepMetric` whose `flowKey`, `flowVersion` and `nodeId` are the drawn flow, the drawn version and the node; the heading reads `直近の集計 (版 <v>・直近 <metricSampleWindow> 件)` and `pipeline-flow-metrics-scope` states the window (`流れ <key> 版 <v> の実行に属する、この節の新しい記録 <n> 件までを集計しています。`), and a node without a metric says so in a sentence headed by the root's unmeasured label (`未測定 (流れ <key> 版 <v> の実行で、この節を記録したものはまだありません)`, `flow-step-metric-empty`) instead of showing another version's figures.
|
|
254
|
+
The figures sit in a `DescriptionList` (`flow-metric`, one row `flow-metric-<field>` per field holding its term and its value).
|
|
240
255
|
The sample, succeeded, failed and skipped counts (`flow-metric-sample-count-value`, `flow-metric-succeeded-value`, `flow-metric-failed-value`, `flow-metric-skipped-value`) are `Measured` counts and p50 / p95 (`flow-metric-p50-value`, `flow-metric-p95-value`) are `Measured` texts that move whole (`data-cui-keep="whole"`, `1,234 ms`), all in the root's format locale; p50 / p95 are taken over measured durations only (`flow-metric-note`, which names the same label: `所要 未測定`).
|
|
241
256
|
The last start sits under one hook either way (`flow-metric-last-started-at-value`): the time in the root's zone with `data-measured="true"`, or the root's unmeasured label with `data-measured="false"`; a p50 / p95 the metric did not measure is likewise the label.
|
|
242
|
-
`metricSampleWindow` in the heading and
|
|
243
|
-
- **MIME classification table**: a `CardSection` (`h3`, `flow-mime-heading`) holding a `DataTable` (`flow-mime-table`) of the catalogue's `mime_kind_rules` in rule order, inside the foundation's table frame (`flow-mime-table-frame`, a region named by the table's caption that the keyboard can scroll), so narrow screens scroll it instead of pushing it past the card.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
257
|
+
`metricSampleWindow` in the heading and the scope sentence is written as a bare number: it is a count inside a sentence, so it is not `Measured`, not grouped, and does not follow the root's `formatLocale`.
|
|
258
|
+
- **MIME classification table**: a `CardSection` titled `MIME 型から種類への分類表` (`h3`, `flow-mime-heading`) holding a `DataTable` (`flow-mime-table`) of the catalogue's `mime_kind_rules` in rule order, inside the foundation's table frame (`flow-mime-table-frame`, a region named by the table's caption that the keyboard can scroll), so narrow screens scroll it instead of pushing it past the card.
|
|
259
|
+
Each row (`flow-mime-rule`, `data-kind`, `data-selected`) is a rule; the rows of a kind the selected flow's catalogue entry names in `document_kinds` (whatever version is drawn) draw that kind's label as an `info` `Pill` (`flow-mime-selected-kind`). Without rules the table says `分類の規則がありません (全ての型が「その他」です)。`.
|
|
260
|
+
- **Origin**: the identity line under the version (`flow-identity-line`) carries, beside `流れ: <title> (<key> 版 <v>)`, a `Pill` naming where the flow is kept (`pipeline-flow-origin`, `data-origin` = `builtin` / `custom`): `組み込み` (`neutral`) for a flow of the code and its ledger, `自前` (`info`) for a tenant's published custom flow. The tabs hold words alone.
|
|
261
|
+
- **Custom flows** (`pipeline-flow-custom`, a `CardSection` titled `自前の流れ` (`h3`), subtitled `テナントが流れを写して作った流れです。公開していない流れと退役した流れも並べます。`): what the host gives as `customFlows` (`CustomFlowsView`).
|
|
262
|
+
`{ kind: "read", list }` lists every custom flow of the tenant, unpublished and retired ones too (`CustomFlowList`, section 20); `{ kind: "hidden" }` draws the `Notice` `読みの権限が行か列に絞られているので、自前の流れは出していません。` (`CUSTOM_FLOWS_HIDDEN_SENTENCE`, `pipeline-flow-custom-hidden`); `{ kind: "unread", notice }` draws the host's notice of the failed read in the list's place.
|
|
263
|
+
`hidden` is given exactly when the catalogue's `custom_flows_hidden` is true (the catalogue hides the custom flows from a reader whose grant is limited to rows or columns, and the host reads no list then); either mismatch throws `RangeError`. The tabs, the figure and the detail stay in every case.
|
|
264
|
+
- **Copy and edit** (only with `canUpdate` and the list read): `写して作る…` on the selected entry (`pipeline-flow-actions`, `pipeline-flow-copy`, named `<title> (<key> 版 <v>) を写して作る…` for a screen reader) copies its current version, and the list's `写して作る…` on a flow with a published version copies its latest; the list's `編む` calls `onEditFlow(flowKey)`.
|
|
265
|
+
The copy dialog (`CopyFlowDialog`, a modal `Dialog` titled `流れを写して作る`) is drawn only while open, and closes when the list stops being read or the right is withdrawn. It takes the new key's part after `custom.` and the two titles, and states on the spot a malformed key, a key the list holds and a blank or overlong title.
|
|
266
|
+
It writes the copy through `editorHost.writeFlowDraft(key, { expected_row_version: null, title, content: { kind: "copy", source } })`: a copy made is announced (`自前の流れ <key> を作りました。`) and its key handed to `onEditFlow`; a 412 (another copy took the key first) becomes the key field's error `この鍵は使われています。ほかの鍵にしてください。` and calls `onRereadCustomFlows()`. Without `canUpdate` the viewer offers neither operation.
|
|
267
|
+
|
|
268
|
+
The viewer is controlled, and every prop is required: `catalog`, `featureFlags`, `metrics` (matched by `flowKey`, `flowVersion` and `nodeId`), `metricSampleWindow` (the number of newest step records per flow version and node the host aggregated into each metric; a positive integer, otherwise `RangeError`), `selectedFlowKey` with `onSelectFlow(flowKey)`, `figure` with `onSelectVersion(version | null)`, `selectedNodeId` (`string | null`) with `onSelectNode(nodeId | null)`, `onOpenFlow(flowKey, version | null)` (a child flow's link) and `headingRef`,
|
|
269
|
+
and the custom flows' `customFlows` (`CustomFlowsView`), `canUpdate` (the reader's `storage_admin:update` limited to neither rows nor columns), `onEditFlow(flowKey)` (the host moves to the edit view), `editorHost` (a `PipelineEditorHost`; the viewer calls its `writeFlowDraft` alone) and `onRereadCustomFlows()`.
|
|
270
|
+
A `selectedFlowKey` the catalogue does not hold throws `RangeError` naming the keys in tab order. A host that could not read the catalogue states why under the viewer's title (`PIPELINE_FLOW_TITLE`, `取り込みパイプライン`) instead of drawing it.
|
|
247
271
|
|
|
248
272
|
```tsx
|
|
249
|
-
<PipelineFlowViewer
|
|
273
|
+
<PipelineFlowViewer
|
|
274
|
+
catalog={flowCatalog} featureFlags={flags} metrics={stepMetrics} metricSampleWindow={stepMetricWindow}
|
|
275
|
+
selectedFlowKey={flowKey} onSelectFlow={(key) => openFlow(key, null)}
|
|
276
|
+
figure={versionFigure} onSelectVersion={(version) => openFlow(flowKey, version)}
|
|
277
|
+
selectedNodeId={nodeId} onSelectNode={setNodeId} onOpenFlow={openFlow} headingRef={flowHeadingRef}
|
|
278
|
+
customFlows={customFlows} canUpdate={canUpdatePipeline} onEditFlow={openEditor} editorHost={pipelineEditorHost} onRereadCustomFlows={rereadCustomFlows}
|
|
279
|
+
/>
|
|
250
280
|
```
|
|
251
281
|
|
|
252
|
-
The pure layout lives in `layoutFlow(nodes, geometry)` (`pipelineFlowLayout.ts`), over nodes `{ id, dependsOn }` given after the nodes they depend on (the executor's order is one such order; an empty input, a repeated id
|
|
282
|
+
The pure layout lives in `layoutFlow(nodes, geometry)` (`pipelineFlowLayout.ts`), over nodes `{ id, dependsOn }` given after the nodes they depend on (the executor's order is one such order; an empty input, a repeated id, a dependency named twice, a dependency on a later or unknown node, or a node whose id is the id of one of the virtual nodes below (`<from>→<to>#<layer>`) throws `RangeError`). It follows the three stages of Gansner, Koutsofios, North and Vo (1993), its layers running left to right (`pipelineFlowStages.ts`, `networkSimplex.ts`):
|
|
283
|
+
a node's layer is its longest dependency path, and an edge that skips layers gets one virtual node per skipped layer (`<from>→<to>#<layer>`), so no edge enters a node it does not connect;
|
|
284
|
+
the order within a layer starts from barycenters and is improved by median and transpose sweeps that reduce the crossings (bounded by `CROSSING_SWEEP_LIMIT` (24) and `CROSSING_SWEEP_PATIENCE` (8));
|
|
285
|
+
and the vertical positions are solved by network simplex, adjacent nodes of a layer at least `node.height + gap.row` apart and each edge weighted by `STRAIGHTNESS_WEIGHTS` (1 between real nodes, 2 between a real and a virtual node, 8 between virtual nodes) so that long edges run straight, balanced as Graphviz's `LR_balance`.
|
|
286
|
+
A node sits at `x = padding + layer × (width + gap.column)` and `y = padding + y_NS`; an edge is a cubic curve across each gap between layers and a horizontal line through each virtual node. The figure is `2 × padding + (L − 1) × (width + gap.column) + width` wide for `L` layers, and as high as its lowest node's bottom plus `padding`, raised to the 4 px lattice. Nothing is random and ties keep the given order, so the same nodes always lay out the same. The viewers lay out `drawnDependencies(nodes)` of the drawn shape.
|
|
253
287
|
The geometry is built from `@aiquants/console-ui`'s figure formula and tokens alone (`flowGeometry(lines)`), of which this package keeps no copy: a node is as wide as the widest box `figureBoxPlan` plans for the lines given (the fixed sans text-width model widened by the most letter and word spacing WCAG 1.4.12 allows, on the 8-px grid, with `space-3` on both sides) and as high as that plan, `space-12` (48) lies between layers and `space-6` (24) between the nodes of a layer and around the figure, more than the reach of the foundation's focus ring (`FIGURE_RING_REACH_PX`, 4).
|
|
254
|
-
|
|
288
|
+
This viewer and `PipelineRunFlowViewer` draw with one geometry, `flowGeometry(flowBoxLines(catalog, shapes))`: every title of every flow of the catalogue and of the drawn shape, with the widest note 0 and note 1 either figure can show, whatever the flags and the records.
|
|
289
|
+
For the built-in catalogue a box is 264 × 84 (a name and two notes; the widest line is the run figure's child counts `子 10 万件以上 · 失敗 10 万件以上`), so a flow of two rows is 240 high and one of one row 132: `email` and `google_native` 2184 × 240, `pdf` and `office` 2496 × 240, `text` 1560 × 240, `archive` and `markdown` 2184 × 132, `image`, `media` and `other` 1560 × 132, `child.read` 936 × 240, `child.document` 1560 × 240 and `child.figure` 1248 × 240, all on the 4 px lattice the foundation's `Figure` requires.
|
|
290
|
+
The layout is computed again only when the geometry or a node's dependencies change (`useFlowFigureLayout`).
|
|
255
291
|
|
|
256
292
|
### 9. `DocumentHistoryInspector`
|
|
257
293
|
|
|
258
294
|
A non-modal panel showing one document's change history across its revisions (S1 §3.3.5-13, §8.19): a console `Card` (`history-inspector`, `data-metadata-id`), the region named by its `h2`, with no `role="dialog"`. It reads through the host and draws only what the host returns.
|
|
259
295
|
|
|
260
|
-
Props: `host:
|
|
296
|
+
Props: `host: DocumentHistoryReads` (`loadHistory`, `compare` and `readLines`, picked from `DocumentHistoryHost`; every read resolves to a `ResourceOutcome`), `metadataId` (any revision row of the document), `target: DocumentHistoryTarget` (`{ kind: "latest_state" }` for an ingest row's history, `{ kind: "run", runId }` for a recent run's detail, `{ kind: "state", stateId }` for a change's arriving state), `openRequest: number` (changed by the host on every open or re-target from outside, including a press naming what is already shown;
|
|
261
297
|
each change reads the history again, keeping the shown one until the answer settles and withdrawing it on a 403, and applies `target` to the fresh history;
|
|
262
|
-
while the re-read is in flight, a target the shown history does not hold reads `文書の履歴を読み込んでいます…` (`history-detail-loading`, `aria-busy`) instead of being reported missing), `onClose()`, `returnFocusFallback: () => HTMLElement | null`, the optional `captureState(metadataId)` (the host's manual capture, a `ResourceOutcome<{ outcome }>`),
|
|
298
|
+
while the re-read is in flight, a target the shown history does not hold reads `文書の履歴を読み込んでいます…` (`history-detail-loading`, `aria-busy`) instead of being reported missing), `onClose()`, `onOpenRunFlow: ((runId: number) => void) | null` (with a function, a run's detail offers `流れの図で見る` (`history-run-flow-open`, in `history-run-actions` after `history-run-summary`), which calls it with the run's id; `null` offers nothing), `returnFocusFallback: () => HTMLElement | null`, the optional `captureState(metadataId)` (the host's manual capture, a `ResourceOutcome<{ outcome }>`),
|
|
299
|
+
and the variant section's `variantSets: readonly VariantSetView[]`, `variantHost: DocumentVariantHost` and `canSendVariantCommands`.
|
|
263
300
|
|
|
264
301
|
- **Header:** the card's `h2` `文書の履歴 — <登録名>` (the level-2 heading of `history-inspector`; the foundation's card title carries no test id), the close button in the card's actions (`history-close`), and a `DescriptionList` (`history-document`, one row `history-document-<key>` each) with the URI, the current revision (`r3`), the confidentiality level, the number of revisions and the last change's time.
|
|
265
302
|
- **Timeline (master, `MasterDetail` with a 288 px master and a 448 px detail minimum):** one group per revision, newest first; the current revision opens, older ones start closed; inside, the readable runs (each named `実行 #<id> · <きっかけ> · <流れ>`, the flow by `pipelineFlowLabel` (`history-row-<key>-flow`), with the badges of the state each produced, `本文 +12 −3`, `チャンク +2 −1 ~4`, `分類 変更`) and the states no readable run produced (`上書きの前の記録`, `手動の記録`, `記録の起点`), newest first, as `SelectableList` rows (`history-item`, `data-item-key="run:<id>"` / `"state:<id>"`).
|
|
@@ -270,21 +307,28 @@ while the re-read is in flight, a target the shown history does not hold reads `
|
|
|
270
307
|
A set that does not cover it reads `未分類` (the document's domain is not known yet) or `対象外` (the set has no row of the domain) in the state column (`VARIANT_UNCOVERED_STATE_TEXTS`, never a badge) and says why there is no projection (`文書のドメインが決まっていません`, `組はこの文書のドメインの行を持ちません`).
|
|
271
308
|
A covered set shows the state as a badge (`PROJECTION_VARIANT_STATE_LABELS`: `投影済み`, `古い版を投影済み`, `投影に失敗`, `未投影`), or `状態を読んでいません` when the reading lacks the set, and `今すぐ投影` projects the document with the set's row of the document's domain (`variantHost.project(metadataId, strategyId)`, the `strategy_id` of `VariantSetView.domains`); `点を消す` (only for a set that may hold points) calls `variantHost.removePoints(variantKey, metadataId)`. The state column is as wide as the widest of these words (`VARIANT_STATE_CELL_CONTENTS`).
|
|
272
309
|
Only the pressed command is busy; a settled one is announced and reads the states again; a refused one says why under the table (`history-variant-failure-${key}`), the 423 refusal of a set with a waiting or running projection job in a sentence of its own that carries the API's reason naming the job (`variantProjectionActiveSentence`). With `canSendVariantCommands` false, both commands are unavailable from the first render, described by `history-variants-forbidden-note`.
|
|
310
|
+
- **Earlier erasure:** when the history carries the latest erasure of the document's URI before its current row (`erasedBefore`, core's `ErasedBefore`; every revision the history holds is newer than its `throughRevision`), one `role="note"` sentence (`history-erased-before`) sits below the variant sets and ahead of the timeline, or of the sentence of a document with no record:
|
|
311
|
+
`r3 までの版の履歴は <時刻> に消去しました。`, the revision written as the timeline writes one (`r<n>`) and the time by `RecordedInstant` (`history-erased-before-at-value`). It names nothing else about the erasure and offers no control; a history with `erasedBefore: null` draws nothing in its place.
|
|
273
312
|
- **Detail (`history-detail`, `data-selected-key`):** a console `CardSection` (not a landmark) with one `h3` (its level-3 heading, the same element whatever it shows), the selected item's summary as a `DescriptionList` (`history-state-summary` or `history-run-summary`, one row `<list>-<key>` each, the run's status `Pill` `history-run-status` with `data-status` and `data-presentation`) and the selected state's change from its predecessor (`DocumentChangeView`: a `nav` of changed facets, one `CardSection` at `level={4}` per changed facet (an `h4`, not a landmark, `data-facet`),
|
|
274
313
|
`変化なし` for the rest; the structured data's JSON values in the mono face with their line breaks and indentation kept; the text and summary as `TextDiffView`, the chunks as `ChunkChangeTable`; the assets facet's added, removed and modified items each in one flat list in the tree order of their keys, the derived tree paths, each row `<treePathLabel(path)>: <value>`).
|
|
275
314
|
Every control sits on a line of its own (a `Cluster`, or the card's actions), so no 44 px target stretches a line of text; a timeline row's lines (the run or cause, the time, each facet badge, the marks, the pin) are the row's lines, and a line mixing a `Pill` and text is one `Text` span.
|
|
276
315
|
A first state and a state whose predecessor the operator cannot read both say `直前の記録との比較はありません。` and read no comparison.
|
|
277
|
-
A run shows its summary, its error, its state's change and its step timeline (`PipelineRunSteps`, the terminal step named `文書の状態の記録`) behind a console `Disclosure` (`history-run-steps`, closed for every run the operator selects).
|
|
278
|
-
The timeline (`run-steps`, the foundation's ordered `List` without markers, its cards 12 px (`space-3`) apart, as in every list inside it)
|
|
316
|
+
A run shows its summary, its error, its state's change and its step timeline (`PipelineRunSteps`, the terminal step named `文書の状態の記録`) behind a console `Disclosure` (`history-run-steps`, closed for every run the operator selects); the timeline stays mounted while the disclosure is closed.
|
|
317
|
+
The timeline (`run-steps`, the foundation's ordered `List` without markers, its cards 12 px (`space-3`) apart, as in every list inside it) is built in `sequence` order. A record of the run's own document, and the run's terminal step whatever it targets, is a list item (`run-step-<sequence>`, `data-status`, `data-step-key`) holding a console `Disclosure` (`run-step-card-<sequence>`). Its summary is one row of labels (a `Cluster as="span"`: the name, a `対象 #<id>` pill for a step that targets another document,
|
|
279
318
|
the strategy pill `戦略 #<id>`, the status pill `run-step-status-<sequence>` and the duration `Measured`), so the status word is part of the button's name.
|
|
280
319
|
Its panel holds the duration as a `Meter` (`run-step-bar-<sequence>`, the step's duration within the run's longest measured one, `最も長い段の <n>%`; none for an unmeasured step or a run whose longest step took 0 ms), the error, and the report: the measured fields as the rows of a `DescriptionList` (`run-step-provenance-<sequence>`, one row `run-step-provenance-<sequence>-<key>` each, a count in a `Measured` count and any other value in a `Measured` text that wraps), the unmeasured fields in one line under the root's label (`data-unmeasured-note`)
|
|
281
320
|
and the raw detail in a closed `Disclosure` around a `CodeBlock`.
|
|
282
|
-
A
|
|
283
|
-
A
|
|
284
|
-
|
|
321
|
+
A child's records (those that target another document than the run's) gather into one group per node, or per step for a record that names no node, whatever the step: a step that does not run per child and a step the current vocabulary does not know gather too.
|
|
322
|
+
A group is one list item (`run-step-group-<group key>`, the key `node:<node id>` or `step:<step key>`) holding a `Disclosure` (`run-step-group-card-<group key>`) whose summary counts its records (`対象 <n> 件`, `run-step-group-count-value-<group key>`) and its records per status (`run-step-group-statuses-<group key>-<status>`); its panel lists them in one flat list in the tree order of their targets' tree paths (`targetTreePath`), each card named `<treePathLabel(path)> (#<id>)` (`添付 0 > 書庫の中身 3 (#412)`);
|
|
323
|
+
the records that carry one path (the same child recorded twice in a run, which the record contract admits) share that path's item in `sequence` order, so none is dropped and the timeline does not throw.
|
|
324
|
+
A child record without a tree path (the path comes from a join, so a record written before or during the paths' backfill has none) is drawn after the group's one alert (`AlertMessage`, `run-step-group-pathless-<group key>`, which holds only `PATHLESS_CHILD_RECORDS_SENTENCE` in `run-step-group-pathless-<group key>-reason`: the reason `木の道を持たない記録 (移行の途中)` and how such a record is named), in a list of its own (`run-step-group-pathless-<group key>-list`), named `#<step record id> → #<target id>`, and the group opens at first;
|
|
325
|
+
the cards sit after the alert, not in it, so `role="alert"` reads the sentence alone and no card's text sits on the alert's tint; no path is made up for it and no record is dropped.
|
|
326
|
+
A group shows `RUN_STEP_GROUP_PAGE_SIZE` (50) records at a time and adds as many through the console's `LoadMore` (`run-step-group-more-<group key>`, the rest counted in `run-step-group-remaining-value-<group key>`); a closed disclosure draws no contents.
|
|
327
|
+
Whether each entry and card is open is kept in one record (its key → open): each writes its default the first time it appears (a record's card opens when the run has at most `STEP_CARDS_OPEN_LIMIT` (8) records, otherwise only a failed step's does; a group opens when the run has at most that many records or when the group holds a failed record or a record without a tree path), and the operator's toggles last across re-reads, so a running run that crosses the limit closes nothing.
|
|
328
|
+
Each entry is keyed by its records (a group by its key and its first record's `sequence`), so drawing the same run again keeps the alert (the same element and occurrence) and the focus on a card's disclosure.
|
|
285
329
|
- **Nodes of a child tree (`TreePathList`):** both lists above are flat by design: the foundation's ordered `List` without markers with one `ListItem` per listed node, spaced by the caller's gap (`space-1` for the comparison's rows, `space-3` for the run's cards; the records that share an item are stacked 12 px apart too), in the tree's pre-order (`compareTreePaths`, whatever order the items come in). Each item is named by its path's full label (`treePathLabel`, which the list hands to the card or row it draws), and the label names every level, so the list nests nothing:
|
|
286
330
|
a tree of any depth draws at the same DOM depth (React's recursive walk of its components stays as shallow) and adds no width.
|
|
287
|
-
Only the listed nodes are drawn; no item stands in for an ancestor the list does not hold, so an unchanged ancestor never reads as added or removed. Every node's item is `<list testId>-item-<path>`, scoped by its list, so the same path in two lists (added, removed and modified, or two
|
|
331
|
+
Only the listed nodes are drawn; no item stands in for an ancestor the list does not hold, so an unchanged ancestor never reads as added or removed. Every node's item is `<list testId>-item-<path>`, scoped by its list, so the same path in two lists (added, removed and modified, or two groups of the run timeline) never collides. The order and the labels are computed once per items array. The DOM order is the tree's pre-order, so focus moves through the cards and rows in the order they are drawn; the names and the targets are those of the cards and rows themselves.
|
|
288
332
|
The list paints nothing of its own: it is the foundation's list part, whose explicit `role="list"` keeps it a list for WebKit and VoiceOver. A path outside the grammar, a path listed twice or a kind with no name throws `RangeError`.
|
|
289
333
|
`比較の起点にする` pins a state (`history-pin-button`); the next state selected is compared with it, older side first; `外す` (`history-unpin`) releases it.
|
|
290
334
|
- **Text diff:** statistics (a `DescriptionList`: lines and graphemes, the similarity or the root's unmeasured label), attribute changes (`改行 CRLF → LF`, a bulleted `List` `<testId>-attributes` of `<testId>-attribute` items with `data-field`), the hunk index (`45 行目 (p.3) +3 −1`, an ordered `List` without markers named `変化の塊`, `<testId>-hunk-index`, of `<testId>-hunk-index-item` items holding the links), and hunk bodies read only when opened, `DIFF_LINE_PAGE_MAX` lines a page (`LoadMore`). Each body is a console `DataTable` (`<body>-lines`,
|
|
@@ -303,7 +347,7 @@ while the re-read is in flight, a target the shown history does not hold reads `
|
|
|
303
347
|
host={documentHistoryHost} metadataId={entry.metadataId} target={entry.target}
|
|
304
348
|
openRequest={entry.openRequest} onClose={closeHistory}
|
|
305
349
|
returnFocusFallback={() => tabRefs.current[activeTab]} captureState={captureDocumentState}
|
|
306
|
-
canSendCommands={operatorMaySendCommands}
|
|
350
|
+
canSendCommands={operatorMaySendCommands} onOpenRunFlow={openRunFlow}
|
|
307
351
|
variantSets={variantSetList.variant_sets} variantHost={documentVariantHost} canSendVariantCommands={operatorMayManageVariants}
|
|
308
352
|
/>
|
|
309
353
|
```
|
|
@@ -376,7 +420,8 @@ Props: `host: ProcessingObjectHost` (`{ loadPage(filter, cursor, signal) }`, `pr
|
|
|
376
420
|
### 14. `ChildRunList`
|
|
377
421
|
|
|
378
422
|
The child flows' runs under one root run of the recent runs table (S1 §3.3.5-13), a `DataTable` read a keyset page at a time (`CHILD_RUN_PAGE_SIZE` rows, `LoadMore`) through `@aiquants/enrichment-core`'s recent runs contract (`ChildRunPageSchema`).
|
|
379
|
-
Props: `rootRunId`, `read: ChildRunPageReader` (`(rootRunId, after, signal) => Promise<ResourceOutcome<ChildRunPage>>`; the first page with `after` `null`, a later page after the `next` the previous page named), `caption` (the table's name for assistive technology, such as `実行 #40 の子の実行`), `rescueTarget()` (where focus goes when the list is withdrawn, the control that opened it)
|
|
423
|
+
Props: `rootRunId`, `read: ChildRunPageReader` (`(rootRunId, after, signal) => Promise<ResourceOutcome<ChildRunPage>>`; the first page with `after` `null`, a later page after the `next` the previous page named), `caption` (the table's name for assistive technology, such as `実行 #40 の子の実行`), `rescueTarget()` (where focus goes when the list is withdrawn, the control that opened it),
|
|
424
|
+
`onOpenRunFlow: ((runId: number) => void) | null` (with a function, the table gains the column `操作` holding a `流れの図` button per run (`<testId>-open-flow-<id>`, described by the row's place), which calls it with the run's id; `null` draws no such column) and `testId`.
|
|
380
425
|
|
|
381
426
|
- A row (`<testId>-row`, `data-run-id`, `data-status`) shows the node the run ran for as the processing-objects list names a node (its place, `treePathLabel` and `#<row id>`; its name, `sourceName` or `名前なし` (`UNNAMED_NODE_TEXT`)), the flow (`pipelineFlowLabel`), the status badge (`PIPELINE_RUN_STATUS_PRESENTATION` with `PIPELINE_RUN_STATUS_TONES`, so a running run waiting for its retry reads `再試行待ち`), the step it stopped at (`ledgerStepLabel`, or `—`) and its start and finish times.
|
|
382
427
|
- The host answers only the child runs of nodes the viewer may read, never a mark; an empty page reads `読める子の実行はありません。`.
|
|
@@ -402,7 +447,8 @@ Also `canSendCommands` (whether the operator may send a cancel at all; when fals
|
|
|
402
447
|
its last failure (`—` without one) and 取り消す while it waits or runs and no cancel was asked.
|
|
403
448
|
- A restricted mark shows `RESTRICTED_DOCUMENT_TEXT`, its state and its enqueue time only, and offers no cancel; a job whose document the row scope hides is not on the board. An empty section reads `読める<section>の仕事はありません。`, and an empty page that is still followed `ここまでに読める<section>の仕事はありません。続きを読み込めます。`.
|
|
404
449
|
- 取り消す (`aria-expanded`, `aria-controls`) opens the section's confirmation above its table (`<section>-confirm`, a named group) with `ingestionJobCancelSentence` and focus on 取り消しを確かめる (`danger`), which calls `onCancel`; やめる closes it and returns focus to 取り消す. No table cell holds an unavailable control: the confirmation's button tells why it cannot send (`他の送信の答えを待っています`, `別の仕事の取り消しの答えを待っています`) and is busy (`取り消しを送っています…`) while its own cancel is in flight.
|
|
405
|
-
- Each new `cancelAnswer` is applied once in place (a job that ended leaves its section; a running job whose stop was asked shows `取り消しを求めた実行中` and keeps its document) and announced (`ingestionJobCancelAnnouncement`); an answer equal to the last one is neither applied nor announced again.
|
|
450
|
+
- Each new `cancelAnswer` is applied once in place (a job that ended leaves its section; a running job whose stop was asked shows `取り消しを求めた実行中` and keeps its document) and announced (`ingestionJobCancelAnnouncement`); an answer equal to the last one is neither applied nor announced again.
|
|
451
|
+
Focus on a confirmation control that disappears returns to the confirmation's opener when it remains, else to the section's heading; focus on a row's 取り消す that disappears while the row stays (its cancel was asked) goes to the section's heading; and when the whole row holding focus leaves, `@aiquants/console-ui`'s `DataTable` moves focus first, to the row now at that position, else the last row, else the empty section's text, so the section's own rescue does not act.
|
|
406
452
|
- Each `revision` re-reads a section's first page, a refused one too (a 403 offers no reload, so the next revision brings it back once the refusal is lifted), except a section whose later pages the operator read: it keeps them and offers `最新の一覧に読み直す` (`<section>-stale`). A failed re-read or later page keeps the rows shown and says why (`<section>-refresh-failed`); a later page's 403 re-reads the first page, and a 403 there withdraws the section (`動いている仕事を閲覧する権限がありません。<原因>。`, `INGESTION_JOBS_REFUSAL`).
|
|
407
453
|
|
|
408
454
|
### 17. `EmbeddingVariantBoard`
|
|
@@ -417,6 +463,187 @@ The board of the tenant's embedding variant sets (the API's `GET /ai/enrichment/
|
|
|
417
463
|
A refusal the host reads (`VariantSetCreateRejection`) puts the API's 422 messages at the fields they name and the rest in `variant-create-rejected`, and an endpoint outside the deployment (403 / 4033) at the URL field (`ENDPOINT_OUTSIDE_DEPLOYMENT_TEXT`); any other failure is `variant-create-failure`. A created set is announced and the form empties.
|
|
418
464
|
- Only the pressed control is pending while its request is in flight (a switch draws `切り替えを送っています…` beside it, a button is busy); each set's last outcome is noted or alerted under it (`variant-set-failure-${key}`). With `canSendCommands` false, every control is unavailable from the first render, described by `variant-board-forbidden-note` (`VARIANT_COMMAND_FORBIDDEN_SENTENCE`), and nothing is sent.
|
|
419
465
|
|
|
466
|
+
### 18. `PipelineRunFlowViewer`
|
|
467
|
+
|
|
468
|
+
One recorded run drawn on the shape of the flow version it ran, from the run flow the API answers (`GET /ai/document-history/runs/{run_id}`, `@aiquants/enrichment-core`'s `RunFlow`, required as `runFlow`): a console `Card` (`run-flow`, `data-run-id`) titled `実行 #<id> · 流れ <pipelineFlowLabel>` (`h2`, `run-flow-heading`) holding the run's summary, its links and notices, the figure with each node in the state the run left it, the selected node's detail and the run's step timeline. It draws only what the run flow and the host's reads hold.
|
|
469
|
+
|
|
470
|
+
Props, all required: `runFlow`, `catalog` (the flows' titles and versions for the links, and the figure's geometry), `selectedNodeId` (`string | null`) with `onSelectNode(nodeId | null)`, `onOpenFlow(flowKey, version | null)` (a definition's figure, `null` for the current version), `onOpenRun(runId, nodeId | null)`, `onOpenDocument(metadataId)`, `onClose()`, `readChildRuns: ChildRunPageReader | null` (`null` draws no child run list), `readStepIo: StepIoReader` with `ioRevision` (the inputs and outputs reads' revision),
|
|
471
|
+
`saveStepIoContent: StepIoContentSaver` (given a `StepIoContentTarget` `{ runId, stepId, direction, slot }`, it saves the slot's whole content and resolves to a `ResourceOutcome<null>`; the host names the file, since the gateway does not relay `Content-Disposition`), `refreshFailure: { status, reason } | null` (the host's last failed re-read of the run, as the console foundation's `keepLastGood` keeps it on a shown value) and `headingRef`.
|
|
472
|
+
|
|
473
|
+
- **Summary** (`run-flow-summary`): 状態 (the `Pill` `run-flow-status` with `data-status` and `data-presentation`, `PIPELINE_RUN_STATUS_PRESENTATION` with `PIPELINE_RUN_STATUS_TONES`), 試行, 開始 (`run-flow-started-at-value`), 終了 (`run-flow-finished-at-value`, or `未完了` in `run-flow-summary-finished-at` while the run is open), 文書 `#<id>`, 選んだ規則 (the rule that chose the run's flow), for a child run 親の実行 (`実行 #<id> (<flow>)`) and 子の道 (`treePathLabel`), and last 誤り (`run-flow-run-error`) when the run recorded an error.
|
|
474
|
+
選んだ規則 reads `組み込みの既定` (`run-flow-binding-default`) when no rule chose the flow, else three lines (`run-flow-binding`): the rule's name with its revision (`run-flow-binding-rule`, `data-binding-id`, `bindingRuleLabel`: `文書の規則 2 番 (改訂 3)`), its conditions (`run-flow-binding-match`, `条件: …`, `bindingMatchLabel`, a child's asset type by its name or `<type> (知らない種類)` outside the vocabulary) and the flow it chose (`run-flow-binding-flow`, `流れ: <title> (<key>)`, or the key alone for a flow the catalogue lacks, as a custom flow the catalogue hides).
|
|
475
|
+
- **Links** (`run-flow-links`): `この版の定形の図` (`run-flow-version`) when the catalogue's entry of the run's flow lists the version it ran, calling `onOpenFlow(key, version)` (`null` for the current version); `今の版 (版 <w>) の図` (`run-flow-current-version`) when the flow is in the catalogue and the run ran another version than the current one or none, calling `onOpenFlow(key, null)`; `親の実行へ` (`run-flow-parent`) for a child run, calling `onOpenRun` with the parent's run and node; and always `定形の流れへ戻る` (`run-flow-close`), calling `onClose()`.
|
|
476
|
+
- **Notices** (`info` `Notice`s): a run without a shape draws no figure and no detail, only the timeline, and says why: a run without a version, recorded before versions and inputs were, says `この実行は、版と入出力を記録する前の実行です。段の記録を時系列で示します。` (`run-flow-notice-unshaped`), and a run of a version today's steps' words cannot draw (a version the ledger does not keep, or a custom version today's steps no longer draw) says `この実行の版 (版 <v>) は、今の段の言葉で図に描けません。段の記録を時系列で示します。` (`run-flow-notice-undrawable`);
|
|
477
|
+
a replaced run says `この実行は、流れの版が変わったため、実行 #<id> (版 <v>) に置き換えられました。` with `置き換えた実行へ` (`run-flow-notice-superseded-by`, link `run-flow-superseded-by`), and a replacing run `この実行は、実行 #<id> (版 <v>) を置き換えました。` with `置き換えられた実行へ` (`run-flow-notice-supersedes`, link `run-flow-supersedes`), both links calling `onOpenRun(runId, null)` (a run without a version reads `版なし`);
|
|
478
|
+
a run that replaced one the operator may not read says `この実行は、閲覧できない実行を置き換えました。` (`run-flow-supersedes-unreadable`).
|
|
479
|
+
- **Refresh failure**: `refreshFailure` draws, by the console's one failure rule (`labels.failureSentence`), `実行の最新の流れを読めませんでした (<理由>)。` (no parentheses for an empty reason), or for a 403 `RUN_FLOW_REFUSAL` (`実行の流れを閲覧する権限がありません。`, exported for a host that draws a run it could not read) followed by the cause the route declared (`run-flow-refresh-failed`, an `AlertMessage` whose occurrence advances with every new failure object, so a failure that repeats is announced again).
|
|
480
|
+
- **Figure**: the definition viewer's figure (the same geometry, layout, keys and hint) over the run's shape, named `実行 #<id> の図 (<shape title> 版 <v>)`, with the hooks `run-flow-figure`, `run-flow-svg`, `run-flow-scroll-hint`, `run-flow-node-<id>` and `run-flow-edge-<from>-<to>`. Each node shows the state the run left it in (`data-flow-node-state`, beside `data-step-key`):
|
|
481
|
+
the state of its records, the last one per target (`running` before `failed`, `cancelled`, `succeeded` and `skipped`), and for a node without a record `waiting` while the run runs and `not_run` after; its tone and outline are `RUN_NODE_STATE_TONES` and `RUN_NODE_STATE_OUTLINES` (`failed` `danger`, `succeeded` `success`, `skipped` `warning`, the rest `neutral`; `waiting` and `not_run` dashed).
|
|
482
|
+
Note 0 says what the node did: a per-child node's counts (`子 <n> 件 · 失敗 <n> 件`, `formatChildCount`), a skipped node's short reason (`skipReasonShortLabel`), `前の成功を使用` when every record reused an earlier success, otherwise its phase; note 1 is the state's word (`RUN_NODE_STATE_LABELS`), written `<word> · 経過 <elapsed>` (`formatElapsed`) for a failed, cancelled or succeeded node whose elapsed time was measured.
|
|
483
|
+
A node's accessible name is `<title>、<note 1>` and its description `<note 0>。<step> 版 <step version>`: the description is text only, so it holds no instant.
|
|
484
|
+
A `selectedNodeId` the shape lacks draws, instead of the detail, the warning `選んだ節「<id>」はこの実行の形にありません。` (`run-flow-selection-missing`) with `節の選びを外す` (`run-flow-selection-clear`).
|
|
485
|
+
- **Node detail** (`run-flow-step-detail`, `data-node-id`, `data-flow-node-state`): a `CardSection` region named by the node's title (`h3`) with its description (`run-flow-step-description`), `閉じる` (`run-flow-step-detail-close`) and the facts (`run-flow-step-facts`): 段 (`run-flow-step-key`, `<step> 版 <step version>`), 状態 (`run-flow-step-state`, `data-state`);
|
|
486
|
+
once a record started, 開始 (`run-flow-step-started-at-value`), 終了 (`run-flow-step-finished-at-value`, `未完了` while running) and 経過 (`run-flow-step-elapsed-value`; `未完了` while running, `経過を測れません (記録の時刻の前後が逆です)。` when the records' instants run backwards); for the node's record of the run's own document, 所要 (`run-flow-step-duration-value`, in ms), 省略の理由 (`skipReasonLabel`), 誤り (`run-flow-step-error`) and 使い回し (`前の成功 (段の記録 #<id>) を使用`); and 段の版の食い違い (`記録の段の版 <versions> (形の版 <v>)`) when a record ran another step version than the shape's.
|
|
487
|
+
使った設定 (`run-flow-settings`, `h4`) lists the node's parameters in the shape's words (`<value title> (<value>)`, or the value) and what the node's own record measured — provider, model, engine, prompt (`<key> 版 <version>`), strategy (`#<id>`), collection, embedding model and body source (`run-flow-settings-list`) — or says `引数と記録の測りはありません。` (`run-flow-settings-none`).
|
|
488
|
+
Then, by the node's kind:
|
|
489
|
+
- a node that runs child flows (`child_flows`): 子の実行 (`run-flow-child-run-section`, `h4`), the run's child flows one item each (`run-flow-child-flows`, `run-flow-child-flow-<index>` with `data-flow-key`, `childFlowCountsText`) with `<title> 版 <v> を見る` (`run-flow-child-flow-<index>-open`, calling `onOpenFlow`) when the catalogue lists that version, or `読める子の実行はありません。` (`run-flow-child-flows-none`); with `readChildRuns`, the run's `ChildRunList` (`run-flow-child-runs`, captioned `実行 #<id> の子の実行`), whose `流れの図` calls `onOpenRun` with the child run and no node;
|
|
490
|
+
- a node that runs per child (`fan_out`): 子ごとの記録 (`run-flow-child-records`, `h4`) with its counts (`run-flow-child-records-counts`, `childRecordCountsText`), a status filter (`run-flow-child-filter`, `CHILD_RECORD_FILTER_LABELS`), the alert over records without a tree path (`run-flow-child-records-pathless`) and a `DataTable` (`run-flow-child-records-table`) of 子・状態・経過・操作, `CHILD_RECORD_PAGE_SIZE` (50) rows at a time (`LoadMore`, `run-flow-child-records-more`), each row's `入出力` (`run-flow-child-records-io-<target id>`) for that record's inputs and outputs;
|
|
491
|
+
an empty table says `読める子の記録はありません。`, or `この状態の子の記録はありません。` under a filter;
|
|
492
|
+
- any other node: its inputs and outputs.
|
|
493
|
+
- **Inputs and outputs** (`run-flow-io-parts`): `受け取ったもの` (`run-flow-io-inputs`) and `作ったもの` (`run-flow-io-outputs`), each an `h4` with a row per slot (`run-flow-io-slot-<direction>-<slot>`), read through `readStepIo` for the node's record.
|
|
494
|
+
A row names where an input came from (出所: a link `<node title> の <slot>` to that node's output, a link `<node title> の子ごとの記録 (<n> 件)` to a per-child node's records, or `流れの入力 (文書 #<id>)`), the slot's size (`run-flow-io-size-<direction>-<slot>`), the head of its SHA-256 (`DIGEST_HEAD_LENGTH` characters) with `ハッシュを写す`, a document it names (`文書 #<id>` in `run-flow-io-document-id-<direction>-<slot>` with the link `文書を開く`, `run-flow-io-document-open-<direction>-<slot>`, calling `onOpenDocument`; then its `名` and `MIME` when they are not empty, and its size),
|
|
495
|
+
its content with `全体を保存` (`saveStepIoContent`) and `開いて見る` (the recorded preview as a tree, `PREVIEW_TRUNCATED_TEXT` where the preview was cut), and for an output the records that took it (受けた記録, `run-flow-io-consumers-<slot>`, each a link `<node title> の <slot>` to that node's input).
|
|
496
|
+
A node without a record says `この節の記録はありません。` (`NO_STEP_RECORD_TEXT`), and a slot without a value says why (`受け取っていません (上流が出力を作りませんでした)`, `作っていません` and `中身の写しはありません (記録の前の出力)` among others). A refused read says `段の記録の入出力を閲覧する権限がありません。` (`STEP_IO_REFUSAL`); any other failure is an `AlertMessage` (`run-flow-io-failed`) with `もう一度読む` (`run-flow-io-retry`).
|
|
497
|
+
- **Timeline** (`run-flow-timeline`, `段の時系列`, `h3`): the run's `PipelineRunSteps` (see `DocumentHistoryInspector`) in step with the figure: each entry carries `data-entry-key` and `data-selected` (whether it names the selected node); when the selected node changes, its entries open once and the first of them is scrolled into view after the commit (`scrollIntoView({ block: "nearest" })`); the selection the timeline mounts with opens its entries without scrolling, so a screen opened on a selected node stays on the figure.
|
|
498
|
+
The operator can still close the selected node's entries, and opening an entry that names a node selects that node. A run without a shape draws the timeline alone.
|
|
499
|
+
- **Focus**: the card is drawn anew for another run (it is keyed by the run's id), and focus that leaves with the old card goes to the new card's heading (`useFocusRescue`). The node detail opens and closes as the definition viewer's does (the opener, `閉じる`, Escape inside, and `節の選びを外す`).
|
|
500
|
+
A link that follows an input or an output selects its node and then moves focus to that slot's row, or to the child records' heading for a per-child node or a link to the child records, or to the detail's heading when neither is drawn; the request is dropped when the run changes, or when the selection moves to anything but the request's node (`null` included) before the request's node is selected. Focus lost inside the inputs and outputs goes to `受け取ったもの`.
|
|
501
|
+
- **Headings**: the card's `h2`, the node detail's and the timeline's `h3`, and the `h4`s inside the detail (`使った設定`, `子の実行`, `子ごとの記録`, `受け取ったもの`, `作ったもの`); `tests/HeadingOutline.spec.tsx` checks the outline.
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
<PipelineRunFlowViewer
|
|
505
|
+
runFlow={runFlow} catalog={flowCatalog} selectedNodeId={nodeId} onSelectNode={setNodeId}
|
|
506
|
+
onOpenFlow={openFlow} onOpenRun={openRun} onOpenDocument={openDocument} onClose={closeRunFlow}
|
|
507
|
+
readChildRuns={readChildRunPage} readStepIo={documentHistoryHost.loadStepIo} ioRevision={ioRevision}
|
|
508
|
+
saveStepIoContent={documentHistoryHost.saveStepIoContent} refreshFailure={runFlowRefreshFailure} headingRef={runFlowHeadingRef}
|
|
509
|
+
/>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### 19. `RetrievalEvaluationWorkbench`
|
|
513
|
+
|
|
514
|
+
The retrieval evaluation workbench (`@aiquants/enrichment-core`'s retrieval evaluation, the routes under `EVALUATION_ROUTE_PREFIX`): it scores 2 to 4 embedding variant sets on the tenant's own documents and questions in three panels drawn from the console foundation only, stacked in `retrieval-evaluation-workbench` — the query sets (`問いの集まり`), the chosen set's runs (`走り`) and the shown run's report (`結果`). Every count, score and summary is the API's; nothing is counted or scored again.
|
|
515
|
+
Props, all required: `variantSets: readonly VariantSetView[]` (`VariantSetList.variant_sets`, in key order), `host: RetrievalEvaluationHost` (`evaluationHost.ts`), `canSendCommands`, `revision` (advanced by the host on a job event) and `refreshMs` (how often a moving run or generation is read again; anything but a positive safe integer throws `RangeError` naming it before anything is drawn).
|
|
516
|
+
|
|
517
|
+
- **Host contract** (`RetrievalEvaluationHost`, one route of the retrieval evaluation each; the routes, the transport and the authorization are the host's): the reads `listQuerySets(signal)` (`GET /query-sets`, newest first), `listQueries(querySetId, status, signal)` (`GET /query-sets/{query_set_id}/queries`, `null` for every state), `listJudgments(queryId, signal)` (`GET /queries/{query_id}/judgments`),
|
|
518
|
+
`listRuns(querySetId, signal)` (`GET /runs?query_set_id=`, newest first), `readRun(runUid, signal)` (`GET /runs/{run_uid}`, scores computed from the current judgments) and `readRunQueries(runUid, signal)` (`GET /runs/{run_uid}/queries`);
|
|
519
|
+
the commands `createQuerySet(request)` (`POST /query-sets`), `requestGeneration(querySetId, request)` (`POST /query-sets/{query_set_id}/generation`, 202, a new job or the one it merged into), `createQuery(querySetId, request)` (`POST /query-sets/{query_set_id}/queries`), `updateQuery(queryId, request)` (`PATCH /queries/{query_id}`), `writeJudgment(queryId, request)` (`PUT /queries/{query_id}/judgments`, replacing the pair's judgment) and `startRun(request)` (`POST /runs`, 202).
|
|
520
|
+
A read takes the workbench's abort signal (a superseded read is aborted and may reject with `AbortError`). No route throws for a failure it can describe: each resolves to a `ResourceOutcome`, and `startRun` to a `CommandOutcome` whose `rejection` is the 422 / 4227 refusal of an incomplete scope the host read (`EvaluationScopeIncompleteRejection`, `{ code: "EVALUATION_SCOPE_INCOMPLETE" }`).
|
|
521
|
+
- **Query sets** (`Card` `問いの集まり`, `evaluation-sets`, `h2` `evaluation-sets-heading`): the chosen set (`集まり`, `evaluation-set-choice`, the newest until the operator chooses; each option `<name> (採った n・決定待ち n・捨てた n)`) and its summary (`evaluation-set-summary`): 説明 (`evaluation-set-description`, `説明はありません` without one), 作った者 (`evaluation-set-created-by`), 作った時刻 (`evaluation-set-created-at`, `RecordedInstant`), 判定の数 (`evaluation-set-judgments`)
|
|
522
|
+
and 最後の生成 (`evaluation-set-generation`: `<selected> チャンクを選び、<kept> 問を足した (機密の区分で読めない n・答えられない n・写し n・既に在る n)`, `問いを作る仕事が待っているか走っています` while its job moves, or `まだ問いを作っていません`). Without a set it says `問いの集まりがありません。下で作ってください。` (`evaluation-sets-empty`) and draws neither the generation, the questions nor the runs. Choosing another set closes the shown report.
|
|
523
|
+
- **Create a set** (`集まりを作る`, `h3`, `evaluation-set-create`): 名 (`evaluation-set-create-name`, `名は 1〜100 字で入力してください。`) and the optional 説明 (`evaluation-set-create-description`, `説明は 1000 字以内で入力してください。`), checked once a send has been attempted and sent trimmed by 作る (`evaluation-set-create-submit`, busy `作っています…`) as `createQuerySet({ name, description })`, an empty description as `null`.
|
|
524
|
+
A created set is announced (`問いの集まり <name> を作りました。`) and chosen, and the form empties; a failure says `問いの集まりを作れませんでした (<理由>)。` (`evaluation-set-create-failed`).
|
|
525
|
+
- **Generate questions** (`問いを作る`, `h3`, `evaluation-generation`, for the chosen set): five whole numbers the operator enters, since the API takes no default — 選ぶチャンクの数 (`evaluation-generation-query-count`, 1〜1000), 1 文書から選ぶ最も多い数 (`evaluation-generation-queries-per-document`, 1〜10), チャンクの字の数の下限 (`evaluation-generation-min-chunk-chars`, 1〜4000), 写しとみなす字の並びの長さ (`evaluation-generation-max-copied-chars`, 1〜200) and 種 (`evaluation-generation-seed`, 0 up to `Number.MAX_SAFE_INTEGER`), each digits only within its bounds (`<min>〜<max> の整数を入力してください。`, `integerField`) —
|
|
526
|
+
sent by 問いを作る (`evaluation-generation-submit`, busy `仕事を積んでいます…`) as `requestGeneration(querySetId, { query_count, queries_per_document, min_chunk_chars, max_copied_chars, seed })`. The job is noted under the form (`evaluation-generation-accepted`) and announced: `問いを作る仕事 <job> を積みました。仕事が終わると問いが決定待ちに並びます。`, or `既に待っているか走っている生成の仕事 <job> に重ねました。` for a merged one; a failure (`問いを作る仕事を積めませんでした (<理由>)。`, `evaluation-generation-failed`) takes the note away.
|
|
527
|
+
- **Questions** (`問い`, `h3`, `evaluation-queries-section`): the state filter (`状態`, `evaluation-queries-status`: `全て`, `決定待ち`, `採った`, `捨てた`, sent as `listQueries`' `status`) over a `DataTable` (`evaluation-queries`, rows `evaluation-query-row` with `data-query-id` and `data-status`) of 問い (the row header, a generated question noted `源: <file uri> · チャンク <index>`), 出どころ (`生成` / `人`), 状態 (`evaluation-query-status-<id>`, a rejected one with its reason, `捨てた (チャンクの写し)` / `捨てた (操作者)`), 判定 (`evaluation-query-judgments-<id>`) and 操作.
|
|
528
|
+
An empty table says `集まりに問いがありません。`, or `<state>の問いはありません。` under a filter. 採る (`evaluation-query-accept-<id>`, unless accepted) and 捨てる (`evaluation-query-reject-<id>`, unless rejected) send `updateQuery(id, { status })` (busy `送っています…`).
|
|
529
|
+
直す (`evaluation-query-edit-<id>`, a proposed question only, with `aria-expanded` and `aria-controls`) opens the row's edit (`evaluation-query-edit-form-<id>`: 問いの本文 `evaluation-query-edit-text`, `問いは 1〜200 字で入力してください。`); its 直す (`evaluation-query-edit-save`, busy `直しています…`) sends `updateQuery(id, { query_text })` trimmed and closes it, and its やめる (`evaluation-query-edit-cancel`), like a second press of 直す, closes it without sending, so it reopens on the question's text.
|
|
530
|
+
人の問い (`evaluation-query-create-text`, the same bounds) is sent trimmed by 問いを足す (`evaluation-query-create-submit`, busy `足しています…`) as `createQuery(querySetId, { query_text })` and is accepted at once.
|
|
531
|
+
A success is announced (`問い <id> を採ったにしました。` / `問い <id> を捨てたにしました。`, `問い <id> の本文を直しました。`, `人の問い <id> を採った問いとして足しました。`); a failure is one alert above the table (`evaluation-query-command-failed`): `問い <id> を採れませんでした (<理由>)。`, `問い <id> を捨てられませんでした (<理由>)。`, `問い <id> の本文を直せませんでした (<理由>)。` (the edit stays open on the typed text) or `人の問いを足せませんでした (<理由>)。`.
|
|
532
|
+
Only the pressed command is pending: while it waits, the other row commands and the edit's 直す point at `EVALUATION_QUERY_ROWS_BUSY_SENTENCE` above the table (`evaluation-queries-busy-note`, drawn only while a row command other than the pressed one remains), and 問いを足す draws its own reason, `他の命令の答えを待っています`.
|
|
533
|
+
- **Runs** (`Card` `走り`, `evaluation-runs`, `h2` `evaluation-runs-heading`, drawn for the chosen set): 比べる組 (`evaluation-run-sets`, production `本番 (default)` first and then the active sets, sent in that order, the first the intervals' baseline; `比べる組を 2〜4 つ選んでください。`), 形 (`evaluation-run-modes`: `融合 (HYBRID)`, `密 (DENSE)`, `疎 (SPARSE)`, sent in that order; `形を 1 つ以上選んでください。`),
|
|
534
|
+
判定役が読むチャンクの数 (`evaluation-run-judge-passages`, 1〜10) and 区間の種 (`evaluation-run-seed`, 0 up to `Number.MAX_SAFE_INTEGER`), sent by 走らせる (`evaluation-run-submit`, busy `走りを始めています…`) as `startRun({ query_set_id, variant_keys, modes, judge_passages, bootstrap_seed })`; a started run is announced (`走り <run uid> を始めました。`) and its report shown.
|
|
535
|
+
The 422 / 4227 refusal says `比べる組の文書が揃っていないので、走りを始めませんでした (<理由>)。組の投影を済ませてから始め直してください。` (`evaluation-run-failed`) above the chosen active sets' projection counts by domain from `variantSets` (`選んだ組の投影`, `evaluation-scope`, rows `evaluation-scope-row` with `data-variant-key` and `data-domain-key`: 組, ドメイン, 今の文書, 今の身元で投影済み, 古い版だけ, 失敗); any other failure says `走りを始められませんでした (<理由>)。` and draws no counts.
|
|
536
|
+
The set's runs, newest first (`集まりの走り`, `evaluation-run-list`, rows `evaluation-run-row` with `data-run-uid` and `data-status`): 作った時刻 (`evaluation-run-created-at-<run uid>`, `RecordedInstant`), 状態 (`evaluation-run-progress-<run uid>`, `runProgressText`: `<state> · <step> <done> / <total> · <failure>`), 組, 形 and 結果を見る (`evaluation-run-open-<run uid>`), which shows the run's report.
|
|
537
|
+
The shown run's command reads `示しています`, unavailable and pointing at `EVALUATION_RUN_SHOWN_SENTENCE` (`evaluation-runs-shown-note`, drawn while that run is listed). An empty list says `この集まりの走りはまだありません。`.
|
|
538
|
+
- **Report** (`Card` `結果`, `evaluation-report`, `h2` `evaluation-report-heading`, drawn anew for each run): the summary (`evaluation-report-summary`) — 走り (`evaluation-report-run`), 状態 (`evaluation-report-status`), 失敗の文 (`evaluation-report-failure`, only for a run that recorded one, `文はありません` when it is empty), 比べた組 (`evaluation-report-sets`),
|
|
539
|
+
範囲 (`evaluation-report-scope`: `<domain> <n> 文書` joined by `・`, `文字のドメインがありません`, or `まだ確かめていません`), 外した採った問い (`evaluation-report-skipped`: `<n> 問 (関連ありと判定した文書が全て今の版を失った)` or `まだ数えていません`) and 採点の決まり (`evaluation-report-constants`: `上位 <cutoff> 件・区間は <resamples> 回の対の bootstrap で信頼 <confidence>%`).
|
|
540
|
+
A run that is not `COMPLETED` says `走りが終わると採点を示します。` while it moves and `終わらなかった走りは採点を持ちません。` otherwise (`evaluation-report-unscored`). A `COMPLETED` run has a section per mode (`evaluation-mode-<mode>`, `h3` by `EVALUATION_SEARCH_MODE_LABELS`) with the questions scored and left out (`evaluation-mode-<mode>-counts`) and a row per set (`evaluation-scores-<mode>`, rows `evaluation-score-row-<mode>` with `data-variant-key`):
|
|
541
|
+
nDCG@10, MRR@10 and Recall@10 (`evaluation-score-<mode>-<key>-<metric>`, three decimals, `採点なし` when the mode scored no question) and each one's difference from the baseline (`evaluation-difference-<mode>-<key>-<metric>`: `基準` for the baseline, otherwise the signed mean with its interval, such as `+0.250 [0.100, 0.400]`, and what the interval says, `基準より良い` above zero, `基準より悪い` below it, `差が分からない` when it reaches zero).
|
|
542
|
+
- **Review** (`問いごとの順位と判定`, a `Disclosure`, `evaluation-review`): closed until the operator opens it, and `readRunQueries` is read only then. It shows one question at a time (`問い`, `evaluation-review-query`, the first until another is chosen; `この走りは問いを順位付けていません。`, `evaluation-review-empty`, for none) with its judgments read by `listJudgments` (`evaluation-review-detail-<id>`):
|
|
543
|
+
`集めた文書と今の判定` (`evaluation-pool-<id>`, rows `evaluation-pool-row` with `data-file-uri` and `data-judge`, `none` without a judgment) lists each pooled document once, in the order the rankings first name it (by mode, then set, then rank), with 今の段 (`evaluation-pool-grade-<id>-<file uri>`: `0 関係しない`, `1 一部が関係する`, `2 答えを含む` or `判定なし`), 判定者 (`源の文書`, `モデル` or `人`, with `(<judged by>)` from the question's judgments, or `判定なし`),
|
|
544
|
+
理由 (the model's rationale, `理由はありません`, `理由を読み込んでいます…` or `理由を読めませんでした`) and 段を直す: a grade choice (`evaluation-pool-choice-<id>-<file uri>`, `段を選ぶ` first) and 書く (`evaluation-pool-write-<id>-<file uri>`, busy `書いています…`), which sends `writeJudgment(queryId, { file_uri, grade })`. An empty pool says `どの組もこの問いに文書を返していません。`.
|
|
545
|
+
A written grade is announced (`文書 <file uri> を段 <grade> と判定しました。採点を読み直します。`), its choice resets and the report is read again with the scores it changes; a failure says `文書 <file uri> の段を書けませんでした (<理由>)。` (`evaluation-review-grade-failed`) and keeps the choice.
|
|
546
|
+
A row without a chosen grade has its 書く point at `EVALUATION_POOL_CHOOSE_SENTENCE` (`evaluation-review-choose-note`), and while a grade waits the other rows' 書く point at `EVALUATION_POOL_BUSY_SENTENCE` (`evaluation-review-busy-note`); each note is drawn only while a 書く points at it.
|
|
547
|
+
Each mode's rankings then stand side by side (`evaluation-rankings-<id>-<mode>`, `<mode> の組ごとの順位`, rows `<n> 位`, a column per set, each cell `<file uri> · <grade>` with `判定なし` for an unjudged document, or `この順位の文書無し`), or the mode says `<mode> ではどの組も文書を返していません。` (`evaluation-rankings-<id>-<mode>-empty`).
|
|
548
|
+
- **Reads and failures**: a read draws its loading line until it answers (`evaluation-sets-loading`, `evaluation-queries-loading`, `evaluation-runs-loading`, `evaluation-report-loading`, `evaluation-review-loading`), and a failed read is `@aiquants/console-ui`'s `ResourceFailed` with its reload (`evaluation-sets-failed`, `evaluation-queries-failed`, `evaluation-runs-failed`, `evaluation-report-failed`, `evaluation-review-failed`, `evaluation-review-judgments-failed`, each with `-reload`):
|
|
549
|
+
`問いの集まりを読めませんでした (<理由>)。`, and likewise `問い`, `走り`, `走りの採点`, `問いごとの順位` and `問いの判定` (no parentheses for an empty reason), or for a 403 `EVALUATION_READ_FORBIDDEN_SENTENCE` (`検索の評価を読む権限がありません。`) and no reload.
|
|
550
|
+
A failed re-read keeps the values shown and says why (`evaluation-sets-refresh-failed`, `evaluation-queries-refresh-failed`, `evaluation-runs-refresh-failed`, `evaluation-report-refresh-failed`, `evaluation-review-refresh-failed`); a command answered 403 says `EVALUATION_COMMAND_FORBIDDEN_SENTENCE` (`検索の評価を操作する権限がありません。`).
|
|
551
|
+
Every failure but the run's scope refusal is stated by the console's one failure rule (`labels.failureSentence`), and every failure is an `AlertMessage` whose occurrence (`data-cui-occurrence`) advances with each failure, so a repeated sentence is announced again.
|
|
552
|
+
- **An operator who may not send commands** (`canSendCommands` false): the workbench says so once above the panels (`evaluation-forbidden-note`, `EVALUATION_COMMAND_FORBIDDEN_SENTENCE`), and from the first render every command — 作る, 問いを作る, 採る, 捨てる, 直す, 問いを足す, 走らせる, each grade choice and 書く — is unavailable (`aria-disabled="true"`) and points at that note (`aria-describedby`); nothing is sent, not even by submitting a form. The fields, the filters, the reads and the reloads keep working, and the notes of a waiting command and of an unchosen grade are not drawn.
|
|
553
|
+
- **Polling and re-reading**: while the chosen set's generation moves (its job is named and it has no summary yet), the sets are read again every `refreshMs`, and when that generation ends every panel is read again; the runs are read again every `refreshMs` while one of them is `QUEUED` or `RUNNING`, and the shown report while its run is. A command's success reads every panel again, as an advance of `revision` does.
|
|
554
|
+
- **Focus**: a busy or unavailable button keeps focus (`aria-disabled`, never `disabled`). Focus on a reload that disappears (its read answered, or came back 403) goes to the panel's heading (`useFocusRescue`): `evaluation-sets-heading` for the sets, `evaluation-queries-section-heading` for the questions, `evaluation-runs-heading` for the runs, and `evaluation-report-heading` for the report, the review and a question's judgments.
|
|
555
|
+
- **Headings**: the cards' `h2` (`問いの集まり`, `走り`, `結果`) and the `h3` sections (`集まりを作る`, `問いを作る`, `問い`, and each mode's); `tests/HeadingOutline.spec.tsx` checks the outline with a report shown.
|
|
556
|
+
|
|
557
|
+
```tsx
|
|
558
|
+
<RetrievalEvaluationWorkbench
|
|
559
|
+
variantSets={variantSetList.variant_sets} host={evaluationHost}
|
|
560
|
+
canSendCommands={mayOperateEvaluation} revision={evaluationJobRevision} refreshMs={5000}
|
|
561
|
+
/>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### 20. `CustomFlowList`
|
|
565
|
+
|
|
566
|
+
The tenant's custom flows as the editing routes list them (`FlowDraftList`, `GET /ai/pipeline/flow-drafts`): every custom flow, unpublished and retired ones too, in the list's order, each with its title and key, its states and the operations its caller offers. It draws no heading, since the caller names the list where it places it (the viewer's `自前の流れ` section, the host's chooser of a flow to edit).
|
|
567
|
+
|
|
568
|
+
- **Items** (`custom-flow-list`, a `List` named `自前の流れ`): one item per flow (`custom-flow-<key>`, `data-flow-key`), holding its title and key on one line (`custom-flow-<key>-name`, `flowLabelOf`: `<title> (<key>)`), its states (`custom-flow-<key>-state`, `DraftStatePills`) and its buttons (`custom-flow-<key>-actions`).
|
|
569
|
+
- **States** (`customFlowStatesOf(summary)`): a tint `Pill` per state the flow is in (`custom-flow-<key>-state-<state>`, `data-draft-state`), in this order: `未公開` before the first publication, else `公開した版 <n>` and, while the draft differs from that version, `下書きに未公開の変更` (a flow without a version always has changes to publish, so the list does not say so twice); then `退役` and `今の段で走らない` where they hold.
|
|
570
|
+
The words and tones are the ones both editors draw (`DRAFT_STATE_LABELS` and `DRAFT_STATE_TONES` of `flowEditorWords.ts`: `未公開` and `退役` `neutral`, `公開した版 <n>` and `今の改訂 <n>` `info`, `下書きに未公開の変更` and `保存していない変更` `warning`, `今の段で走らない` `danger`), so a flow reads the same in the list and in its editor.
|
|
571
|
+
- **Operations** (`operations: CustomFlowListOperations`): `{ kind: "none" }` offers nothing (the flows view, for a reader who may not update);
|
|
572
|
+
`{ kind: "view", onViewFlow }` offers `見る` (`custom-flow-<key>-view`) on every flow (the edit view's chooser, for a reader who may not update, whose edit view opens the draft to read);
|
|
573
|
+
`{ kind: "edit", onEditFlow }` offers `編む` (`custom-flow-<key>-edit`) on every flow (the edit view's chooser); `{ kind: "edit_and_copy", onEditFlow, onCopyFlow }` adds `写して作る…` (`custom-flow-<key>-copy`) on a flow with a published version, calling `onCopyFlow({ ref: { flow_key, version }, title })` with its latest version.
|
|
574
|
+
Every flow's buttons carry the same visible words, so each one's accessible name starts with the flow it acts on, written for a screen reader alone (`<title> (<key>) を見る`, `<title> (<key>) を編む`, `<title> (<key>) 版 <n> を写して作る…`; WCAG 2.4.6 and 2.5.3).
|
|
575
|
+
- **Empty** (`custom-flow-list-empty`): `自前の流れはまだありません。`, followed, where the list offers `編む`, by `流れの目録で流れを選び、「写して作る…」でその流れを写すと作れます。` (copying is the only way a custom flow is made, and a reader who may not update cannot copy).
|
|
576
|
+
|
|
577
|
+
```tsx
|
|
578
|
+
<CustomFlowList list={flowDraftList} operations={{ kind: "edit", onEditFlow: openEditor }} />
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 21. `FlowEditor`
|
|
582
|
+
|
|
583
|
+
The editor of one custom flow's draft (`flow-editor`; `GET /ai/pipeline/flow-drafts/{flow_key}/draft`, `@aiquants/enrichment-core`'s `FlowDraftAnswer`): its states, its toolbar, the reasons publishing would meet, the flow's own fields, and the draft figure and the node table beside the selected node's fields.
|
|
584
|
+
|
|
585
|
+
Props, all required: `host` (`PipelineEditorHost`), `steps` (the step catalogue, `GET /ai/pipeline/steps`), `opened` (the draft, read once when the editor mounts, so a host draws it with the flow's key as its React key), `canUpdate` (the reader's `storage_admin:update`, limited to neither rows nor columns) and `headingRef` (the host's ref of the heading, where it moves the focus when it draws the editor in place of something else). A reader without the right sees the same draft without the toolbar, the fields' controls or the way to add a node, and keeps no work copy.
|
|
586
|
+
|
|
587
|
+
- **Heading** (`flow-editor-heading`, an `h2` of the flow's `title.ja`, `tabIndex={-1}`, the host's `headingRef`): the key line `鍵 <key> · 文書の流れ` or `鍵 <key> · 子の読みの流れ` (`flow-editor-key`), and the states (`flow-editor-state`, tint `Pill`s `flow-editor-state-<state>` with `data-draft-state`): the list's states (`customFlowStatesOf`), then `保存していない変更` while the work differs from the saved draft.
|
|
588
|
+
- **Toolbar** (`flow-editor-toolbar`, a `Toolbar` named `下書きの操作`): `元に戻す` and `やり直す` (`flow-editor-undo`, `flow-editor-redo`; `元に戻す手はありません。` / `やり直す手はありません。` without a step), `下書きを保存` (`flow-editor-save`, busy `保存しています…`; `保存していない変更はありません。` without one), `公開…` (`flow-editor-publish`), `版から戻す…` (`flow-editor-restore`; `公開した版がありません。` before the first publication), `題を変える…` (`flow-editor-title`) and `退役…` or `退役を解く…` (`flow-editor-retire`).
|
|
589
|
+
`公開…` waits for saved work and a finished check without reasons, and says which is missing (`保存していない変更があります。先に下書きを保存してください。`, `確かめが終わるまで公開できません。`, `確かめられなかったので公開できません。「もう一度確かめる」で確かめ直してください。`, `公開できない理由が <n> 件あります。`); `題を変える…` and `退役…` wait for saved work (`保存していない変更があります。先に下書きを保存するか、元に戻してください。`), since both act on the saved draft. An unavailable button keeps its focus and says why (the foundation's `unavailable`).
|
|
590
|
+
Ctrl or ⌘ with Z undoes, with Shift and Z redoes, and with S saves unsaved work while no command is pending (`editorShortcutOf`); a press typed in a text field is left to the field (a checkbox or a radio button is not one).
|
|
591
|
+
- **Notices and failures**: `この窓では作業の写しを残せないので、保存せずに閉じると作業が消えます。` (`flow-editor-work-copy-refused`, `warning`) when `sessionStorage` refuses the work copy; after a re-read, `ほかの人が下書きを変えたので、読み直しました。` (a publication, a retirement or a title refused with 412 / 4120 `PIPELINE_EDIT_STALE`) or `公開の直前の確かめが公開できない理由を見つけたので、下書きを読み直しました。理由の一覧を確かめてください。` (422 / 42210 `PIPELINE_RULES_BROKEN`), both `flow-editor-notice` (`info`) until the next edit;
|
|
592
|
+
and a failed command or re-read (`flow-editor-failure`, an `AlertMessage`) in the console's one failure rule (`labels.failureSentence`): `自前の流れを編む権限がありません。` for a refusal of the right, else `下書きを保存できませんでした (<reason>)。` or `下書きを読み直せませんでした (<reason>)。`.
|
|
593
|
+
- **Reasons** (`flow-editor-issues`, `IssueSummary`): the reasons the check of the latest answered round found (`POST /ai/pipeline/flow-drafts/{flow_key}/checks`), numbered, each with its place and a link that selects its node and moves focus to its field (a reason of the node as a whole to the node's reasons at the head of its fields), the editor moving there itself since the browser's move would drop the focus at a field's wrapper.
|
|
594
|
+
For a reader without the update right, whose detail draws no fields, a link leads to the node's heading, or to the flow's own line, and so does a reason of a field that draws no control (the parameters of a step that cannot be edited here). With none it says `公開できない理由はありません (写した元との違い <n> 件)。` (after the first publication `公開した版との違い`).
|
|
595
|
+
While a check is in flight (`確かめています。`) or after it failed (the failure's sentence, with `もう一度確かめる`), the previous round's reasons stay listed and the sentence says so. A change of the reasons is announced (`公開できない理由は <n> 件です。` / `公開できない理由はありません。`).
|
|
596
|
+
- **Flow fields** (`flow-editor-wide`, `FlowWideFields`): the flow's inputs as the draft's answer names them (`流れの入力`, which the editor does not change) and its terminal nodes (`終わりの節`, a choice among the body's nodes).
|
|
597
|
+
- **Master and detail** (`flow-editor-master-detail`, the foundation's `MasterDetail`): the master holds the draft figure and the node table, the detail the selected node's fields (`flow-editor-detail`).
|
|
598
|
+
The figure (`flow-editor-figure`, `FlowFigure`, captioned `<title> の下書きの図`) is drawn from the latest answered round's body and the check's figure (`DraftFigure`), under `前の手の図です。` (`flow-editor-figure-previous`) while a newer round waits; when the check found a cycle or a name used twice it is replaced by `図は描けません (輪か重なった名があります)。公開できない理由から直してください。` (`flow-editor-figure-blocked`).
|
|
599
|
+
Its boxes draw no gate state: each names its step (`stepTitleOf`), its node and its count of reasons (`理由なし`, `理由 <n> 件`), `danger` with reasons and `neutral` without, with `data-flow-issue-count`; the keys and the selection are the viewer's.
|
|
600
|
+
The node table (`flow-editor-node-table`, a `DataTable` captioned `節の表`, rows `flow-editor-node-row` with `data-node-id` and `data-selected`) lists the current body's nodes: `順`, the node as a disclosure button `節 <id>` (`flow-editor-node-select`, `aria-expanded`, `aria-controls` naming the detail) with its step's title under it, and `理由の数` (`確かめ待ち` for a node the answer has not seen). With `canUpdate`, `節を足す…` (`flow-editor-add-node`) follows it.
|
|
601
|
+
The detail holds the selected node's fields (`flow-editor-node`, `FlowNodeEditor`: the reasons of the node as a whole at its head (a duplicate name, an unregistered step, an output nothing reads and the like; `flow-editor-node-node-issues`, which takes the focus), then its name, its step (`<title> (<key> 版 <version>)`), its inputs, the nodes it waits for, its parameters and its gates (`門 <n>`), each with its description and its reasons) or `表か図で節を選ぶと、ここに節の欄を示します。` (`flow-editor-detail-empty`). Removing a node moves focus to the node now in its row, or to `節を足す…` when it was the last.
|
|
602
|
+
The widths come from `flowEditorWidths(formatLocale)`: the position column fits the largest position (`FLOW_NODES_BOUND`), the count column the widest count and `確かめ待ち`;
|
|
603
|
+
the master's minimum is the wider of the history panel's master (`HISTORY_MASTER_MIN_PX`, a line of 20 full-width characters) and the table's frame, and the detail's minimum is the master's times φ, not rounded to the lattice (the foundation gives the master the golden share of the container), so both panes reach their minimums at the width where they come side by side (about 840.8 px for the 312 px master and the 24 px gap) and are split exactly 1:φ at every width from there on;
|
|
604
|
+
a locale whose digits need more room widens the count column, and the master's and detail's minimums follow it.
|
|
605
|
+
- **Dialogs**: `公開…`, `退役…` and `退役を解く…` open `FlowChangeDialog` (`flow-editor-change`; titled `流れを公開する` / `流れを退役させる` / `流れの退役を解く`, busy `公開しています…` / `退役させています…` / `退役を解いています…`).
|
|
606
|
+
Publishing compares the saved draft with the latest published version, or before the first publication with the source it was copied from (`比べる元は公開した版 <n> です。`, `比べる元は写した元 (<flow> 版 <v>) です。`; when the copied version's body is gone, `写した元の版 <n> の構造は残っていないので、今の版 <m> と比べています。`, `flow-editor-change-compare-moved`), node by node (`足す節`, `外す節` and `変わる節` with each changed field's before and after, the terminal nodes and the flow's inputs), and counts the rules that name the flow (`この流れを名指す規則 <n> 本 (次の試みからこの版が走ります)。`). Retiring lists those rules and waits until none stands (`この流れを名指す規則が <n> 本あります。規則を外してから退役させてください。`).
|
|
607
|
+
`版から戻す…` opens `RestoreVersionDialog` (`flow-editor-restore-dialog`), which lists the published versions newest first (`版 <n>`), says under them who published the chosen one and when (`版 <n> は <published_by> が <instant> に公開しました。`, `flow-editor-restore-dialog-published`, the instant a `RecordedInstant`), reads the chosen version (`readFlowVersion`) and copies its body into the work as one step that undo reverts (`版 <n> の構造を作業へ写しました。元に戻すで戻せます。`);
|
|
608
|
+
`題を変える…` opens `FlowTitleDialog` (`flow-editor-title-dialog`), which saves the titles with the saved body (`題を変えました。`); `節を足す…` opens `AddNodeDialog` (`flow-editor-add-dialog`), whose node joins the end of the flow unconnected and is selected with focus on its heading.
|
|
609
|
+
After a publication, a retirement or a reinstatement the editor reads the draft again, starts from it and announces the dialog's sentence (`版 <n> を公開しました。` and the like).
|
|
610
|
+
- **Work copy and conflicts**: with `canUpdate`, the unsaved work is kept in `sessionStorage` under `workCopyKey(host.workCopyScope, "flows/<key>")` (`flowWorkCopyKey`), with the row version it started from. Opening the flow again at that row version restores it; at another one (someone saved since), the dialog `保存していない作業と、ほかの人の保存が食い違います` (`flow-editor-conflict`) shows how the kept work differs from that save (`flow-editor-conflict-changes`) and offers `残した作業を採る` (`flow-editor-conflict-keep`; closing the dialog keeps it too) and `残した作業を捨てる` (`flow-editor-conflict-drop`).
|
|
611
|
+
A save refused with 412 / 4120 `PIPELINE_EDIT_STALE` reads the draft again and offers the same choice.
|
|
612
|
+
|
|
613
|
+
```tsx
|
|
614
|
+
<FlowEditor key={flowKey} host={pipelineEditorHost} steps={stepCatalog} opened={flowDraft} canUpdate={canUpdatePipeline} headingRef={editorHeadingRef} />
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### 22. `BindingRulesEditor`
|
|
618
|
+
|
|
619
|
+
The editor of a tenant's binding rules (`binding-editor`; `GET /ai/pipeline/bindings`, `BindingsAnswer`): which flow a document's run and a child's reading take.
|
|
620
|
+
|
|
621
|
+
Props, all required: `host`, `opened` (the rules, read once when the editor mounts), `catalog` (the built-in flows' titles and purposes), `drafts` (`FlowDraftList`: every custom flow of the tenant, published or not, retired or not), `sources` (`SourceCatalog`, whose types a scheme condition offers), `canUpdate` and `headingRef` (as the flow editor's). A reader without the right sees the draft as words, without the toolbar, the rows' operations, the fields' descriptions or the ways to add a rule, keeps no work copy, and keeps the trial.
|
|
622
|
+
|
|
623
|
+
- **Heading** (`binding-editor-heading`, `h2` `結び付けの規則`, `tabIndex={-1}`, the host's `headingRef`): the states (`binding-editor-state`, tint `Pill`s `binding-editor-state-<state>` with `data-draft-state`, in the editors' words and tones): `未公開` before the first revision, else `今の改訂 <n>`; `下書きに未公開の変更` while the draft's rules differ from the current revision's; `保存していない変更` while the work is unsaved.
|
|
624
|
+
Under them, who published the current revision and when (`binding-editor-published`: `今の改訂 <n> は <published_by> が <instant> に公開しました。`, the instant a `RecordedInstant`, `binding-editor-published-at-value`), and `規則は <n> 本です (公開できるのは <max_rules> 本まで)。` (`binding-editor-count`).
|
|
625
|
+
- **Toolbar** (`binding-editor-toolbar`, a `Toolbar` named `規則の操作`): `元に戻す`, `やり直す`, `下書きを保存` (busy `保存しています…`; unavailable without unsaved changes, or while the work has shape errors: `形の誤りが <n> 件あります。直すと保存できます。`) and `公開…` (on the flow editor's terms), with the flow editor's keys.
|
|
626
|
+
- **Notices and failures**: as the flow editor's (`binding-editor-work-copy-refused`, `binding-editor-notice`, `binding-editor-failure`), in the rules' words: `ほかの人が規則を変えたので、読み直しました。`, `公開の直前の確かめが公開できない理由を見つけたので、規則を読み直しました。理由の一覧を確かめてください。` and `結び付けの規則を編む権限がありません。`.
|
|
627
|
+
- **Reasons** (`binding-editor-issues`, `IssueSummary`): as the flow editor's, with `公開できない理由はありません (今の改訂と違う規則を持つ順 <n> 個)。` (`空の組と…` before the first revision) and a fourth state: the work's shape is checked by the core's schema on every edit (`bindingShapeOf`), and while it has errors the region lists them alone (`形の誤りが <n> 件あります。直すまで、確かめ・試し・保存・公開を送りません。`) and nothing is checked, saved or tried.
|
|
628
|
+
A rule's reason links to its row's field only while that row still holds the rule the reason was found on (`bindingIssueTarget`).
|
|
629
|
+
- **How rules match** (`binding-editor-guide`, `h3` `規則の当たり方`): `規則は目的ごとに表の上から順に試し、有効な規則のうち、設定した条件が全て成り立つ最初の規則の流れが走ります。どの規則にも当たらなければ、組み込みの既定の流れが走ります (「どれにも当たらない時」)。下書きは公開するまで走りません。` (`binding-editor-guide-order`), and, with `canUpdate`, every field's description (`binding-editor-field-descriptions`, WCAG 3.3.5).
|
|
630
|
+
- **Rule tables**: per purpose a `CardSection` (`h3`; `binding-editor-document` `文書の規則`, `binding-editor-child_reading` `子の読みの規則`) holding a `DataTable` (`binding-editor-<purpose>-table`, rows `binding-editor-<purpose>-row` with `data-binding-rule="<purpose>-<position>"`) of the purpose's rules in trial order.
|
|
631
|
+
With `canUpdate` the columns are `bindingRuleColumns`: `順` with `▲` and `▼` (`binding-editor-<purpose>-rule-move-up` / `-move-down`, named `<rule>を上へ動かす` / `<rule>を下へ動かす`, the glyphs hidden from a screen reader), `有効` (a checkbox), `条件` (a group of the scheme, the URI prefix, the document kind and, for a child's reading, the asset type, each offering `(条件にしない)`), `流れ` (a `Select` of `(選んでいない)` and the purpose's published flows in service, a current value outside them written with why: `<title> (退役)`, `(未公開)`, `(子の読みの流れ)` or `(流れに無い)`) and `操作` (`外す`, named `<rule>を外す`);
|
|
632
|
+
without it the same rules as words (`bindingRuleTextColumns`).
|
|
633
|
+
An empty table says `文書の規則はありません。どの文書にも、文書の種類の組み込みの流れが走ります。` / `子の読みの規則はありません。どの子にも、子の種類の組み込みの子の流れが走ります。`.
|
|
634
|
+
Each table has its own `文書の規則を足す…` / `子の読みの規則を足す…` (`binding-editor-<purpose>-add`), unavailable once the two tables hold `BINDING_RULES_BOUND` (1,000) rules together (`規則は 2 つの表を合わせて 1,000 本までです。`). It opens `AddRuleDialog` (`binding-editor-add-dialog`): the rule's conditions, checked on the spot (`URI の接頭辞は <型>:// で始めて / で終え、源の型を条件にする時はその型で始めてください。`), and its flow; the rule joins the end of its table (`<rule>を足しました。`).
|
|
635
|
+
- **Defaults** (`binding-editor-defaults`, `h3` `どれにも当たらない時`): `どの規則にも当たらない文書と子は、組み込みの既定の流れを走ります。既定は規則で上書きするだけで、ここでは変えられません。` (`binding-editor-defaults-summary`), and the answer's `defaults` in two tables, `文書の種類ごとの既定` (`binding-editor-default-document`) and `子の種類ごとの既定` (`binding-editor-default-child`), each a kind and its built-in flow.
|
|
636
|
+
- **Trial** (`binding-editor-trial-section`, `h3` `試す`; `BindingTrialPanel`, `binding-editor-trial`): a document's URI, its MIME type, the purpose (`文書の実行` or `子の読み`) and, for a child's reading, the child's asset type (a child's reading gives the URI of its tree's root document and the child's MIME type), resolved at once by the current revision and by the editor's set (`POST /ai/pipeline/bindings/trials`, which writes nothing and needs only the read right): the operator's work (`今の作業`), or the draft for a reader (`下書き`).
|
|
637
|
+
The fields are checked by the core's `BindingTrialSchema` when a trial is asked for, each error at its field and the first taking the focus; while the work has shape errors the trial is unavailable (`作業に形の誤りがあるので試せません。形の誤りを直してください。`).
|
|
638
|
+
The result names the document tried and sets the rule each chooses and the flow it runs side by side (`binding-editor-trial-grid`, a `ComparisonGrid`), says so once the work has changed since (`この結果は試した時の作業のものです。その後に作業が変わりました。`) or a revision has been published since (`この結果は試した時の改訂のものです。その後に規則が公開されました。`), and is announced (`試しました。今の改訂 <n>: 流れ「<flow>」、今の作業: 流れ「<flow>」。`).
|
|
639
|
+
- **Publishing** (`BindingPublishDialog`, `binding-editor-publish-dialog`, titled `規則を公開する`): the saved draft against the current revision (`空の組` before the first one), rule by rule at each position of each purpose, every changed position marked (`<n> 番 (足す)`, `(外す)` or `(変わる)`), with the count of positions that differ (rules are tried in order, so a moved rule differs at both of its positions) and `公開する` (busy `公開しています…`).
|
|
640
|
+
A publication is announced (`改訂 <n> を公開しました。`), and the editor reads the rules again and starts from them.
|
|
641
|
+
- **Work copy and conflicts**: as the flow editor's, under `workCopyKey(host.workCopyScope, "bindings")`, the dialog (`binding-editor-conflict`) setting the other save (`ほかの人が保存した下書き`) against the kept work (`残した作業`).
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
<BindingRulesEditor host={pipelineEditorHost} opened={bindings} catalog={flowCatalog} drafts={flowDraftList} sources={sourceCatalog} canUpdate={canUpdatePipeline} headingRef={editorHeadingRef} />
|
|
645
|
+
```
|
|
646
|
+
|
|
420
647
|
## Variant Sets (`variantPresentation.ts`, `variantHost.ts`)
|
|
421
648
|
|
|
422
649
|
The words every variant component draws, each a total map over `@aiquants/enrichment-core`'s vocabulary: `PROJECTION_VARIANT_STATE_LABELS` and `PROJECTION_VARIANT_STATE_TONES`, `EMBEDDING_PROVIDER_LABELS`, `SPARSE_MODIFIER_LABELS`, `COMPARISON_MODE_LABELS`, `VARIANT_PROJECTION_SCOPE_LABELS`, with `PRODUCTION_VARIANT_LABEL` / `variantKeyLabel`, `variantSetOptionLabel`, `VARIANT_STATE_UNREAD_TEXT`, `VARIANT_UNCOVERED_STATE_TEXTS`, `VARIANT_SET_NO_DOCUMENT_TEXT`, `VARIANT_COMMAND_FORBIDDEN_SENTENCE` and `variantProjectionActiveSentence`.
|
|
@@ -428,10 +655,54 @@ The host contracts are `VariantSetCommands` (the board's four commands: `create`
|
|
|
428
655
|
## Search Exchange Shapes (`documentSearch.ts`)
|
|
429
656
|
|
|
430
657
|
The shapes the document search exchanges with its host, field for field the public API's answers, and the words both search workbenches are drawn with:
|
|
431
|
-
`DocumentSearchParams` (`threshold` is sent with `DENSE` only; `fusion`, `rrf_k` and `
|
|
432
|
-
and `DocumentSearchParams.variant_key` (
|
|
658
|
+
`DocumentSearchParams` (`threshold` is sent with `DENSE` only; `fusion`, `rrf_k`, `alpha` and `dense_sparse_fusion` with `HYBRID` only), `DocumentSearchAnswer` (`hits: DocumentSearchHit[]`, `authz_scopes`, `search_metadata: DocumentSearchMetadata`), `DocumentSearchHit` (every column `null` when the caller's scope hides it, `graph_relations` `null` while a row scope applies or the column scope hides the body), `DocumentScores` (`fused`, `vector: DocumentVectorScore | null`, `lexical_rank`, `graph_rank`),
|
|
659
|
+
and the two keys sent only away from the API's default: `DocumentSearchParams.variant_key` (a set other than production) and `DocumentSearchParams.dense_sparse_fusion` (a fusion other than `DEFAULT_DENSE_SPARSE_FUSION`). The comparison exchanges the core's `VariantComparisonRequest` and `VariantComparisonAnswer` as they are.
|
|
433
660
|
The document search's `degradation_reasons` is `readonly string[]`, not the closed vocabulary, so an answer from a newer API that adds a code is still taken and the code shown as it is; `DEGRADATION_REASON_LABELS` words every code of the core's vocabulary, `dense_embedding_unavailable` (the query's embedding endpoint was not reached, so the dense tier was skipped) included.
|
|
434
|
-
The words: `SEARCH_MODE_LABELS`, `SEARCH_FUSION_LABELS`, `QUALITY_TIER_LABELS`, `BREAKER_STATE_LABELS`, `DEGRADATION_REASON_LABELS` / `degradationReasonText`, `HIDDEN_FIELD_TEXT`, and the value helpers `vectorScoreText`, `SCORE_DECIMALS` (3), `rankAgreement` / `RankAgreement` and `variantHitKey`.
|
|
661
|
+
The words: `SEARCH_MODE_LABELS`, `SEARCH_FUSION_LABELS`, `DENSE_SPARSE_FUSION_LABELS`, `QUALITY_TIER_LABELS`, `BREAKER_STATE_LABELS`, `DEGRADATION_REASON_LABELS` / `degradationReasonText`, `HIDDEN_FIELD_TEXT`, and the value helpers `vectorScoreText`, `SCORE_DECIMALS` (3), `rankAgreement` / `RankAgreement` and `variantHitKey`.
|
|
662
|
+
|
|
663
|
+
## Pipeline Editor Host (`pipelineEditorHost.ts`)
|
|
664
|
+
|
|
665
|
+
The routes the editors send and read again through (`PipelineEditorHost`), one editing route each under `/ai/pipeline`. The first reads a view opens with (the step catalogue, the flow catalogue, the custom flows' list, a draft and the rules) are the loader's, so the components take them as props; the routes, the transport and the authorization are the host's.
|
|
666
|
+
`workCopyScope` names the tenant and the operator the unsaved work belongs to, in the host's words (one string per pair, e.g. the tenant's and the operator's ids). The editors key their work copies by it (`workCopyKey`, which throws `RangeError` for an empty scope), so a window's storage, which outlives a sign-out, never restores one operator's work to another, nor one tenant's to another. A host confirms before its sign-out while any copy is kept and clears every copy during it, both with the light entry `@aiquants/enrichment-react/work-copies` (below).
|
|
667
|
+
|
|
668
|
+
| Route | Request | Answer |
|
|
669
|
+
| --- | --- | --- |
|
|
670
|
+
| `publishFlow(flowKey, request)` | `POST /flows/{flow_key}/versions` | `FlowPublished` (201) |
|
|
671
|
+
| `setFlowRetirement(flowKey, request)` | `PUT /flows/{flow_key}/retirement` | `FlowDraftSummary` |
|
|
672
|
+
| `writeFlowDraft(flowKey, request)` | `PUT /flow-drafts/{flow_key}/draft` | `FlowDraftAnswer` (the draft as saved, or as a copy made it) |
|
|
673
|
+
| `checkFlowDraft(flowKey, request)` | `POST /flow-drafts/{flow_key}/checks` | `FlowDraftCheck` |
|
|
674
|
+
| `writeBindingDraft(request)` | `PUT /bindings/draft` | `BindingsAnswer` |
|
|
675
|
+
| `publishBindings(request)` | `POST /bindings/revisions` | `BindingRevisionAnswer` (201) |
|
|
676
|
+
| `checkBindings(request)` | `POST /bindings/checks` | `BindingSetCheckAnswer` |
|
|
677
|
+
| `tryBindings(request)` | `POST /bindings/trials` | `BindingTrialAnswer` |
|
|
678
|
+
| `readFlowDrafts(signal)` | `GET /flow-drafts` | `FlowDraftList` |
|
|
679
|
+
| `readFlowDraft(flowKey, signal)` | `GET /flow-drafts/{flow_key}/draft` | `FlowDraftAnswer` |
|
|
680
|
+
| `readBindings(signal)` | `GET /bindings` | `BindingsAnswer` |
|
|
681
|
+
| `readFlowVersion(flowKey, version, signal)` | `GET /flow-drafts/{flow_key}/versions/{version}` | `FlowVersionBody` |
|
|
682
|
+
|
|
683
|
+
Every command resolves to a `PipelineEditOutcome<T>` (`CommandOutcome<T, PipelineEditRejection>`), whose `rejection` names the refusal the host read from the upstream status and business code (`pipelineEditCommandFailureCodeOf` of `@aiquants/enrichment-core`): `STALE` (412 / 4120 `PIPELINE_EDIT_STALE`), `RULES_BROKEN` (422 / 42210 `PIPELINE_RULES_BROKEN`), `NOT_FOUND` (404 / 4040 `NOT_FOUND`), `REQUEST_INVALID` (422 / 4222 `REQUEST_INVALID`) or `FORBIDDEN` (403 / 4031 `PERMISSION_DENIED`).
|
|
684
|
+
A refusal the table does not name is no rejection (`null`), and the screen words it from the outcome's status and reason. Every read again resolves to a `ResourceOutcome` and takes the editor's abort signal (a superseded read is aborted and may reject with `AbortError`). No route throws for a failure it can describe.
|
|
685
|
+
|
|
686
|
+
## Work Copies Entry (`@aiquants/enrichment-react/work-copies`)
|
|
687
|
+
|
|
688
|
+
The editors keep their unsaved work in the tab's `sessionStorage` until it is saved, under keys that start with `PIPELINE_EDITOR_WORK_COPY_KEY_PREFIX` (`@aiquants/enrichment-react:pipeline-editor:`) and go on to name the work's scope and target (`workCopyKey`).
|
|
689
|
+
A sign-out discards that work, so a host confirms first while there is any (WCAG 3.3.6): `hasPipelineEditorWorkCopies()` answers `true` while this tab keeps at least one key under the prefix, whatever tenant and operator it names.
|
|
690
|
+
It reads no work copy's body, so a copy the editors can no longer read counts too.
|
|
691
|
+
For the next reader of a shared browser, the host's sign-out then clears the work with `clearPipelineEditorWorkCopies()`: every key under the prefix leaves this tab's `sessionStorage`, and every other key and `localStorage` stay.
|
|
692
|
+
The two collect the same keys, so the question finds exactly what the clearing removes.
|
|
693
|
+
Neither throws: outside a browser the question answers `false` and the clearing does nothing; when the browser refuses to open the storage, the question answers `false` (the editors cannot keep work there either), and the clearing reports a storage it cannot open or change with `console.warn` and leaves it as it is, so the sign-out still signs out.
|
|
694
|
+
The entry imports nothing (no component and no `@aiquants/enrichment-core`), so a layout or a root route that every page loads can import it statically.
|
|
695
|
+
In the sample, `openSignOutConfirmation` and `signOut` stand for the host's own.
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
import { clearPipelineEditorWorkCopies, hasPipelineEditorWorkCopies } from "@aiquants/enrichment-react/work-copies"
|
|
699
|
+
|
|
700
|
+
const onSignOutPressed = () => (hasPipelineEditorWorkCopies() ? openSignOutConfirmation() : signOut())
|
|
701
|
+
|
|
702
|
+
const clearOnSignOut = () => {
|
|
703
|
+
clearPipelineEditorWorkCopies()
|
|
704
|
+
}
|
|
705
|
+
```
|
|
435
706
|
|
|
436
707
|
## Colour Contrast
|
|
437
708
|
|
|
@@ -446,10 +717,19 @@ The package keeps no luminance computation or contrast bar of its own, the colou
|
|
|
446
717
|
No class inside the Qdrant card's badge can change what it paints (a colour, `opacity`, a filter, a blend mode, a mask, an animation or any arbitrary property such as `[color:…]`, under any variant).
|
|
447
718
|
On the card's elements around it there is no compositing class (`opacity`, a filter, a blend mode, a mask, an animation), no arbitrary property, and no class that paints the elements inside (a colour or compositing utility under a variant that reaches them: `*:`, `**:`, `marker:`, `selection:` or an arbitrary variant with `&` such as `[&_p]:`), which would set the badge's paint directly, whatever the order of the host's layers.
|
|
448
719
|
- **Pipeline tab:** every state `EnrichmentStrategyBoard` (read with every gate state, a tenant and a deployment-default row and an inactive strategy; empty; the forbidden note; a change in flight) and `InfrastructureHealthMonitor` (all connected; some failed with error texts and unmeasured values; re-checking; without the button) draw passes `scanColourRoles` over the card as exactly that state's set of pairs (the gate words are `success-fg`, `warning-fg` or `fg-muted` on the column header's `subtle`).
|
|
449
|
-
Every state `PipelineFlowViewer` draws (read with every gate state as a node tone; a selected node with its step, gates, override and measured figures; a node without gates or a metric; an unmeasured gate's node with unmeasured values)
|
|
720
|
+
Every state `PipelineFlowViewer` draws (read with every gate state as a node tone; a selected node with its step, gates, override and measured figures; a node without gates or a metric; an unmeasured gate's node with unmeasured values; a selected node the drawn version lacks; the custom flows listed with every state's pill (`公開した版 <n>` `info`, `下書きに未公開の変更` `warning`, `未公開` and `退役` `neutral`, `今の段で走らない` `danger`) and their buttons, and a catalogue that hides them, said in an information notice; a version the operator may not read and one that could not be read, both drawn without a figure)
|
|
450
721
|
passes `scanColourRoles` over the viewer as exactly that state's set of text pairs and of line pairs (`measuredGraphics`: a plate's accent on the figure's `surface` and on its own tint, `border-control` for the edges, the arrowheads and an unmeasured gate's plate, `focus` for the rings), reading every SVG shape of the figure (`graphics`); a node's notes are `fg`, the one text role every face a node takes carries.
|
|
722
|
+
Every state `PipelineRunFlowViewer` draws (seven states of a run with a shape, and a run without one, which draws its notices and its timeline only) passes the same scan over the viewer as exactly that state's set of text pairs and of line pairs, reading every SVG shape of the figure (two arrowheads, one shape per edge and two per node).
|
|
723
|
+
- **Editors:** every state the two editors draw on the console foundation, with their dialogs and trial, passes `scanColourRoles` over its root as exactly that state's set of text pairs and of line pairs, reading every shape.
|
|
724
|
+
`FlowEditor` is drawn as a writer's first draft with its toolbar, the reasons a control is unavailable and the ready sentence; with a node with gates and parameters open; as a draft with reasons, their links and a node-wide reason open; as a flow published to version 2 whose draft differs, retired and not runnable; and for a reader with a node open.
|
|
725
|
+
`BindingRulesEditor` is drawn with nothing published, as a draft that differs from the current revision, as a draft with reasons, and for a reader of a used and a stopped rule.
|
|
726
|
+
The dialogs and the trial are `FlowChangeDialog` with the rules that name the flow read, `RestoreVersionDialog` with a version chosen, `BindingPublishDialog`, `CopyFlowDialog` as it opens and with a key of the wrong form, `AddNodeDialog`, `AddRuleDialog`, `FlowTitleDialog`, and `BindingTrialPanel`'s result.
|
|
727
|
+
Only the flow editor's figure draws shapes (a plate's accent, `danger-accent` for a node with reasons, `border-control` for the edges and arrowheads, `focus` for the rings); every other state draws none.
|
|
728
|
+
The state pills take `DraftStatePills`' tones (`公開した版 <n>` and `今の改訂 <n>` `info`, `下書きに未公開の変更` `warning`, `未公開`, `退役` and a stopped rule `neutral`, `今の段で走らない` `danger`, a used rule `success`); the package's explanations are `fg-secondary` and the console's field descriptions and a control's unavailability reasons `fg-muted`, both on `surface`, while a dialog's title and description are the console `Dialog`'s `fg`;
|
|
729
|
+
a neutral pill and a key field's prefix are `fg-secondary` on `sunken`; errors and reasons are `danger-fg`; a selected node is `fg` on `info-tint` and a node with reasons `fg` on `danger-tint`; the diff pieces are `fg` on `danger-tint` / `success-tint` (`InlineChange`).
|
|
451
730
|
- **Monitoring cards:** every state `SyncStatusMonitor`, `QdrantStorageCard` and `OntologyStatusCard` draw (read; empty; unmeasured; a refused list; a retry in flight, the pressed one and the others pointing at the note; unavailable controls with their reasons; the forbidden note; a selected and an unselected row; a failed probe) passes `scanColourRoles` over the card as exactly that state's set of pairs, so a text drawn in another role (a failure pill in a neutral tone, a selected row's text in `fg-secondary`, which `info-tint` does not carry) fails.
|
|
452
|
-
- **Ingest tab and a run's step timeline:** every state `DocumentIngestConsole` (read rows of every kind with the restricted marks; an empty list with no source and no domain; a refused list; an invalid identifier; a reachable and an unreachable inspection; the restricted warning with the advanced settings open and labels added; a promotion in flight; the forbidden note) and `PipelineRunSteps` (steps of every status with their reports and meters; the report's detail open; a
|
|
731
|
+
- **Ingest tab and a run's step timeline:** every state `DocumentIngestConsole` (read rows of every kind with the restricted marks; an empty list with no source and no domain; a refused list; an invalid identifier; a reachable and an unreachable inspection; the restricted warning with the advanced settings open and labels added; a promotion in flight; the forbidden note) and `PipelineRunSteps` (steps of every status with their reports and meters; the report's detail open; a group with its alert and the records after it;
|
|
732
|
+
a group shown a page at a time with its `LoadMore`, and one read to its end; the terminal step)
|
|
453
733
|
draw passes `scanColourRoles` over the console or the timeline as exactly that state's set of pairs (a neutral pill is `fg-secondary` on `sunken`).
|
|
454
734
|
- **Qdrant storage card's selected look:** the four ratios of the focus ring, the tint and the start-edge bar (see `QdrantStorageCard`), recomputed in both schemes.
|
|
455
735
|
|
|
@@ -461,7 +741,7 @@ Colour pairs outside this section are not measured.
|
|
|
461
741
|
Exported for hosts that draw their own run tables, so a run reads the same everywhere.
|
|
462
742
|
|
|
463
743
|
- `ledgerStepLabel(key)` — the name of any step a run records: a released step's recorded label (`recordedStepOf`), the terminal step `record_document_state` as `RUN_TERMINAL_STEP_LABEL` (`文書の状態の記録`), and a key the current vocabulary does not know (an `UnknownTerm`) as `知らない段 (<key>)`.
|
|
464
|
-
`documentKindLabel(kind)` names a run's document kind the same way (`DOCUMENT_KIND_LABELS`, or `知らない種類 (<kind>)`). `PipelineRunSteps`
|
|
744
|
+
`documentKindLabel(kind)` names a run's document kind the same way (`DOCUMENT_KIND_LABELS`, or `知らない種類 (<kind>)`). In `PipelineRunSteps` a step with an unknown key is one card when it records the run's own document, and joins its node's group (its step's, when it names no node) when it records a child. The flow diagram and the step metrics never show the terminal step (no flow of the flow catalogue names it); only `PipelineRunSteps` does. `RUN_TERMINAL_STEP_OUTCOME_LABELS` words a succeeded terminal step's outcome.
|
|
465
745
|
- `documentHistoryPresentation` — the history's words for hosts: `FACET_LABELS`, `DOCUMENT_STATE_CAUSE_LABELS`, `facetCountsText(counts)`, `revisionLabel`, and the restricted marks' fixed texts `RESTRICTED_DOCUMENT_TEXT` (`隔離 (RESTRICTED) の文書`), `RESTRICTED_STATE_TEXT` and `RESTRICTED_RUN_TEXT` (`隔離されていた期間の実行`), so a host table that draws a mark row writes the same text.
|
|
466
746
|
|
|
467
747
|
- `syncStatusPresentation` — the words of every sync status (`SYNC_STATUS_LABELS`: `待機中`・`本文抽出中`・`AI 要約・構造化中`・`ベクトル/グラフ投影中`・`同期完了`, and a failure as `失敗: <status>`), the tone of its badge (`syncStatusTone`: a failure `danger`, `SYNCED` `success`, the rest `info`) and `NO_ENRICHMENT_RECORD_TEXT` (`取り込みの記録なし`), read by the recent-ingest table and the processing-objects list alike.
|
|
@@ -472,7 +752,13 @@ Exported for hosts that draw their own run tables, so a run reads the same every
|
|
|
472
752
|
- `PIPELINE_TRIGGER_LABELS` — operator-facing names of the triggers `PipelineTriggerSchema` allows: `ingest` (取り込み), `retry` (再試行), `resync` (再同期).
|
|
473
753
|
- `pipelineFlowLabel({ flowKey, flowVersion })` — the flow a run ran: `pdf 版 2`, or `pdf (版なし)` (`UNVERSIONED_FLOW_LABEL`) for a run recorded before flows had versions. The key is written as recorded (a built-in flow's key is its document kind, a child flow's its own, such as `child.read`); no table of flow names is copied here.
|
|
474
754
|
- `skipReasonLabel(text)` — reads the text through `parseSkipReason`: `gate_closed:<flag>` → `<flag> が無効`, `no_children` → `子アセットなし`, `source_skipped:<step>` → `<step label> が実行されなかった`, `nothing_to_read:<token>` → `NOTHING_TO_READ_LABELS[token]` (`no_pages` → `読む対象なし (ページなし)`, `no_pages_rendered` → `読む対象なし (描画できたページなし)`), and the terminal step's `superseded` → `実行の版が現在の版でなくなった`, `recording_disabled` → `記録の切替が無効`, `recording_misconfigured` → `記録の切替の設定の誤り`, and the unlock step's `not_protected` → `パスワードが付いていない`; text outside the grammar is returned verbatim.
|
|
475
|
-
A
|
|
755
|
+
A child's records — including a child skipped as `gate_closed:ENABLE_OCR` or `nothing_to_read:no_pages` — are grouped per node (per step, for a record that names no node) in the step timeline, in the tree order of their targets' tree paths, each with its own reason.
|
|
756
|
+
- `SKIP_REASON_KIND_SHORT_LABELS` / `skipReasonShortLabel(text)` — the short reason a skipped node of the run figure writes as its note, one per kind of `parseSkipReason`: `gate_closed` → `フラグが無効`, `no_children` → `子アセットなし`, `source_skipped` → `元の段が未実行`, `nothing_to_read` → `読む対象なし`, `not_protected` → `パスワードなし`, `superseded` → `版が現在でない`, `recording_disabled` → `記録の切替が無効`, `recording_misconfigured` → `記録の切替の誤り`; `null` for a text outside the grammar.
|
|
757
|
+
- `RUN_NODE_STATE_LABELS`, `RUN_NODE_STATE_TONES` and `RUN_NODE_STATE_OUTLINES` — the word, the `FigureNode` / `Pill` tone and the outline of each state a node of the run figure takes (the type `RunNodeState`): `running` `実行中`, `failed` `失敗` (`danger`), `cancelled` `取り消し`, `succeeded` `成功` (`success`), `skipped` `省略` (`warning`), `waiting` `待機中` and `not_run` `未実行`; every other tone is `neutral`, and only `waiting` and `not_run` are `dashed` (the rest `solid`).
|
|
758
|
+
- `CHILD_RECORD_FILTER_LABELS` and `childRecordCountsText(records)` — the child record table's filter words (`全て`, then each step status's label from `PIPELINE_STEP_STATUS_PRESENTATION`; a choice is the type `ChildRecordFilter`, `"all"` or a step status) and its counts, `<label> <n>` per status joined by `・` in the order succeeded, skipped, failed, cancelled, running.
|
|
759
|
+
- `formatElapsed(ms)` — an elapsed time: `<ms> ms` under a second, `<s>.<tenths> 秒` under a minute, `<m> 分 <s> 秒` under an hour, `<h> 時間 <m> 分` under 100 hours, else `100 時間以上`; a value that is not a non-negative safe integer throws `RangeError`. `ELAPSED_LONGEST_MS` holds the longest value of each form, and `ELAPSED_REVERSED_SENTENCE` (`経過を測れません (記録の時刻の前後が逆です)。`) and `ELAPSED_REVERSED_LABEL` (`測れません (前後が逆)`) state an elapsed time whose records' instants run backwards.
|
|
760
|
+
- `formatChildCount(count)` — a count of children, `<n> 件` with its digits grouped by threes up to 99,999 and `10 万件以上` beyond (`CHILD_COUNT_LONGEST`, the two longest forms); a value that is not a non-negative safe integer throws `RangeError`.
|
|
761
|
+
- `childFlowCountsText(flow)` — a run's child flow (`RunFlowChildFlow`) as `<pipelineFlowLabel>: <counts>`, the counts being its succeeded, failed, cancelled and running runs in the run statuses' words, joined by `・` (`child.read 版 4: 成功 12・失敗 1・取り消し 0・実行中 0`; a count of 0 is written too).
|
|
476
762
|
- `RecordedInstant({ iso, testId })` — a recorded instant in the root's zone, to the second (`2026/09/24 01:02:03`), in a `Measured` text that moves whole under `testId` (`data-measured="true"`, `data-cui-keep="whole"`, tabular figures, moved to the next line whole instead of breaking between the date and the time); `iso: null` is the root's unmeasured label in the same slot (`data-measured="false"`, `data-cui-keep="whole"`). A refused instant throws `ZonedTimeError` (see **Time zone is the root's**). Render it inside the Japanese console root, like every component that reads the root.
|
|
477
763
|
- `FinishedInstant({ iso, testId })` — a finish time that may still be open: `RecordedInstant` for a finished run or step, or the word `未完了` (`UNFINISHED_LABEL`) outside any slot while `iso` is `null`. It checks the root in both cases.
|
|
478
764
|
- The package publishes no function that writes a given instant as text: a host table draws its instants with these two, so they share the root's clock, the slot and its hook with every panel of the package.
|