pgkiln 0.69.0 → 0.71.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/db/migrations/136_vector_search.sql +252 -0
- package/db/migrations/137_create_page_types.sql +904 -0
- package/db/migrations/138_app_utilities.sql +153 -0
- package/docs/apex-feature-parity.md +10 -8
- package/docs/guide/03-builder.md +176 -4
- package/docs/guide/04-pages-and-regions.md +45 -3
- package/docs/guide/05-items.md +1 -1
- package/docs/guide/06-processing.md +18 -0
- package/docs/guide/09-reference.md +34 -1
- package/docs/guide/12-development.md +53 -3
- package/docs/guide/15-extensions.md +1 -1
- package/docs/guide/16-files.md +1 -1
- package/examples/hr/README.md +1 -0
- package/examples/hr/hr_72_vector_search.sql +158 -0
- package/examples/hr/hr_73_configuration_options.sql +17 -0
- package/examples/hr/hr_74_app_utilities.sql +22 -0
- package/package.json +1 -1
- package/public/app.css +9 -0
- package/public/builder.css +48 -0
- package/src/ai/embeddings.ts +197 -0
- package/src/ai/service.ts +13 -9
- package/src/app.ts +13 -0
- package/src/builder/advisor.ts +43 -3
- package/src/builder/ai-agents.ts +13 -5
- package/src/builder/app-updates.ts +3 -0
- package/src/builder/components.ts +24 -4
- package/src/builder/create-page.ts +259 -0
- package/src/builder/dependencies.ts +311 -0
- package/src/builder/features.ts +313 -0
- package/src/builder/home.ts +3 -0
- package/src/builder/i18n/de.ts +38 -0
- package/src/builder/i18n/en.ts +38 -0
- package/src/builder/i18n/es.ts +38 -0
- package/src/builder/i18n/fr.ts +38 -0
- package/src/builder/i18n/it.ts +38 -0
- package/src/builder/i18n/ja.ts +38 -0
- package/src/builder/i18n/ko.ts +38 -0
- package/src/builder/i18n/nl.ts +38 -0
- package/src/builder/i18n/pt.ts +38 -0
- package/src/builder/i18n/zh-cn.ts +38 -0
- package/src/builder/i18n/zh-tw.ts +38 -0
- package/src/builder/login-page.ts +17 -14
- package/src/builder/newapp.ts +41 -23
- package/src/builder/object-actions.ts +1 -0
- package/src/builder/routes.ts +13 -2
- package/src/builder/shared.ts +1 -1
- package/src/builder/sql.ts +3 -0
- package/src/builder/table-editor.ts +1 -0
- package/src/builder/ui-defaults.ts +161 -0
- package/src/builder/ui.ts +2 -1
- package/src/builder/upgrade.ts +154 -0
- package/src/builder/utilities.ts +181 -0
- package/src/builder/vector-column.ts +229 -0
- package/src/builder/vector-providers.ts +286 -0
- package/src/builder/wizards.ts +211 -26
- package/src/cli/replace.ts +11 -1
- package/src/history.ts +155 -0
- package/src/runtime/ai-agents.ts +53 -2
- package/src/runtime/builtin-components.ts +23 -2
- package/src/runtime/items.ts +4 -2
- package/src/runtime/search.ts +116 -12
- package/src/server.ts +3 -0
- package/src/vector.ts +288 -0
|
@@ -1160,7 +1160,8 @@ select empno as id, mgr as parent_id, initcap(ename) || ' · ' || initcap(job) a
|
|
|
1160
1160
|
```
|
|
1161
1161
|
|
|
1162
1162
|
Attributes: `expanded` (levels open at first, default 1), `link` (as for maps; `#id#` and any other
|
|
1163
|
-
column), `empty`.
|
|
1163
|
+
column), `empty`. (137) **Create page → Tree** makes one from a table: the key as the id, a foreign key to the
|
|
1164
|
+
table itself (`mgr` → `empno`) as the parent and the name column as the label. The tree is drawn on the server with `<details>`: it works without JavaScript,
|
|
1164
1165
|
and the browser's find-in-page opens closed branches. Clicking a label follows the link; the rest
|
|
1165
1166
|
of the row opens and closes the branch. Up to 5000 nodes.
|
|
1166
1167
|
|
|
@@ -1460,6 +1461,7 @@ with 26.1's *Metric Card* and group support). Show a region as `multiple` to get
|
|
|
1460
1461
|
| `ut_avatar` | Avatar: a picture or initials | `NAME`, `INITIALS`, `IMAGE` (a relative or https URL), `SIZE` (small, medium, large), `SHAPE` (circle, square) | An avatar group (overlapping) |
|
|
1461
1462
|
| `ut_badge` | Badge coloured by a state | `LABEL`, `STATE` (success, warning, danger, info, neutral; approved, pending, … are understood) | A row of badges |
|
|
1462
1463
|
| `ut_comments` | Comments | `USER`, `INITIALS`, `DATE`, `COMMENT`, `ACTIONS` (extra text) | One conversation |
|
|
1464
|
+
| `ut_content_row` | (137) Content row: initials or a picture, title (linked when the region has a link) and description, a secondary text and a badge on the right | `TITLE`, `DESCRIPTION`, `MISC`, `INITIALS`, `IMAGE`, `BADGE`, `STATE` | One list |
|
|
1463
1465
|
| `ut_media_list` | Media list: picture or initials, title (linked when the region has a link), description, badge | `TITLE`, `DESCRIPTION`, `INITIALS`, `IMAGE`, `BADGE`, `STATE` | One list |
|
|
1464
1466
|
| `ut_metric_card` | Metric card: a key figure with its unit, change and trend | `LABEL`, `VALUE`, `UNIT`, `CHANGE`, `TREND` (up, down, flat), `GOOD` (up or down: which direction shows green), `DESCRIPTION` | A row of cards |
|
|
1465
1467
|
| `ut_timeline` | Timeline: when, who, title, text, a coloured marker | `TITLE`, `WHEN`, `WHO`, `BODY`, `STATE` | One timeline |
|
|
@@ -1641,10 +1643,15 @@ Search configurations):
|
|
|
1641
1643
|
| Icon | An [icon name](09-reference.md#icons), or `#COLUMN#` for a column that holds icon names |
|
|
1642
1644
|
| Link | Where a result goes: `{"page": 3, "items": {"P3_ID": "#ID#"}}` (`#COLUMN#` is the row's value; the link gets a checksum, a modal page opens as a dialog) |
|
|
1643
1645
|
| Maximum results | Per configuration, 1–100 (default 10); "Showing the first *n* rows." when there are more |
|
|
1644
|
-
| Search type | (sprint 135) `standard`: the words as below · `fulltext`: PostgreSQL full-text search (table and sql sources; see [full-text search](#full-text-search))
|
|
1646
|
+
| Search type | (sprint 135) `standard`: the words as below · `fulltext`: PostgreSQL full-text search (table and sql sources; see [full-text search](#full-text-search)) · (sprint 136) `vector`: by meaning, the rows whose embedding is nearest to the search text's · `hybrid`: full-text and vector together (see [vector search](#vector-search)) |
|
|
1645
1647
|
| tsvector column or expression | `fulltext`: a `tsvector` column of the source (best: with a GIN index), or an expression such as `to_tsvector('english', title \|\| ' ' \|\| body)`. Empty: `to_tsvector` over the searchable columns on every search |
|
|
1646
1648
|
| Text search configuration | `fulltext`: `english`, `dutch`, `german`, `simple`… (`pg_ts_config`): the one the tsvector was made with. Empty: the application's primary language's, else `simple` |
|
|
1647
1649
|
| Did you mean | With the `pg_trgm` extension installed: when nothing is found, a link that searches for the closest words of the searched columns (any search type; reads up to 10,000 rows). Without `pg_trgm` nothing is suggested |
|
|
1650
|
+
| Vector provider | `vector`, `hybrid`: the [vector provider](03-builder.md#vector-providers) (static ID) that embeds the search text: the same one that embedded the rows |
|
|
1651
|
+
| Vector column | `vector`, `hybrid`: the source's `vector(n)` column, *n* the provider's dimensions |
|
|
1652
|
+
| Key column | `hybrid`: the key (e.g. `id`) that matches the full-text and the vector results |
|
|
1653
|
+
| Distance | `cosine` (default; most embedding models), `l2` or `inner_product`; an HNSW index helps only when it is built for the same one |
|
|
1654
|
+
| Minimum similarity | `vector`, `hybrid`: rows less similar than this are left out (cosine: 1 − distance; inner product: the product; l2: 1 / (1 + distance)). Empty: the nearest rows, however far |
|
|
1648
1655
|
|
|
1649
1656
|
**How it searches.** Each word of the search text must be found (case-insensitively, anywhere in
|
|
1650
1657
|
the value) in one of the searchable columns, so `clerk mil` finds MILLER the clerk. `%` and `_` are
|
|
@@ -1700,6 +1707,41 @@ with search type `fulltext`, tsvector `search_vector`, configuration `english`,
|
|
|
1700
1707
|
every search), reports a configuration name that doesn't exist, and a column that isn't a tsvector. A report's
|
|
1701
1708
|
search box can use the same engine: see its `fulltext` attribute.
|
|
1702
1709
|
|
|
1710
|
+
#### Vector search
|
|
1711
|
+
|
|
1712
|
+
A configuration of search type **vector** (APEX: the search type *Oracle Vector Search*) finds rows by meaning:
|
|
1713
|
+
*time off for a family move* finds the policy *Relocation leave … relocate to another city … the new house*,
|
|
1714
|
+
though they share no word. It needs the [pgvector](https://github.com/pgvector/pgvector) extension (see
|
|
1715
|
+
[chapter 3](03-builder.md#vector-providers) for installing it), a vector provider and a vector column:
|
|
1716
|
+
|
|
1717
|
+
1. **A vector provider** (Shared Components → Vector providers, [chapter 3](03-builder.md#vector-providers)): an
|
|
1718
|
+
AI service with an embedding model (OpenAI `text-embedding-3-small`, Gemini, Ollama's `nomic-embed-text` …)
|
|
1719
|
+
or a SQL function returning `vector`.
|
|
1720
|
+
2. **A vector column** on the table, kept current: SQL Workshop › Table Properties › **Add vector column**
|
|
1721
|
+
([chapter 3](03-builder.md)) adds `embedding vector(n)`, an HNSW index and a trigger, and queues the rows for
|
|
1722
|
+
their embeddings.
|
|
1723
|
+
3. **The configuration**: search type `vector`, the provider, the vector column, title and description columns,
|
|
1724
|
+
icon and link as for any configuration.
|
|
1725
|
+
|
|
1726
|
+
When a user searches, pgkiln embeds the search text with the provider (an AI service: one request, remembered a
|
|
1727
|
+
few minutes for search-as-you-type, logged under AI usage; the text goes to that provider), then asks the
|
|
1728
|
+
database for the nearest rows (`order by embedding <=> $1 limit n`, which the HNSW index serves) as the
|
|
1729
|
+
application's role, so **row level security decides what is found**. The vector is a query parameter; pgvector's
|
|
1730
|
+
operators are called schema-qualified. Results are not marked word by word: they need not contain the words.
|
|
1731
|
+
|
|
1732
|
+
**Hybrid** (`hybrid`) runs the full-text search (the tsvector and text search configuration above) and the vector
|
|
1733
|
+
search, takes the best 50 rows of each, and ranks them together by **reciprocal rank fusion**: each row scores
|
|
1734
|
+
1 / (60 + its rank) in each list it is in, summed, best first. Rows found by both rise to the top; a row that only
|
|
1735
|
+
one of them finds still shows. Hybrid needs the key column that tells which rows are the same.
|
|
1736
|
+
|
|
1737
|
+
Without pgvector a vector or hybrid configuration shows that the extension is missing (and the Advisor reports
|
|
1738
|
+
it); the other configurations answer as before. The Advisor also checks that the provider exists, that the
|
|
1739
|
+
column is `vector(n)` with the provider's *n*, and warns when it has no HNSW index or one for another distance.
|
|
1740
|
+
The HR example (part 72) adds `POLICIES_MEANING` (hybrid, with a stand-in function provider) to page 42.
|
|
1741
|
+
|
|
1742
|
+
An AI agent can use any table or SQL configuration as a **Retrieve** tool: the model gives a text, the agent gets
|
|
1743
|
+
the best rows ([AI agents](03-builder.md#ai-agents)).
|
|
1744
|
+
|
|
1703
1745
|
**The region**: attributes
|
|
1704
1746
|
|
|
1705
1747
|
| Key | Meaning |
|
|
@@ -1714,7 +1756,7 @@ The region's own field is a GET form (`?s<region id>=text`, so a search can be b
|
|
|
1714
1756
|
without JavaScript; with JavaScript it searches while the user types and replaces only the results.
|
|
1715
1757
|
The Advisor plans every configuration with the columns it names and reports names a region uses
|
|
1716
1758
|
that don't exist. Search configurations are exported with the application. The HR example's page 42
|
|
1717
|
-
searches employees, departments, the HR policies (full-text, part 71) and the shortcuts list.
|
|
1759
|
+
searches employees, departments, the HR policies (full-text, part 71; by meaning, part 72) and the shortcuts list.
|
|
1718
1760
|
|
|
1719
1761
|
### `url` (URL region)
|
|
1720
1762
|
|
package/docs/guide/05-items.md
CHANGED
|
@@ -21,7 +21,7 @@ page, optionally inside a region. Name them `P<page>_<NAME>` (uppercase letters,
|
|
|
21
21
|
| `radio` | radio group | the chosen return value |
|
|
22
22
|
| `checkbox` | a single checkbox | `true` / `false` |
|
|
23
23
|
| `switch` | on/off switch | `true` / `false` |
|
|
24
|
-
| `checkbox_group` | several checkboxes | **colon-separated** return values, e.g. `10:30` |
|
|
24
|
+
| `checkbox_group` | several checkboxes | **colon-separated** return values, e.g. `10:30`; (137) `{"switches": true}` shows each as a switch, one per line |
|
|
25
25
|
| `multiselect` | multi-select list | **colon-separated** return values |
|
|
26
26
|
| `display` | read-only text (shows the display value for list items) | whatever was set |
|
|
27
27
|
| `hidden` | not rendered | set by URL (with checksum), fetch or processes |
|
|
@@ -598,6 +598,15 @@ the component out (fail closed); the Advisor reports it. Build options are part
|
|
|
598
598
|
export. On HR page 22, the *Forecast* region needs `LEAVE_FORECAST` (excluded), and *Planner tips*
|
|
599
599
|
(`!LEAVE_FORECAST`) takes its place until it is included.
|
|
600
600
|
|
|
601
|
+
**Configurable build options** (sprint 137; APEX: the Configuration Options feature): tick *Configurable* on a
|
|
602
|
+
build option, and **Create page → Feature → Configuration options** adds page 9020 *Configuration* where the
|
|
603
|
+
application's administrators switch those options on and off while it runs (a checkbox group shown as switches,
|
|
604
|
+
`{"switches": true}`). From SQL, as the application's own role and for a user with the `admin` role only:
|
|
605
|
+
`meta.configurable_options()` (name, description, enabled) and `meta.set_configurable_options('EXPORT:BETA')`
|
|
606
|
+
(the named ones included, the other configurable ones excluded; an option that is not configurable is refused).
|
|
607
|
+
The change applies to every user at once, like a change in the builder. An import of a newer version of the
|
|
608
|
+
application sets the options as the export has them.
|
|
609
|
+
|
|
601
610
|
## Application processes
|
|
602
611
|
|
|
603
612
|
**Shared Components → Application processes** run for the whole application:
|
|
@@ -819,6 +828,15 @@ work for the **current application** only when the caller runs as that applicati
|
|
|
819
828
|
(pages, processes, automations, workflows and background chains do); the SQL Workshop is not an
|
|
820
829
|
application.
|
|
821
830
|
|
|
831
|
+
### The activity report by mail
|
|
832
|
+
|
|
833
|
+
`meta.send_activity_report(to, days default 7)` (sprint 137; APEX: the Email Reporting feature) queues a mail with
|
|
834
|
+
the current application's activity of the last days (page views, users, sign-ins, failed sign-ins, errors and the
|
|
835
|
+
most viewed pages) and the rows as `activity.csv`. It is for the application's automations and its signed-in
|
|
836
|
+
administrators; others get an error. **Create page → Feature → E-mail reporting** adds the application setting
|
|
837
|
+
`EMAIL_REPORT_TO` and an automation `EMAIL_REPORTING` that sends it every Monday at 07:00 UTC; without addresses
|
|
838
|
+
nothing is sent.
|
|
839
|
+
|
|
822
840
|
### E-mail templates
|
|
823
841
|
|
|
824
842
|
Shared Components → **E-mail templates** (APEX: Email Templates): a name, a subject, an HTML body
|
|
@@ -167,6 +167,24 @@ select meta.ai_generate('CLAUDE', 'Classify: ' || :P5_TEXT, null,
|
|
|
167
167
|
enabled and the application is allowed); whether the provider accepts the key shows only when a
|
|
168
168
|
request is made.
|
|
169
169
|
|
|
170
|
+
### Embeddings in SQL
|
|
171
|
+
|
|
172
|
+
(sprint 136) `meta.embed(text, provider [, app_id])` returns the embedding of a text by a
|
|
173
|
+
[vector provider](03-builder.md#vector-providers) of the current application (or of `app_id`, one the
|
|
174
|
+
login may see) as `real[]`; cast it for pgvector: `meta.embed(:P1_Q, 'DOC_EMBEDDINGS')::vector`. It
|
|
175
|
+
works for **function providers** and calls the function as the role of the query (security invoker:
|
|
176
|
+
its grants apply), checking the dimensions. A provider that uses an AI service is called by the
|
|
177
|
+
pgkiln server, never from SQL: `meta.embed` raises an error that says so (use a vector search
|
|
178
|
+
configuration, a Retrieve tool or a vector column's queue). `meta.vector_provider(name [, app_id])`
|
|
179
|
+
returns a provider's definition (they hold no secrets).
|
|
180
|
+
|
|
181
|
+
```sql
|
|
182
|
+
-- the nearest documents to a text, as the application's role (row level security applies)
|
|
183
|
+
select title from sales.doc
|
|
184
|
+
order by embedding <=> meta.embed('how do I return an order?', 'DOC_EMBEDDINGS')::vector
|
|
185
|
+
limit 5;
|
|
186
|
+
```
|
|
187
|
+
|
|
170
188
|
### Parsing files in SQL
|
|
171
189
|
|
|
172
190
|
`meta.parse_data()` reads CSV/TSV and JSON in a `bytea` (an uploaded file in `meta.temp_files`, a
|
|
@@ -278,13 +296,16 @@ Run these as the owner (in the SQL Workshop, `psql` or migrations):
|
|
|
278
296
|
|---|---|
|
|
279
297
|
| `meta.generate_crud(app, table, report_page, form_page, label default null, icon default 'table')` | Report page + modal form page + menu entry for a table, with the column names as labels (kept as it was for existing scripts; `meta.generate_page` *report_form* makes the labelled pages of the wizards) |
|
|
280
298
|
| `meta.generate_grid(app, table, page, label default null, icon default 'grid')` | Interactive grid page + menu entry |
|
|
281
|
-
| `meta.generate_page(app, kind, table, page, options default '{}')` | The create page wizards: `kind` is `form`, `cards`, `calendar`, `chart`, `map`, `facets`, `master_detail` (or `report_form`, `grid`)
|
|
299
|
+
| `meta.generate_page(app, kind, table, page, options default '{}')` | The create page wizards: `kind` is `form`, `cards`, `calendar`, `chart`, `map`, `facets`, `master_detail` (or `report_form`, `grid`); (137) also `report`, `smart_filters`, `tree`, `timeline`, `media_list`, `content_row`, `comments`, `data_load`, `wizard`, and without a table (`null`) `search`, `tasks`, `workflows`. Options left out take the defaults of `meta.wizard_defaults`; JSON `null` means none. Returns the new page's id (below; a wizard: its first page's) |
|
|
282
300
|
| `meta.wizard_defaults(kind, table)` | The options a wizard proposes for a table, from the catalog |
|
|
283
301
|
| `meta.wizard_catalog(table)` | The columns as the wizards see them: kind (`text`, `number`, `date`, `timestamp`, `boolean`, `point`, `geometry`, `binary`, `other`), key, unique, foreign key and the parent's display column |
|
|
284
302
|
| `meta.wizard_label(name)`, `meta.wizard_table_label(table, plural default true)`, `meta.wizard_column_label(table, column)` | (097) The labels of the wizards: `hiredate` → *Hire Date*, `hr.emp` → *Employees* / *Employee*, `deptno` → *Department*; short comments win ([what the generated pages look like](03-builder.md#what-the-generated-pages-look-like)) |
|
|
285
303
|
| `meta.wizard_report(table, columns default null)` | (097) A report on a table as the wizards make it: `{"source", "hidden", "headings", "formats"}` (foreign keys joined to their parent's display column) |
|
|
286
304
|
| `meta.wizard_grid_labels(table)` | (100) An interactive grid's `{"headings", "formats"}` as the wizards make them: the labels of `meta.wizard_column_label` and the number format masks (keys get none) |
|
|
287
305
|
| `meta.app_activity(days default 30)` | (098) The current application's page views, sign-ins and errors of the last days (1 to 366, at most 10,000 rows, no IP addresses), for a signed-in user with the `admin` role of that application; others get an error. The Activity page of the Create Application wizard reads it |
|
|
306
|
+
| `meta.configurable_options()`, `meta.set_configurable_options(names)` | (137) The current application's [configurable build options](06-processing.md#build-options) (name, description, enabled), and switching them (`names` colon-separated: those are included, the other configurable ones excluded). Only as the application's own role, for a user with the `admin` role; other options are refused |
|
|
307
|
+
| `meta.send_activity_report(to, days default 7)` | (137) Queue a mail with a summary of the current application's activity of the last days and the rows as `activity.csv`. For the application's automations and its signed-in administrators; empty `to`: nothing is sent. The *E-mail reporting* feature's automation calls it |
|
|
308
|
+
| `meta.copy_page(from_app, page, to_app, new_page, name default null, map default '{}')` | (137) [Create page as copy](03-builder.md#create-page-as-copy) (owner only); `map` is `{"lov:JOBS": "JOB_TITLES"}`: a shared component to use instead of copying it. `meta.page_shared_refs(page_id, target_app)` lists what a page refers to by name |
|
|
288
309
|
| `meta.wizard_files(table)` | (097) The `bytea` columns and the columns holding their file name, mime type, size and change time |
|
|
289
310
|
| `meta.export_app(alias)` | The application as JSON (`pgkiln/2` format) |
|
|
290
311
|
| `meta.import_app(json, alias default null)` | Import an export, optionally under a new alias; returns the new app id |
|
|
@@ -309,6 +330,17 @@ Options of `meta.generate_page` (all optional; column names must be columns of t
|
|
|
309
330
|
| `facets` | `columns` (report columns, array), `facets` (array), `search` (boolean), `form_page` |
|
|
310
331
|
| `master_detail` | `detail` (a table), `detail_column` (its column referring to this table) |
|
|
311
332
|
| `report_form` | `form_page` (required) |
|
|
333
|
+
| `report` | `form_page` (137: an interactive report alone) |
|
|
334
|
+
| `smart_filters` | `facets` (array), `form_page` |
|
|
335
|
+
| `tree` | `id_column`, `parent_column` (proposed: a foreign key to the table itself), `label_column`, `expanded` (levels open), `form_page` |
|
|
336
|
+
| `timeline` | `title`, `when`, `who`, `body`, `form_page` |
|
|
337
|
+
| `media_list`, `content_row` | `title`, `description`, `badge` (also its colour), `misc` (content row), `form_page` |
|
|
338
|
+
| `comments` | `user`, `date`, `comment` |
|
|
339
|
+
| `data_load` | `definition` (a data load definition's name: made for the table unless it exists), `mode` (`append`, `merge`, `replace`), `skip_errors` |
|
|
340
|
+
| `wizard` | `columns` (array; NOT NULL columns without a default always), `steps` (1–10), `return_page` |
|
|
341
|
+
| `search` | `configs` (search configurations by name); with a table: a new configuration on it, and `form_page` |
|
|
342
|
+
| `tasks` | `context` (`my`, `initiated`, `admin`), `completed` (boolean) |
|
|
343
|
+
| `workflows` | `context` (`initiator`, `admin`) |
|
|
312
344
|
|
|
313
345
|
```sql
|
|
314
346
|
select meta.generate_page('shop', 'calendar', 'shop.meeting', 12, '{"form_page": 13, "drag": true}');
|
|
@@ -488,6 +520,7 @@ navigation entries and application processes have the same `build_option` column
|
|
|
488
520
|
| `app_ai_service` | Which applications may use which AI service, with daily limits `max_requests` and `max_tokens` (null: no limit). Not exported | no (through `meta.ai_available`) |
|
|
489
521
|
| `ai_usage` | One row per AI request: `at`, `app_id`, `page_no`, `username`, `service`, `provider`, `model`, `source`, `input_tokens`, `output_tokens`, `duration_ms`, `status`, `message` (an error class, never prompt or answer text). Not exported | no |
|
|
490
522
|
| `ai_tool_call` | (121) Every tool call of an [AI assistant or agent](03-builder.md#ai-agents): `at`, `app_id`, `page_no`, `region_id`, `username`, `agent`, `tool`, `type`, `arguments` (the model's, cut to 4 KB), `status` (`ok`, `error`, `refused`), `duration_ms`, `message`. Kept 30 days, not exported | no |
|
|
523
|
+
| `embedding_queue` | (136) Rows whose vector column the server re-embeds: `app_id`, `provider`, `tbl` (the table's oid), `vector_column`, `key_column`, `source_columns`, `key_value`, `queued_at`, `next_at`, `attempts`, `last_error`. Filled by `meta.embedding_enqueue` triggers and Backfill; not exported | no |
|
|
491
524
|
| `ai_conversation` | [AI assistant](04-pages-and-regions.md#ai_assistant-ai-assistant) conversations: one per session and region (`messages`: the provider's history, `turns`: what the page shows); deleted with the session. Not exported | no |
|
|
492
525
|
| `ai_table_note` | Descriptions of tables and columns for models ([App Builder AI](03-builder.md#app-builder-ai)): `schema_name`, `table_name`, `column_name` (`''` for the table), `note`. Installation data, not exported | no |
|
|
493
526
|
| `blueprint` | Saved [blueprints](03-builder.md#creating-an-application-from-a-blueprint): `name`, `spec` (the JSON), `app_id` (the application last created from it), `created_by`. Builder data, not exported | no |
|
|
@@ -145,6 +145,12 @@ src/
|
|
|
145
145
|
scramVerifier (SCRAM-SHA-256, SASLprep) and the password slot (maskSql for review and log, withVerifier to run)
|
|
146
146
|
cron-schedule.ts (sprint 101, DA-12) pg_cron schedules without a database: parseSchedule (cron fields, $, n seconds, macros),
|
|
147
147
|
nextRuns in a time zone, describeSchedule in words, the builder's presets and fields
|
|
148
|
+
vector.ts (sprint 136, AP-1) pgvector: pgvectorStatus(), vectorSchema() (operators and the type schema-qualified),
|
|
149
|
+
DISTANCES (cosine/l2/inner_product: operator, HNSW opclass, similarity), vectorColumnDims(); the embedding
|
|
150
|
+
queue (meta.embedding_queue, migration 136): embeddingTick() (groups, FOR UPDATE SKIP LOCKED, batches
|
|
151
|
+
within the provider's per-minute limit, written with devDbForApp(), retries with backoff), drainEmbeddings(),
|
|
152
|
+
kickEmbeddings(), startEmbeddingRunner() (server.ts; EMBEDDINGS=off, EMBEDDING_INTERVAL_S), vectorTriggers()
|
|
153
|
+
(meta.embedding_set / embedding_enqueue triggers and their arguments), backfill(), queueState()
|
|
148
154
|
pgcron.ts (sprint 101, DA-12) pg_cron for SQL Workshop → Scheduling: cronState (installed, loaded, cron.database_name,
|
|
149
155
|
access), jobs and runs, jobSql/activeSql/unscheduleSql/purgeSql (reviewed, then applySql), runNow
|
|
150
156
|
secrets.ts secrets at rest (web credentials): AES-256-GCM with PGKILN_SECRET_KEY (or a key given, for rotate-key);
|
|
@@ -176,9 +182,19 @@ src/
|
|
|
176
182
|
(121) an agent's effort (Claude output_config.effort, OpenAI reasoning_effort) and output limit).
|
|
177
183
|
Providers (121): anthropic, openai, gemini (OpenAI-compatible endpoint, GEMINI_BASE_URL), openai_compatible
|
|
178
184
|
(Ollama, vLLM, Azure OpenAI; base URL required, key optional): service.ts serviceKey(), baseUrlFor(), providerLabel()
|
|
185
|
+
(sprint 136, AP-1) embeddings.ts: vector providers (meta.app.vector_providers: ai_service with an embedding
|
|
186
|
+
model, or a function), vectorProviderProblems(), embedTexts() (the openai SDK's embeddings API, float
|
|
187
|
+
encoding, dimensions checked, daily limits via checkDailyLimit(), meta.ai_usage source "embedding"; a
|
|
188
|
+
function provider through meta.embed on the caller's connection), embedQuery() (search texts remembered
|
|
189
|
+
10 minutes per process), vectorLiteral()
|
|
179
190
|
debug.ts debug messages: DebugLog (levels, timed steps, NOTICEs of meta.debug from appTx), started in
|
|
180
191
|
loadContext, stored after the response (onResponse hook → meta.debug_save), hourly purge
|
|
181
192
|
icons.ts icon helper (sprite in public/icons.svg)
|
|
193
|
+
history.ts (sprint 138, AP-5) change history and checksums: meta.app_snapshot (the export after the last recorded
|
|
194
|
+
change), recordHistory() (export again, workingcopy.ts components() of both, each added/changed/deleted
|
|
195
|
+
component with its files before and after into meta.component_change; called by app-updates.ts onSend,
|
|
196
|
+
the upgrade page and cli/replace.ts replaceApp), ensureSnapshot() (before a first change), diffDocs(),
|
|
197
|
+
appChecksum() / pageChecksums() (SHA-256 of the text layout without the alias), CHANGE_HISTORY_DAYS
|
|
182
198
|
runtime/
|
|
183
199
|
routes.ts HTTP handlers: show, submit, dynamic actions, cascading lists, login
|
|
184
200
|
signin.ts the sign-in form (built-in page and the login region of an application's own login page), safeNext, passwordResetAvailable
|
|
@@ -234,7 +250,7 @@ src/
|
|
|
234
250
|
tree.ts tree region
|
|
235
251
|
lists.ts lists: static entries or a query, visibility (authorization, conditions, page access), safe URLs; list regions, navigation menu and bar
|
|
236
252
|
template-components.ts template components: template language (allow-list, directives, escaping), plug-in files, report column templates
|
|
237
|
-
builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_media_list, ut_metric_card, ut_timeline)
|
|
253
|
+
builtin-components.ts built-in template components (ut_avatar, ut_badge, ut_comments, ut_content_row, ut_media_list, ut_metric_card, ut_timeline)
|
|
238
254
|
template-region.ts template_component region
|
|
239
255
|
tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route;
|
|
240
256
|
(sprint 114) custom outcome buttons, the Fail event and recovery of a failing completion SQL
|
|
@@ -255,7 +271,8 @@ src/
|
|
|
255
271
|
definitionProblems), agentFor(), prepareTools() (authorization schemes, navigate pages the user may
|
|
256
272
|
open, strict schemas), runAgentTool() (sql: read-only transaction as the app role with a time limit;
|
|
257
273
|
function: named arguments, committed; rest; javascript: queued for the browser; navigate: URL and
|
|
258
|
-
checksum built here), logToolCall() (meta.ai_tool_call, debug level 4, 30-day purge)
|
|
274
|
+
checksum built here), logToolCall() (meta.ai_tool_call, debug level 4, 30-day purge); (136) retrieve:
|
|
275
|
+
retrieveTool() (QUERY only, search.ts retrieve() in a read-only transaction as the app role)
|
|
259
276
|
ai-filter.ts "Ask in your own words" on a report ("ai_filter", sprint 112: AI interactive reports): the box, the chips
|
|
260
277
|
form (Edit/Remove/Undo, undo history in the session), ask and chips routes; an AI service's structured
|
|
261
278
|
output (planSchema) or the phrase parser, then ir-plan.ts
|
|
@@ -270,6 +287,10 @@ src/
|
|
|
270
287
|
searchable columns), websearch_to_tsquery / ts_rank_cd / ts_headline between private-use markers
|
|
271
288
|
(headlined() escapes, only the markers become <mark>), textSearchConfig() (pg_ts_config names only,
|
|
272
289
|
passed as $1::regconfig; tsConfigFor(language)), trigramSchema() and "did you mean" with pg_trgm
|
|
290
|
+
(sprint 136, migration 136) search types vector (vectorRows: the text embedded by the provider, nearest
|
|
291
|
+
rows by the schema-qualified distance operator, the vector as $1, min_similarity) and hybrid (full-text
|
|
292
|
+
and vector rankings, 50 rows each, reciprocal rank fusion 1/(60+rank) per key_column); retrieve() for
|
|
293
|
+
an agent's Retrieve tool
|
|
273
294
|
url-region.ts URL region (migration 078): literal origins only (urlTarget), &ITEM. URL-encoded, sandbox and height,
|
|
274
295
|
frameOrigins() for the CSP's frame-src (security.ts)
|
|
275
296
|
ajax.ts Ajax callbacks (migration 085: POST …/ajax/:name, point "ajax" processes, x01 … x10 binds) and the
|
|
@@ -293,6 +314,10 @@ src/
|
|
|
293
314
|
language links (?lang=, a cookie), meta.developer.language
|
|
294
315
|
routes.ts sign-in, app home, settings, activity, developers, create (POST, via newapp.ts)/import (POST)
|
|
295
316
|
wizards.ts create page wizards: step 2 forms per page type (defaults from meta.wizard_defaults), POST → meta.generate_page
|
|
317
|
+
create-page.ts (137) Create page: the grid of page types with SVG previews (tabs Component / Feature / Copy), the table step,
|
|
318
|
+
Create page as copy (step per app and page, shared components mapped or copied → meta.copy_page)
|
|
319
|
+
features.ts (137) Add feature: review (what it adds) and install, once, of Access control, Activity reporting,
|
|
320
|
+
Configuration options, E-mail reporting, Feedback, About, Login page, Theme style selection, Push
|
|
296
321
|
(the generators are PL/pgSQL in migration 047: catalog, defaults, form/cards/calendar/chart/map/facets/master-detail)
|
|
297
322
|
home.ts App Builder home at /builder/apps and /builder (tiles, applications report/cards, Recent; /builder redirects
|
|
298
323
|
to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities;
|
|
@@ -360,6 +385,13 @@ src/
|
|
|
360
385
|
fulltext.ts (sprint 135, AP-2) Table Properties › Add full-text search: GET/POST /sql/objects/fulltext?o= (text columns
|
|
361
386
|
with weights A–D, a configuration from pg_ts_config, fulltextDdl(): a generated tsvector column and a GIN
|
|
362
387
|
index), Preview / Apply (one transaction, recorded in the Change Log) / Open in the worksheet
|
|
388
|
+
vector-column.ts (sprint 136, AP-1) Table Properties › Add vector column: GET/POST /sql/objects/vector?o= (a provider of the
|
|
389
|
+
workspace's applications, source columns, distance; vectorDdl(): vector(n), HNSW index, the
|
|
390
|
+
meta.embedding_set / embedding_enqueue trigger), Preview / Apply (Change Log; the rows queued) / worksheet;
|
|
391
|
+
the table's vector columns with progress and Backfill (mode=backfill, all=on)
|
|
392
|
+
vector-providers.ts (sprint 136, AP-1) Shared Components → Vector providers (/apps/:id/vector-providers: list, provider form,
|
|
393
|
+
test, delete) and Workspace utilities → Vector providers (/vector-providers: pgvector status, Create
|
|
394
|
+
extension for administrators, the workspace's providers and vector columns); pgvectorNote()
|
|
363
395
|
data-security.ts (sprint 119) a table's Security: GET/POST /sql/objects/security?o= (policies, roles × columns, masks), Preview / Apply
|
|
364
396
|
object-actions.ts (sprint 94) an object's ⋯ menu (objectMenu) in the worksheet and the Object Browser: Quick DDL (objectDdl), Drop with
|
|
365
397
|
dependents, Add index / trigger / policy as drafts; GET /sql/objects/action, /sql/objects/panel (Open in a panel)
|
|
@@ -389,6 +421,18 @@ src/
|
|
|
389
421
|
region-settings.ts page designer: settings forms for grid, chart (gauge, drill-down), cards, calendar (views, create, drag and drop), facets, smart filters, display selector, list
|
|
390
422
|
search.ts app search, "where used" (appEntries, search, whereUsed, usedInPanel)
|
|
391
423
|
advisor.ts Advisor: EXPLAIN every SQL fragment, reference checks, plpgsql_check
|
|
424
|
+
utilities.ts (sprint 138, AP-5) an application's Utilities tab: tiles, dashboard figures, recently updated pages,
|
|
425
|
+
application and page checksums; Change history (filters, diffHtml of a change); the icon browser
|
|
426
|
+
/builder/icons; historyHooks() (preHandler: ensureSnapshot before a builder POST of an application)
|
|
427
|
+
dependencies.ts (sprint 138) Utilities → Database object dependencies: appDependencies() (the Advisor's fragments, each
|
|
428
|
+
parsed as the app role into a BEGIN ATOMIC temp function, one statement by the extended protocol;
|
|
429
|
+
pg_depend → tables, views, sequences, functions; missing objects from the parser's errors; privileges
|
|
430
|
+
needed vs the role's), the page with Object Browser links
|
|
431
|
+
upgrade.ts (sprint 138) Utilities → Upgrade application: upgradeCandidates() (nav_appearance, date_picker,
|
|
432
|
+
popup_lov), preview diff, POST upgrade by key or rule (found again first), recorded in the history
|
|
433
|
+
ui-defaults.ts (sprint 138) UI defaults /builder/ui-defaults (meta.ui_default, migration 138): per column label, mask,
|
|
434
|
+
help, alignment, list of values; seed from comments; tables of the workspace's catalog only. The wizards
|
|
435
|
+
read them through meta.wizard_column_label and the meta.item trigger meta.ui_default_item
|
|
392
436
|
ddl-tools.ts SQL Workshop → DDL and Compare (src/ddl.ts); (sprint 106) the Apply and record form
|
|
393
437
|
changelog.ts (sprint 106) SQL Workshop → Change Log (/builder/sql/changes): change sets per schema, latest app export,
|
|
394
438
|
downloads (POST, mark exported_at), rename, delete; /sql/compare/apply (one transaction, recorded)
|
|
@@ -505,6 +549,10 @@ test/
|
|
|
505
549
|
fulltext-search.test.ts (sprint 135) full-text search configurations: stemming, rank order, highlights, websearch syntax, the
|
|
506
550
|
configuration fallback, did you mean (pg_trgm when it can be installed), the report search on a tsvector
|
|
507
551
|
column, the Advisor's GIN check, Add full-text search (preview, apply, change log), export/import
|
|
552
|
+
vector-search.test.ts (sprint 136) vector providers and their checks, meta.embed, Add vector column (preview, apply, the queue
|
|
553
|
+
with the mock's embeddings, a change queued again, Backfill, a failing batch), vector and hybrid search
|
|
554
|
+
("time off for a family move" → Relocation leave; RLS), the Advisor, Retrieve tools on page 49, the
|
|
555
|
+
builder pages, export; skips what needs pgvector when it isn't installed
|
|
508
556
|
object-actions.test.ts (sprint 94) object menus: Quick DDL, Drop with dependents, Add index / trigger / policy, the panel, not-found
|
|
509
557
|
data-modeler.test.ts (sprint 98) Data Modeler: "hr core" with its FK lines, saved positions, SVG, objects, DDL, dictionary, geometry
|
|
510
558
|
worksheet.test.ts (sprint 91) the SQL Worksheet: statement at the cursor, Run Script log, plans, notices, history, navigator, Run as
|
|
@@ -566,7 +614,9 @@ test/
|
|
|
566
614
|
find-workspace.test.ts developers' addresses, builder mail, the workspaces mail, the same answer, limits
|
|
567
615
|
ai-builder.test.ts App Builder AI: the builder's service, SQL from a question, explain, describe tables, pages with AI
|
|
568
616
|
ai-script-mock.ts a scripted mock of Claude (SSE, tool_use with a thinking block) and OpenAI (tool calls), reply by reply
|
|
569
|
-
ai-mock.ts a local mock of the Claude Messages API (SSE stream) and OpenAI Chat Completions
|
|
617
|
+
ai-mock.ts a local mock of the Claude Messages API (SSE stream) and OpenAI Chat Completions; (136) OpenAI's
|
|
618
|
+
embeddings (/embeddings, also in ai-script-mock.ts): conceptEmbedding(), eight "topics", the same
|
|
619
|
+
as the HR example's hr.policy_embedding
|
|
570
620
|
helpers.ts a cookie-keeping test browser
|
|
571
621
|
e2e/responsive.test.ts browser tests at phone/tablet/desktop widths (Playwright)
|
|
572
622
|
e2e/builder-i18n.test.ts (sprint 121) Deutsch on the sign-in page, the status bar's language menu, German screens at 390 px, CSP
|
|
@@ -63,7 +63,7 @@ that includes them.
|
|
|
63
63
|
| **[pg_cron](https://github.com/citusdata/pg_cron)** | Run SQL on a schedule inside the database (`select cron.schedule('nightly', '0 2 * * *', 'call hr.close_month()')`) | **Automations**, `DBMS_SCHEDULER` |
|
|
64
64
|
| **[pgaudit](https://github.com/pgaudit/pgaudit)** | Statement-level audit logging (reads, writes, DDL, role changes) to the server log, for compliance | Oracle Unified Auditing |
|
|
65
65
|
| **PostGIS** | Geometry and geography types, spatial indexes and functions: the foundation for a map region | **Map region**, Oracle Spatial |
|
|
66
|
-
| **pgvector** | Vector similarity search (embeddings) for semantic search and retrieval-augmented generation | APEX AI (RAG sources), Oracle AI Vector Search |
|
|
66
|
+
| **pgvector** | Vector similarity search (embeddings) for semantic search and retrieval-augmented generation; pgkiln's vector providers, vector and hybrid search configurations and Retrieve tools use it (sprint 136, [chapter 4](04-pages-and-regions.md#vector-search)) | APEX AI (RAG sources), Oracle AI Vector Search |
|
|
67
67
|
| **[plpgsql_check](https://github.com/okbob/plpgsql_check)** | Static analysis of PL/pgSQL: wrong columns, unused variables, missing `RETURN`, hidden casts. Run it on your app's functions in CI | **Advisor** (for code) |
|
|
68
68
|
|
|
69
69
|
Scheduling example with pg_cron. pg_cron must be in `shared_preload_libraries` (a server setting;
|
package/docs/guide/16-files.md
CHANGED
|
@@ -406,7 +406,7 @@ The HR sample has `EMP_XML`, for XML files like `/static/samples/employees.xml`.
|
|
|
406
406
|
### Data loading in an application
|
|
407
407
|
|
|
408
408
|
For end users, add a page with a `file` item and a button, and a **process of type
|
|
409
|
-
`data_load
|
|
409
|
+
`data_load`** ((sprint 137) **Create page → Data loading** makes the definition and such a page for a table). The process runs **as the application's database role**, so grants, row level
|
|
410
410
|
security, triggers and the audit trail apply, just as they do for the form.
|
|
411
411
|
|
|
412
412
|
| `config` key | Meaning |
|
package/examples/hr/README.md
CHANGED
|
@@ -43,6 +43,7 @@ Then open http://127.0.0.1:3100/a/hr and sign in as `king`, `blake`, `jones`, `a
|
|
|
43
43
|
| `hr_64_ai_agent.sql` | *HR agent* (page 46): the AI agent `HR_AGENT` (Shared Components → AI Agents) with four tools: `top_earners` (SQL, read-only as `hr_app`), `open_employee` (navigate: a link to the employee form with its checksum, only for users who may open page 3), `give_raise` (the PL/pgSQL function `hr.give_raise`, managers only) and `note` (a JavaScript tool: the custom event `hr_agent_note`). Ask *Who in Sales earns most?* Needs an AI service named `HR_ASSISTANT` (see part 41) |
|
|
44
44
|
| `hr_68_component_details.sql` | *Component details* (page 47): a popup LOV over 6,000 rows that loads more as the dialog scrolls, a wait popup around a two-second process (*Recalculate*) and around an Ajax call (*Check in the background*), and an *Actions* menu whose entries run dynamic actions (event `menu_entry`) |
|
|
45
45
|
| `hr_71_fulltext_search.sql` | Full-text search: `hr.policy` gets a generated `search_vector` column (title weight A, body B, english) with a GIN index, and the search configuration `POLICIES` (search type *fulltext*, *Did you mean* when `pg_trgm` is installed) joins page 42 *Search*: "works home" finds *Working from home*, the matched words highlighted |
|
|
46
|
+
| `hr_72_vector_search.sql` | Search by meaning: `hr.policy` gets *Relocation leave* and a managers-only *Salary bands* with row level security (`audience`); with pgvector, the vector provider `POLICY_EMBEDDINGS` (a stand-in function, `hr.policy_embedding`, eight topics), `hr.policy.embedding vector(8)` with an HNSW index and a trigger, the hybrid search configuration `POLICIES_MEANING` on page 42 (*time off for a family move* finds *Relocation leave*), and *Policy questions* (page 49): the agent `POLICY_AGENT` with the Retrieve tool `policy_search`. Needs an AI service named `HR_ASSISTANT` for page 49 |
|
|
46
47
|
|
|
47
48
|
The files run in order, each once (recorded in `public.pgkiln_seed`), so new ones can be added
|
|
48
49
|
later. pgkiln's own tests use this application as their fixture (`npm test` installs it first).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 72: search by meaning (sprint 136, AP-1; migration
|
|
3
|
+
-- 136; docs/guide/04-pages-and-regions.md "Vector search", 03-builder.md
|
|
4
|
+
-- "Vector providers", 20-ai-agents.md)
|
|
5
|
+
--
|
|
6
|
+
-- hr.policy (parts 42 and 71) gets two more policies and an audience:
|
|
7
|
+
-- "Salary bands" is for managers only, and row level security now shows
|
|
8
|
+
-- hr_app only the policies the user may read. Every search below runs as
|
|
9
|
+
-- hr_app, so an employee never finds the managers' policy.
|
|
10
|
+
--
|
|
11
|
+
-- With pgvector installed (the extension is created here when the server
|
|
12
|
+
-- has it and this connection may; otherwise this part only adds the
|
|
13
|
+
-- policies and says so):
|
|
14
|
+
--
|
|
15
|
+
-- - the vector provider POLICY_EMBEDDINGS (Shared Components → Vector
|
|
16
|
+
-- providers) of type function: hr.policy_embedding(text), a tiny
|
|
17
|
+
-- stand-in for an embedding model that maps words to eight topics
|
|
18
|
+
-- (moving house, time off, home working, sickness, pay, requests,
|
|
19
|
+
-- public holidays, anything else). It keeps the example working without
|
|
20
|
+
-- an AI service; for real use, make the provider an AI service with an
|
|
21
|
+
-- embedding model (e.g. OpenAI text-embedding-3-small, 1536 dimensions,
|
|
22
|
+
-- or Ollama's nomic-embed-text, 768) and add the column again with
|
|
23
|
+
-- Table Properties › Add vector column;
|
|
24
|
+
-- - what Table Properties › Add vector column writes: hr.policy.embedding
|
|
25
|
+
-- vector(8), an HNSW index (vector_cosine_ops) and a trigger
|
|
26
|
+
-- (meta.embedding_set) that embeds a policy when its title or body
|
|
27
|
+
-- changes;
|
|
28
|
+
-- - the search configuration POLICIES_MEANING (search type hybrid:
|
|
29
|
+
-- full-text on search_vector and the embedding, merged by reciprocal
|
|
30
|
+
-- rank fusion) joins page 42 "Search": "time off for a family move"
|
|
31
|
+
-- finds "Relocation leave", which uses none of those words;
|
|
32
|
+
-- - page 49 "Policy questions": an AI assistant region with the agent
|
|
33
|
+
-- POLICY_AGENT, whose Retrieve tool policy_search hands the model the
|
|
34
|
+
-- policies that match a question (as hr_app). Like parts 41, 42 and 64
|
|
35
|
+
-- it needs an AI service named HR_ASSISTANT.
|
|
36
|
+
-- =====================================================================
|
|
37
|
+
|
|
38
|
+
alter table hr.policy add column audience text not null default 'all' check (audience in ('all', 'managers'));
|
|
39
|
+
|
|
40
|
+
insert into hr.policy (title, body, audience) values
|
|
41
|
+
('Relocation leave', 'Employees who relocate to another city for the company get three paid days to settle into the new house, and help with the removal costs.', 'all'),
|
|
42
|
+
('Salary bands', 'Managers review the salary bands every spring; a raise above the band needs the director''s approval.', 'managers');
|
|
43
|
+
|
|
44
|
+
alter table hr.policy enable row level security;
|
|
45
|
+
create policy policy_visible on hr.policy for select
|
|
46
|
+
using (audience = 'all' or meta.has_role('manager') or meta.has_role('admin'));
|
|
47
|
+
|
|
48
|
+
-- a stand-in for an embedding model: each word counts for one of eight topics (unknown words a little for the
|
|
49
|
+
-- last), and the vector is normalised, so the cosine of two texts says how much they share topics
|
|
50
|
+
create function hr.policy_embedding(p_text text) returns real[]
|
|
51
|
+
language sql immutable parallel safe set search_path = pg_catalog as $$
|
|
52
|
+
with w as (select lower(m[1]) as w from regexp_matches(coalesce(p_text, ''), '([[:alpha:]]+)', 'g') m),
|
|
53
|
+
d as (select case
|
|
54
|
+
when w ~ '^(relocat|move|moving|removal|house|settl|city|family)' then 1
|
|
55
|
+
when w ~ '^(leave|time|off|holiday|vacation|days|absence)$' then 2
|
|
56
|
+
when w ~ '^(home|remote|working|work)$' then 3
|
|
57
|
+
when w ~ '^(sick|ill|doctor)' then 4
|
|
58
|
+
when w ~ '^(salar|pay|band|raise|wage|scale)' then 5
|
|
59
|
+
when w ~ '^(request|approv|manager)' then 6
|
|
60
|
+
when w ~ '^(public|closed|office)' then 7
|
|
61
|
+
else 8 end as dim,
|
|
62
|
+
case when w ~ '^(relocat|move|moving|removal|house|settl|city|family|leave|time|off|holiday|vacation|days|absence|home|remote|working|work|sick|ill|doctor|salar|pay|band|raise|wage|scale|request|approv|manager|public|closed|office)'
|
|
63
|
+
then 1.0 else 0.05 end as weight
|
|
64
|
+
from w),
|
|
65
|
+
v as (select i, coalesce((select sum(weight) from d where d.dim = i), 0) as x from generate_series(1, 8) i),
|
|
66
|
+
n as (select sqrt(sum(x * x)) as n from v)
|
|
67
|
+
select array_agg((case when n.n = 0 then (case when i = 8 then 1 else 0 end) else x / n.n end)::real order by i) from v, n
|
|
68
|
+
$$;
|
|
69
|
+
grant execute on function hr.policy_embedding(text) to hr_app;
|
|
70
|
+
|
|
71
|
+
update meta.app set
|
|
72
|
+
vector_providers = vector_providers || $j$[
|
|
73
|
+
{"name": "POLICY_EMBEDDINGS", "type": "function", "function": "hr.policy_embedding", "dimensions": 8,
|
|
74
|
+
"description": "A stand-in for an embedding model (eight topics); use an AI service for real texts"}
|
|
75
|
+
]$j$::jsonb,
|
|
76
|
+
ai_tools = ai_tools || $j$[
|
|
77
|
+
{"name": "policy_search", "type": "retrieve", "search_config": "POLICIES_MEANING", "max_rows": 3,
|
|
78
|
+
"description": "The HR policies that match a question, best first (title and text). Use it for every question about rules, leave, pay or working arrangements."}
|
|
79
|
+
]$j$::jsonb,
|
|
80
|
+
ai_agents = ai_agents || $j$[
|
|
81
|
+
{"name": "POLICY_AGENT", "description": "Answers questions about the HR policies the user may read",
|
|
82
|
+
"service": "HR_ASSISTANT", "effort": "low", "tools": ["policy_search"], "max_steps": 3, "max_tokens": 1500,
|
|
83
|
+
"system": "You answer questions of &APP_USER. about the company's HR policies. Look the policies up with policy_search and answer only from what it returns; say so when no policy covers the question. Name the policy you used. Be brief."}
|
|
84
|
+
]$j$::jsonb
|
|
85
|
+
where alias = 'hr';
|
|
86
|
+
|
|
87
|
+
-- page 49: questions about the policies, answered through the Retrieve tool
|
|
88
|
+
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
89
|
+
select id, 49, 'Policy questions', 'Policy questions', 1 from meta.app where alias = 'hr';
|
|
90
|
+
|
|
91
|
+
insert into meta.region (page_id, seq, title, type, columns, template, source, condition, config)
|
|
92
|
+
select p.id, r.seq, r.title, r.type, r.cols, 'standard', r.source, r.cond, r.config::jsonb
|
|
93
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
94
|
+
(10, 'Not set up yet', 'static', 12,
|
|
95
|
+
'<p class="alert alert-info">Policy questions need the pgvector extension in the database and an AI service named <b>HR_ASSISTANT</b> (App Builder → Workspace utilities → AI services, allowed for the HR application).</p>',
|
|
96
|
+
'not (meta.ai_available(''HR_ASSISTANT'') and exists (select from pg_catalog.pg_extension where extname = ''vector''))', '{}'),
|
|
97
|
+
(20, 'Ask about the policies', 'ai_assistant', 8, null,
|
|
98
|
+
'meta.ai_available(''HR_ASSISTANT'') and exists (select from pg_catalog.pg_extension where extname = ''vector'')',
|
|
99
|
+
$j${"agent": "POLICY_AGENT",
|
|
100
|
+
"welcome": "Hello! Ask me about the HR policies, e.g. how much time off you get when you move for the company.",
|
|
101
|
+
"placeholder": "e.g. Do I get time off for a family move?",
|
|
102
|
+
"max_turns": 20,
|
|
103
|
+
"error_message": "The policy assistant could not answer just now. Please try again later."}$j$)
|
|
104
|
+
) as r (seq, title, type, cols, source, cond, config)
|
|
105
|
+
where a.alias = 'hr' and p.page_no = 49;
|
|
106
|
+
|
|
107
|
+
insert into meta.nav_entry (app_id, seq, label, icon, target_page)
|
|
108
|
+
select id, 30, 'Policy questions', 'book', 49 from meta.app where alias = 'hr';
|
|
109
|
+
|
|
110
|
+
-- with pgvector: the extension (when this connection may create it), the vector column, the search configuration
|
|
111
|
+
-- and the search page
|
|
112
|
+
do $$
|
|
113
|
+
declare
|
|
114
|
+
v_schema text;
|
|
115
|
+
begin
|
|
116
|
+
if not exists (select from pg_extension where extname = 'vector') then
|
|
117
|
+
if exists (select from pg_available_extensions where name = 'vector') and (select rolsuper from pg_roles where rolname = current_user) then
|
|
118
|
+
create extension vector;
|
|
119
|
+
else
|
|
120
|
+
raise notice 'HR part 72: pgvector is not installed: hr.policy gets no vector column (install pgvector, then add one with Table Properties › Add vector column).';
|
|
121
|
+
return;
|
|
122
|
+
end if;
|
|
123
|
+
end if;
|
|
124
|
+
select n.nspname into v_schema from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'vector';
|
|
125
|
+
execute format('alter table hr.policy add column embedding %I.vector(8)', v_schema);
|
|
126
|
+
execute format('create index policy_embedding_idx on hr.policy using hnsw (embedding %I.vector_cosine_ops)', v_schema);
|
|
127
|
+
execute format($t$create trigger embedding_embed before insert or update of title, body on hr.policy
|
|
128
|
+
for each row execute function meta.embedding_set(%L, 'POLICY_EMBEDDINGS', 'embedding', 'id', 'title', 'body')$t$,
|
|
129
|
+
(select id from meta.app where alias = 'hr'));
|
|
130
|
+
-- the policies there are: embedded now (the trigger does it for new and changed ones)
|
|
131
|
+
execute format($u$update hr.policy set embedding = translate(meta.embed(meta.embedding_text(to_jsonb(policy), '{title,body}'), 'POLICY_EMBEDDINGS', %s)::text, '{}', '[]')::text::%I.vector$u$,
|
|
132
|
+
(select id from meta.app where alias = 'hr'), v_schema);
|
|
133
|
+
insert into meta.search_config (app_id, name, label, seq, source_type, source, search_type, fts_vector, fts_config,
|
|
134
|
+
vector_provider, vector_column, key_column, distance, min_similarity,
|
|
135
|
+
title_column, description_column, icon, max_results)
|
|
136
|
+
select id, 'POLICIES_MEANING', 'Policies by meaning', 26, 'table', 'hr.policy', 'hybrid', 'search_vector', 'english',
|
|
137
|
+
'POLICY_EMBEDDINGS', 'embedding', 'id', 'cosine', 0.6,
|
|
138
|
+
'title', 'body', 'book', 3
|
|
139
|
+
from meta.app where alias = 'hr';
|
|
140
|
+
update meta.region r
|
|
141
|
+
set config = jsonb_set(r.config, '{configs}', (r.config->'configs') || '["POLICIES_MEANING"]'::jsonb)
|
|
142
|
+
from meta.page p join meta.app a on a.id = p.app_id
|
|
143
|
+
where r.page_id = p.id and a.alias = 'hr' and p.page_no = 42 and r.type = 'search';
|
|
144
|
+
end $$;
|
|
145
|
+
|
|
146
|
+
insert into meta.translation (app_id, language, source, target)
|
|
147
|
+
select a.id, 'nl', t.source, t.target
|
|
148
|
+
from meta.app a, (values
|
|
149
|
+
('Policies by meaning', 'Regelingen op betekenis'),
|
|
150
|
+
('Policy questions', 'Vragen over regelingen'),
|
|
151
|
+
('Ask about the policies', 'Vraag naar de regelingen'),
|
|
152
|
+
('Not set up yet', 'Nog niet ingesteld'),
|
|
153
|
+
('Hello! Ask me about the HR policies, e.g. how much time off you get when you move for the company.',
|
|
154
|
+
'Hallo! Vraag me naar de HR-regelingen, bijv. hoeveel vrij je krijgt als je voor het bedrijf verhuist.'),
|
|
155
|
+
('e.g. Do I get time off for a family move?', 'bijv. Krijg ik vrij voor een verhuizing met het gezin?')
|
|
156
|
+
) as t (source, target)
|
|
157
|
+
where a.alias = 'hr'
|
|
158
|
+
on conflict do nothing;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 73: a configurable build option (sprint 137, AP-3;
|
|
3
|
+
-- migration 137; docs/guide/06-processing.md "Build options",
|
|
4
|
+
-- 03-builder.md "Add feature")
|
|
5
|
+
--
|
|
6
|
+
-- LEAVE_FORECAST (part 22: the Forecast region of page 22, excluded) is
|
|
7
|
+
-- marked configurable. In the builder, Create page → Feature →
|
|
8
|
+
-- Configuration options then adds page 9020 "Configuration" where HR's
|
|
9
|
+
-- administrators (king, demo) switch the forecast on and off while the
|
|
10
|
+
-- application runs, without a developer. Nothing else changes until that
|
|
11
|
+
-- feature is added.
|
|
12
|
+
-- =====================================================================
|
|
13
|
+
|
|
14
|
+
update meta.build_option o set configurable = true,
|
|
15
|
+
description = coalesce(o.description, 'The leave forecast on the leave planner (page 22)')
|
|
16
|
+
from meta.app a
|
|
17
|
+
where a.id = o.app_id and a.alias = 'hr' and o.name = 'LEAVE_FORECAST';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 74: an application's Utilities (sprint 138, AP-5;
|
|
3
|
+
-- migration 138; docs/guide/03-builder.md "Utilities")
|
|
4
|
+
--
|
|
5
|
+
-- UI defaults (App Builder → Utilities → UI defaults, or Workspace
|
|
6
|
+
-- utilities → UI defaults) for hr.leave_request: the page wizards and the
|
|
7
|
+
-- Create Application wizard label its "days" column "Working days" and
|
|
8
|
+
-- show it with a number format mask, and a generated form item for
|
|
9
|
+
-- "reason" gets a help text. Pages that exist already don't change: create
|
|
10
|
+
-- a form on hr.leave_request (Create Page → Form) to see them.
|
|
11
|
+
--
|
|
12
|
+
-- The rest of the Utilities page needs no example data: Database object
|
|
13
|
+
-- dependencies, Change history (recorded from the next change on),
|
|
14
|
+
-- Upgrade application and the application checksum work on the HR
|
|
15
|
+
-- application as it is.
|
|
16
|
+
-- =====================================================================
|
|
17
|
+
|
|
18
|
+
insert into meta.ui_default (schema_name, table_name, column_name, label, format_mask, help, alignment, updated_by)
|
|
19
|
+
values
|
|
20
|
+
('hr', 'leave_request', 'days', 'Working days', '990', 'Working days the leave takes, without weekends and public holidays.', 'end', 'admin'),
|
|
21
|
+
('hr', 'leave_request', 'reason', null, null, 'Optional: what the leave is for. Your manager sees it when deciding.', null, 'admin')
|
|
22
|
+
on conflict (schema_name, table_name, column_name) do nothing;
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -1602,6 +1602,15 @@ img.tc-avatar { object-fit: cover; }
|
|
|
1602
1602
|
.tc-media { display: flex; gap: 0.8rem; align-items: center; padding: 0.6rem 0; min-width: 0; overflow-wrap: anywhere; }
|
|
1603
1603
|
.tc-media > .tc-stack { flex: 1; }
|
|
1604
1604
|
.tc-media-badge { flex: none; }
|
|
1605
|
+
/* (137) ut_content_row: avatar, title and description, secondary text and badge on the right */
|
|
1606
|
+
.tc-content-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
|
|
1607
|
+
.tc-content-row { display: flex; gap: 0.8rem; align-items: center; padding: 0.6rem 0; min-width: 0; overflow-wrap: anywhere; }
|
|
1608
|
+
.tc-content-row + .tc-content-row { border-top: 1px solid var(--border); }
|
|
1609
|
+
.tc-content-row > .tc-stack { flex: 1; }
|
|
1610
|
+
.tc-content-row-end { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; flex: none; max-width: 40%; text-align: end; }
|
|
1611
|
+
@media (max-width: 480px) { .tc-content-row { flex-wrap: wrap; } .tc-content-row-end { flex-direction: row; align-items: center; max-width: none; margin-inline-start: auto; } }
|
|
1612
|
+
/* (137) a checkbox group shown as switches ({"switches": true}): one per line */
|
|
1613
|
+
.radio-group.switch-group { flex-direction: column; align-items: flex-start; }
|
|
1605
1614
|
.tc-metrics { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr)); gap: 0.75rem; }
|
|
1606
1615
|
.tc-metric { gap: 0.25rem; }
|
|
1607
1616
|
.tc-metric-label { margin: 0; color: var(--muted); font-size: 0.85rem; font-weight: 600; }
|