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
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import { owner, type Client } from '../db.ts';
|
|
3
|
+
import { html, raw, type Raw } from '../html.ts';
|
|
4
|
+
import { icon } from '../icons.ts';
|
|
5
|
+
import { appPublicKey } from '../push.ts';
|
|
6
|
+
import { secretKeyConfigured } from '../secrets.ts';
|
|
7
|
+
import { appOr404 } from './forms.ts';
|
|
8
|
+
import { createLoginPage } from './login-page.ts';
|
|
9
|
+
import { ABOUT_PAGE, ACTIVITY_PAGE, addAboutPage, addAccessSchemes, addActivityPage, ensureAdminScheme } from './newapp.ts';
|
|
10
|
+
import { appHeader, back, BASE, csrf, developer, flash, region, send, shell, type Req } from './ui.ts';
|
|
11
|
+
|
|
12
|
+
// (sprint 137, AP-3) Add feature (APEX: Create a Page → Feature, and App home → Tasks → Add Feature Page):
|
|
13
|
+
// the Create Application wizard's features (newapp.ts) and a few more, added to an existing application.
|
|
14
|
+
// Each says what it adds before it creates anything, refuses to install twice, and shows as installed
|
|
15
|
+
// (greyed out) on the Create page grid's Feature tab afterwards. Everything is a plain form.
|
|
16
|
+
|
|
17
|
+
/** The page number of the Configuration page (Configuration options). */
|
|
18
|
+
export const CONFIG_PAGE = 9020;
|
|
19
|
+
|
|
20
|
+
interface AppRow {
|
|
21
|
+
id: number;
|
|
22
|
+
alias: string;
|
|
23
|
+
name: string;
|
|
24
|
+
authentication: string;
|
|
25
|
+
feedback: boolean;
|
|
26
|
+
pwa: boolean;
|
|
27
|
+
pwa_push: boolean;
|
|
28
|
+
login_page: number | null;
|
|
29
|
+
theme: Record<string, unknown> | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Feature {
|
|
33
|
+
key: string;
|
|
34
|
+
label: string;
|
|
35
|
+
icon: string;
|
|
36
|
+
help: string;
|
|
37
|
+
/** what installing adds, in words */
|
|
38
|
+
adds: (a: AppRow) => string[];
|
|
39
|
+
/** installed already */
|
|
40
|
+
installed: (c: Client, a: AppRow) => Promise<boolean>;
|
|
41
|
+
/** why it can't be installed (when not installed), or null */
|
|
42
|
+
blocked?: (c: Client, a: AppRow) => Promise<string | null>;
|
|
43
|
+
/** fields of the review form */
|
|
44
|
+
fields?: (a: AppRow) => Promise<Raw | string>;
|
|
45
|
+
/** install (in the caller's transaction); returns the page to open, if any */
|
|
46
|
+
install: (c: Client, a: AppRow, b: Record<string, unknown>) => Promise<number | null>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const pageExists = async (c: Client, appId: number, no: number) => !!(await c.query('select 1 from meta.page where app_id = $1 and page_no = $2', [appId, no])).rowCount;
|
|
50
|
+
const pageId = async (c: Client, appId: number, no: number) => (await c.query<{ id: number }>('select id from meta.page where app_id = $1 and page_no = $2', [appId, no])).rows[0]?.id ?? null;
|
|
51
|
+
/** a page of the feature: the page number holds a component whose code mentions the marker */
|
|
52
|
+
const pageHas = async (c: Client, appId: number, no: number, marker: string) =>
|
|
53
|
+
!!(await c.query(
|
|
54
|
+
`select 1 from meta.page p where p.app_id = $1 and p.page_no = $2
|
|
55
|
+
and (exists (select 1 from meta.region r where r.page_id = p.id and r.source like '%' || $3 || '%')
|
|
56
|
+
or exists (select 1 from meta.item i where i.page_id = p.id and i.lov like '%' || $3 || '%')
|
|
57
|
+
or exists (select 1 from meta.process x where x.page_id = p.id and x.code like '%' || $3 || '%'))`,
|
|
58
|
+
[appId, no, marker],
|
|
59
|
+
)).rowCount;
|
|
60
|
+
|
|
61
|
+
const EMAIL = /^[^\s@,;<>"]+@[^\s@,;<>"]+\.[^\s@,;<>"]+$/;
|
|
62
|
+
|
|
63
|
+
export const FEATURE_LIST: Feature[] = [
|
|
64
|
+
{
|
|
65
|
+
key: 'access',
|
|
66
|
+
label: 'Access control',
|
|
67
|
+
icon: 'lock',
|
|
68
|
+
help: 'Authorization schemes for the roles admin, contributor and reader, to put on pages, regions and buttons.',
|
|
69
|
+
adds: () => [
|
|
70
|
+
'Authorization schemes CONTRIBUTOR (role admin or contributor) and READER (admin, contributor or reader), and ADMIN when the application has none',
|
|
71
|
+
'Give users their roles under Shared Components → Users (application access)',
|
|
72
|
+
],
|
|
73
|
+
installed: async (c, a) => (await c.query(`select count(*)::int as n from meta.authz_scheme where app_id = $1 and name in ('CONTRIBUTOR', 'READER')`, [a.id])).rows[0].n === 2,
|
|
74
|
+
install: async (c, a) => {
|
|
75
|
+
await addAccessSchemes(c, a.id);
|
|
76
|
+
return null;
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'activity',
|
|
81
|
+
label: 'Activity reporting',
|
|
82
|
+
icon: 'activity',
|
|
83
|
+
help: 'A page for administrators with the page views per day and the latest activity.',
|
|
84
|
+
adds: () => [
|
|
85
|
+
`Page ${ACTIVITY_PAGE} "Activity" (authorization ADMIN): a chart of the page views per day and a report of the latest activity, from meta.app_activity()`,
|
|
86
|
+
'A navigation entry Activity for administrators',
|
|
87
|
+
'Authorization scheme ADMIN (role admin) when the application has none',
|
|
88
|
+
],
|
|
89
|
+
installed: (c, a) => pageHas(c, a.id, ACTIVITY_PAGE, 'meta.app_activity'),
|
|
90
|
+
blocked: async (c, a) => ((await pageExists(c, a.id, ACTIVITY_PAGE)) ? `Page ${ACTIVITY_PAGE} is in use by another page.` : null),
|
|
91
|
+
install: async (c, a) => {
|
|
92
|
+
await addActivityPage(c, a.id);
|
|
93
|
+
return pageId(c, a.id, ACTIVITY_PAGE);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'config',
|
|
98
|
+
label: 'Configuration options',
|
|
99
|
+
icon: 'sliders',
|
|
100
|
+
help: 'A page where the application\'s administrators switch build options on and off while it runs.',
|
|
101
|
+
adds: () => [
|
|
102
|
+
`Page ${CONFIG_PAGE} "Configuration" (authorization ADMIN): the configurable build options as switches, and Save`,
|
|
103
|
+
'The build options ticked below become configurable (Shared Components → Build options → Configurable)',
|
|
104
|
+
'A navigation entry Configuration for administrators',
|
|
105
|
+
],
|
|
106
|
+
installed: (c, a) => pageHas(c, a.id, CONFIG_PAGE, 'meta.set_configurable_options'),
|
|
107
|
+
blocked: async (c, a) => ((await pageExists(c, a.id, CONFIG_PAGE)) ? `Page ${CONFIG_PAGE} is in use by another page.` : null),
|
|
108
|
+
fields: async (a) => {
|
|
109
|
+
const opts = (await owner.query<{ name: string; description: string | null; status: string; configurable: boolean }>(
|
|
110
|
+
'select name, description, status, configurable from meta.build_option where app_id = $1 order by name', [a.id])).rows;
|
|
111
|
+
return opts.length
|
|
112
|
+
? html`<fieldset class="prop-group"><legend>Configurable build options</legend><div class="qb-pick">${opts.map((o) =>
|
|
113
|
+
html`<label class="check"><input type="checkbox" name="options" value="${o.name}"${o.configurable ? raw(' checked') : ''}> ${o.name} <span class="muted small">${o.status}${o.description ? `, ${o.description}` : ''}</span></label>`)}</div></fieldset>`
|
|
114
|
+
: html`<p class="muted">The application has no build options yet: add them under Shared Components → Build options and tick <em>Configurable</em>; the page shows them as they come.</p>`;
|
|
115
|
+
},
|
|
116
|
+
install: async (c, a, b) => {
|
|
117
|
+
const chosen = ([] as unknown[]).concat(b.options ?? []).map(String);
|
|
118
|
+
await c.query('update meta.build_option set configurable = (name = any($2)) where app_id = $1', [a.id, chosen]);
|
|
119
|
+
await ensureAdminScheme(c, a.id);
|
|
120
|
+
const home = (await c.query('select home_page from meta.app where id = $1', [a.id])).rows[0].home_page;
|
|
121
|
+
const page = (await c.query(`insert into meta.page (app_id, page_no, name, title, parent_page, authz) values ($1, $2, 'Configuration', 'Configuration', $3, 'ADMIN') returning id`, [a.id, CONFIG_PAGE, home])).rows[0].id;
|
|
122
|
+
const regionId = (await c.query(`insert into meta.region (page_id, seq, title, type) values ($1, 10, 'Configuration options', 'form') returning id`, [page])).rows[0].id;
|
|
123
|
+
const item = `P${CONFIG_PAGE}_OPTIONS`;
|
|
124
|
+
await c.query(
|
|
125
|
+
`insert into meta.item (page_id, region_id, seq, name, label, type, lov, config, help) values ($1, $2, 10, $3, 'Options', 'checkbox_group',
|
|
126
|
+
'select description as d, name as r from meta.configurable_options()', '{"switches": true}', 'Switched on: the parts of the application that belong to the option are shown.')`,
|
|
127
|
+
[page, regionId, item],
|
|
128
|
+
);
|
|
129
|
+
await c.query(
|
|
130
|
+
`insert into meta.computation (page_id, seq, item_name, point, type, expression) values ($1, 10, $2, 'before_header', 'sql_query',
|
|
131
|
+
'select string_agg(name, '':'' order by name) from meta.configurable_options() where enabled')`,
|
|
132
|
+
[page, item],
|
|
133
|
+
);
|
|
134
|
+
await c.query(`insert into meta.button (page_id, region_id, seq, name, label, action, hot) values ($1, $2, 10, 'SAVE', 'Save', 'submit', true)`, [page, regionId]);
|
|
135
|
+
await c.query(
|
|
136
|
+
`insert into meta.process (page_id, seq, name, type, code, when_button, success_message) values ($1, 10, 'Save the configuration', 'sql', $2, 'SAVE', 'Configuration saved.')`,
|
|
137
|
+
[page, `select meta.set_configurable_options(:${item})`],
|
|
138
|
+
);
|
|
139
|
+
await c.query(`insert into meta.nav_entry (app_id, seq, label, icon, target_page, authz) values ($1, 9020, 'Configuration', 'sliders', $2, 'ADMIN')`, [a.id, CONFIG_PAGE]);
|
|
140
|
+
return page;
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
key: 'email',
|
|
145
|
+
label: 'E-mail reporting',
|
|
146
|
+
icon: 'mail',
|
|
147
|
+
help: 'A weekly e-mail to the administrators with the application\'s activity, the rows as a CSV file.',
|
|
148
|
+
adds: () => [
|
|
149
|
+
'Application setting EMAIL_REPORT_TO: the addresses the report goes to (Shared Components → Application settings)',
|
|
150
|
+
'Automation EMAIL_REPORTING, every Monday at 07:00 UTC: meta.send_activity_report() with the last 7 days (Shared Components → Automations)',
|
|
151
|
+
'Mail goes out through the application\'s mail service (Workspace utilities → Mail services); without addresses nothing is sent',
|
|
152
|
+
],
|
|
153
|
+
installed: async (c, a) => !!(await c.query(`select 1 from meta.automation where app_id = $1 and name = 'EMAIL_REPORTING'`, [a.id])).rowCount,
|
|
154
|
+
fields: async () => html`<div class="field"><label class="label" for="f_report_to">Send it to</label>
|
|
155
|
+
<input id="f_report_to" name="report_to" type="text" placeholder="admin@example.com, boss@example.com" autocomplete="off">
|
|
156
|
+
<small class="help">Comma separated; may stay empty and be set later in Shared Components → Application settings (EMAIL_REPORT_TO).</small></div>`,
|
|
157
|
+
install: async (c, a, b) => {
|
|
158
|
+
const to = String(b.report_to ?? '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
159
|
+
const bad = to.find((x) => !EMAIL.test(x));
|
|
160
|
+
if (bad) throw new Error(`"${bad.slice(0, 100)}" is not an e-mail address.`);
|
|
161
|
+
if (to.join(', ').length > 4000) throw new Error('Too many addresses.');
|
|
162
|
+
await c.query(
|
|
163
|
+
`insert into meta.app_setting (app_id, name, value, description) values ($1, 'EMAIL_REPORT_TO', $2, 'E-mail reporting: the addresses of the weekly activity report (comma separated)')
|
|
164
|
+
on conflict (app_id, name) do nothing`,
|
|
165
|
+
[a.id, to.join(', ') || null],
|
|
166
|
+
);
|
|
167
|
+
await c.query(
|
|
168
|
+
`insert into meta.automation (app_id, name, description, schedule, time_zone) values ($1, 'EMAIL_REPORTING', 'E-mail reporting: the activity of the last 7 days to EMAIL_REPORT_TO', '0 7 * * 1', 'UTC')`,
|
|
169
|
+
[a.id],
|
|
170
|
+
);
|
|
171
|
+
await c.query(`insert into meta.automation_action (app_id, automation_name, seq, name, code) values ($1, 'EMAIL_REPORTING', 10, 'Send the activity report', $2)`, [a.id, CODE_EMAIL]);
|
|
172
|
+
return null;
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: 'feedback',
|
|
177
|
+
label: 'Feedback',
|
|
178
|
+
icon: 'message-square',
|
|
179
|
+
help: 'Users send feedback from the header; you read it under the application\'s Activity → Feedback.',
|
|
180
|
+
adds: () => ['A Feedback entry in the header for signed-in users (Settings → Feedback)', 'Their feedback, with the page, under Activity → Feedback in the builder'],
|
|
181
|
+
installed: async (_c, a) => a.feedback,
|
|
182
|
+
install: async (c, a) => {
|
|
183
|
+
await c.query('update meta.app set feedback = true, updated_at = now() where id = $1', [a.id]);
|
|
184
|
+
return null;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
key: 'about',
|
|
189
|
+
label: 'About page',
|
|
190
|
+
icon: 'info',
|
|
191
|
+
help: 'An About page in a Help menu of the header.',
|
|
192
|
+
adds: () => [`Page ${ABOUT_PAGE} "About" with the application's name and the pages of its menu`, 'A Help menu with About in the navigation bar (list NAVBAR), unless the application has a navigation bar list already'],
|
|
193
|
+
installed: (c, a) => pageExists(c, a.id, ABOUT_PAGE),
|
|
194
|
+
install: async (c, a) => {
|
|
195
|
+
await addAboutPage(c, a.id);
|
|
196
|
+
return pageId(c, a.id, ABOUT_PAGE);
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: 'login',
|
|
201
|
+
label: 'Login page',
|
|
202
|
+
icon: 'log-in',
|
|
203
|
+
help: 'The application\'s own login page instead of the built-in sign-in page, editable in the Page Designer.',
|
|
204
|
+
adds: () => ['Page 9999 (or the next free number) "Login": public, page template Minimal, with a Login region', 'It becomes the login page (Settings → Sign-in methods)'],
|
|
205
|
+
installed: async (_c, a) => a.login_page !== null,
|
|
206
|
+
blocked: async (_c, a) => (a.authentication === 'none' ? 'The application has no sign-in (authentication none).' : null),
|
|
207
|
+
install: async (c, a) => (await createLoginPage(c, a)).page,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: 'theme',
|
|
211
|
+
label: 'Theme style selection',
|
|
212
|
+
icon: 'palette',
|
|
213
|
+
help: 'Users choose the light or the dark theme in their user menu (Appearance).',
|
|
214
|
+
adds: () => ['Appearance in the user menu: light, dark or the device\'s setting (Settings → Theme → Users may choose)'],
|
|
215
|
+
installed: async (_c, a) => a.theme?.user_choice !== false,
|
|
216
|
+
install: async (c, a) => {
|
|
217
|
+
await c.query(`update meta.app set theme = coalesce(theme, '{}') - 'user_choice', updated_at = now() where id = $1`, [a.id]);
|
|
218
|
+
return null;
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
key: 'push',
|
|
223
|
+
label: 'Push notifications',
|
|
224
|
+
icon: 'bell',
|
|
225
|
+
help: 'Notifications to the users\' devices, from processes and SQL (meta.send_push); the application becomes installable.',
|
|
226
|
+
adds: () => ['Install as an app (PWA): a manifest, a service worker and an Install button', 'Push notifications: users subscribe on My account; send with the send_push process or meta.send_push', 'The application\'s push key pair (kept encrypted)'],
|
|
227
|
+
installed: async (_c, a) => a.pwa_push,
|
|
228
|
+
blocked: async () => (secretKeyConfigured() ? null : 'Push notifications need PGKILN_SECRET_KEY in the server\'s environment.'),
|
|
229
|
+
install: async (c, a) => {
|
|
230
|
+
await c.query('update meta.app set pwa = true, pwa_push = true, updated_at = now() where id = $1', [a.id]);
|
|
231
|
+
return null;
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const CODE_EMAIL = `select meta.send_activity_report(meta.get_app_setting('EMAIL_REPORT_TO'), 7)`;
|
|
237
|
+
|
|
238
|
+
const FEATURES_BY_KEY = new Map(FEATURE_LIST.map((f) => [f.key, f]));
|
|
239
|
+
|
|
240
|
+
async function appRow(id: string | number, c?: Client): Promise<AppRow | null> {
|
|
241
|
+
const q = 'select id, alias, name, authentication, feedback, pwa, pwa_push, login_page, theme from meta.app where id = $1';
|
|
242
|
+
return c ? ((await c.query<AppRow>(q, [id])).rows[0] ?? null) : ((await owner.one<AppRow>(q, [id])) ?? null);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Each feature with whether it is installed or blocked, for the Feature tab. */
|
|
246
|
+
export async function featureStates(appId: number) {
|
|
247
|
+
return owner.tx(async (c) => {
|
|
248
|
+
const a = (await appRow(appId, c))!;
|
|
249
|
+
const out: { f: Feature; installed: boolean; blocked: string | null }[] = [];
|
|
250
|
+
for (const f of FEATURE_LIST) {
|
|
251
|
+
const installed = await f.installed(c, a);
|
|
252
|
+
out.push({ f, installed, blocked: installed ? null : ((await f.blocked?.(c, a)) ?? null) });
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function featureRoutes(app: FastifyInstance) {
|
|
259
|
+
// the review: what the feature adds, and Install (or why not)
|
|
260
|
+
app.get(`${BASE}/apps/:id/features/:key`, async (req: Req, reply) => {
|
|
261
|
+
const s = await developer(req, reply);
|
|
262
|
+
if (!s) return;
|
|
263
|
+
const a0 = await appOr404(req.params.id);
|
|
264
|
+
const f = FEATURES_BY_KEY.get(String(req.params.key));
|
|
265
|
+
if (!a0 || !f) return reply.code(404).send('Not found');
|
|
266
|
+
const a = (await appRow(a0.id))!;
|
|
267
|
+
const { installed, blocked } = await owner.tx(async (c) => {
|
|
268
|
+
const installed = await f.installed(c, a);
|
|
269
|
+
return { installed, blocked: installed ? null : ((await f.blocked?.(c, a)) ?? null) };
|
|
270
|
+
});
|
|
271
|
+
const main = html`${appHeader(a0, 'pages', s)}
|
|
272
|
+
${region(f.label, html`
|
|
273
|
+
<p class="muted u-mt0">${icon(f.icon)} ${f.help}</p>
|
|
274
|
+
${installed
|
|
275
|
+
? html`<p class="alert alert-info" role="status">${f.label} is installed in this application already.</p>`
|
|
276
|
+
: blocked ? html`<p class="alert alert-error" role="status">${blocked}</p>` : ''}
|
|
277
|
+
<h3>${installed ? 'It added' : 'It adds'}</h3>
|
|
278
|
+
<ul class="cp-adds">${f.adds(a).map((x) => html`<li>${x}</li>`)}</ul>
|
|
279
|
+
${installed || blocked
|
|
280
|
+
? html`<div class="buttons"><a class="btn" href="${BASE}/apps/${a.id}/create-page?tab=feature">Back to the features</a></div>`
|
|
281
|
+
: html`<form method="post" action="${BASE}/apps/${a.id}/features/${f.key}">${csrf(s)}
|
|
282
|
+
${f.fields ? await f.fields(a) : ''}
|
|
283
|
+
<div class="buttons"><a class="btn" href="${BASE}/apps/${a.id}/create-page?tab=feature">Cancel</a><button class="btn btn-hot">Add ${f.label.toLowerCase()}</button></div>
|
|
284
|
+
</form>`}`)}`;
|
|
285
|
+
return send(reply, s, shell(s, `${f.label} · ${a.name}`, [['App Builder', BASE], [a.name, `${BASE}/apps/${a.id}`], ['Create page', `${BASE}/apps/${a.id}/create-page?tab=feature`], [f.label]], main));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
app.post(`${BASE}/apps/:id/features/:key`, async (req: Req, reply) => {
|
|
289
|
+
const s = await developer(req, reply);
|
|
290
|
+
if (!s) return;
|
|
291
|
+
const f = FEATURES_BY_KEY.get(String(req.params.key));
|
|
292
|
+
if (!f || !(await appRow(req.params.id))) return reply.code(404).send('Not found');
|
|
293
|
+
const b = (req.body ?? {}) as Record<string, unknown>;
|
|
294
|
+
try {
|
|
295
|
+
const page = await owner.tx(async (c) => {
|
|
296
|
+
// one at a time per application, and never twice
|
|
297
|
+
await c.query('select 1 from meta.app where id = $1 for update', [req.params.id]);
|
|
298
|
+
const a = (await appRow(req.params.id, c))!;
|
|
299
|
+
if (await f.installed(c, a)) throw new Error(`${f.label} is installed in this application already.`);
|
|
300
|
+
const blocked = await f.blocked?.(c, a);
|
|
301
|
+
if (blocked) throw new Error(blocked);
|
|
302
|
+
return f.install(c, a, b);
|
|
303
|
+
});
|
|
304
|
+
// the push key pair after the commit (as the Create Application wizard does)
|
|
305
|
+
if (f.key === 'push') await appPublicKey(Number(req.params.id)).catch(() => undefined);
|
|
306
|
+
flash(s, `${f.label} added.`);
|
|
307
|
+
return back(reply, s, page ? `${BASE}/pages/${page}` : `${BASE}/apps/${req.params.id}/create-page?tab=feature`);
|
|
308
|
+
} catch (e) {
|
|
309
|
+
flash(s, (e as Error).message, 'error');
|
|
310
|
+
return back(reply, s, `${BASE}/apps/${req.params.id}/features/${f.key}`);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
package/src/builder/home.ts
CHANGED
|
@@ -295,6 +295,9 @@ export async function homeRoutes(app: FastifyInstance) {
|
|
|
295
295
|
: s.workspace && (await workspaceAdmin(s.username, s.workspace.id))
|
|
296
296
|
? card(`${BASE}/workspaces/${s.workspace.id}`, icon('layers'), `Workspace ${s.workspace.name}`, 'Its name, developers, preferences and extension links (workspace administrators).')
|
|
297
297
|
: ''}
|
|
298
|
+
${card(`${BASE}/ui-defaults`, icon('sliders-horizontal'), 'UI defaults', 'Labels, format masks, help and lists of values per table column, used by the page wizards.')}
|
|
299
|
+
${card(`${BASE}/icons`, icon('image'), 'Icons', 'Browse the icons of the icon picker.')}
|
|
300
|
+
${card(`${BASE}/vector-providers`, icon('search'), 'Vector providers', 'Embeddings for search by meaning: the applications\' vector providers, their vector columns, and whether pgvector is installed.')}
|
|
298
301
|
${(await isAdmin(s.username)) ? card(`${BASE}/ai`, icon('bolt'), 'AI services', 'Claude and OpenAI for the applications: models, keys, limits and usage (administrators).') : ''}
|
|
299
302
|
${(await isAdmin(s.username)) ? card(`${BASE}/mail`, icon('send'), 'Mail services', 'SMTP, Microsoft 365, Amazon SES, Resend, Postmark, SendGrid or Mailgun for the applications\' e-mail (administrators).') : ''}
|
|
300
303
|
${(await isAdmin(s.username)) ? card(`${BASE}/installation`, icon('history'), 'Installation', 'Version, install and upgrade runs, applied migrations (administrators).') : ''}
|
package/src/builder/i18n/de.ts
CHANGED
|
@@ -285,6 +285,7 @@ export const de: BuilderMessages = {
|
|
|
285
285
|
'app.tab.api': 'REST-API',
|
|
286
286
|
'app.tab.settings': 'Einstellungen',
|
|
287
287
|
'app.tab.advisor': 'Advisor',
|
|
288
|
+
'app.tab.utilities': 'Dienstprogramme',
|
|
288
289
|
'app.lock_refused': 'Änderungen werden abgelehnt, bis die Sperre aufgehoben ist.',
|
|
289
290
|
'app.search_pages': 'Seiten durchsuchen',
|
|
290
291
|
'app.search_placeholder': 'Seitenname oder -nummer',
|
|
@@ -318,6 +319,9 @@ export const de: BuilderMessages = {
|
|
|
318
319
|
'app.table_or_view': 'Tabelle oder View (Mastertabelle für Master-Detail)',
|
|
319
320
|
'app.no_access': '{table} (kein Zugriff für {role})',
|
|
320
321
|
'app.ai_pages': 'Seiten mit KI erstellen',
|
|
322
|
+
'app.all_page_types': "Alle Seitentypen, Funktionen und Kopien",
|
|
323
|
+
'app.task.add_feature': "Funktionsseite hinzufügen",
|
|
324
|
+
'app.task.copy_page': "Seite als Kopie erstellen",
|
|
321
325
|
'app.ai_pages_note': '(beschreiben Sie sie mit eigenen Worten)',
|
|
322
326
|
'app.blank': 'Leere Seite erstellen',
|
|
323
327
|
'app.page_number': 'Seitennummer',
|
|
@@ -1065,6 +1069,40 @@ export const de: BuilderMessages = {
|
|
|
1065
1069
|
'fts.gin_yes': "GIN-Index",
|
|
1066
1070
|
'fts.gin_no': "kein GIN-Index",
|
|
1067
1071
|
'fts.added': "Volltextsuche zu {table} hinzugefügt: Spalte {column} mit Index {index}.",
|
|
1072
|
+
// (sprint 136, AP-1) Table Properties › Add vector column
|
|
1073
|
+
'vec.add': "Vektorspalte hinzufügen",
|
|
1074
|
+
'vec.title': "Vektorspalte für {table}",
|
|
1075
|
+
'vec.intro': "Fügt eine Embedding-Spalte vector(n) hinzu (n: die Dimensionen des Vektoranbieters), einen HNSW-Index für die Distanz und einen Trigger, der eine Zeile neu einbettet, wenn sich ihre Quellspalten ändern. Eine Suchkonfiguration vom Typ vector oder hybrid oder das Retrieve-Werkzeug eines KI-Agenten findet Zeilen dann nach Bedeutung. Die vorhandenen Zeilen werden sofort für ihre Embeddings eingereiht.",
|
|
1076
|
+
'vec.existing': "Vektorspalten jetzt",
|
|
1077
|
+
'vec.provider': "Vektoranbieter",
|
|
1078
|
+
'vec.provider_help': "Gemeinsame Komponenten › Vektoranbieter der Anwendungen dieses Arbeitsbereichs. Suchen Sie mit demselben Anbieter.",
|
|
1079
|
+
'vec.keep': "Aktuell halten",
|
|
1080
|
+
'vec.keep_help': "Ein Funktionsanbieter kann das Embedding sofort im Trigger setzen. Einen KI-Dienst ruft der Server auf: Der Trigger reiht die Zeile ein, und der Server bettet eingereihte Zeilen stapelweise ein, innerhalb der Anfragen pro Minute des Anbieters und der täglichen KI-Limits der Anwendung.",
|
|
1081
|
+
'vec.mode_set': "vom Trigger gesetzt, sofort",
|
|
1082
|
+
'vec.mode_queue': "für den Server eingereiht",
|
|
1083
|
+
'vec.sources': "Quellspalten",
|
|
1084
|
+
'vec.sources_help': "Der Text, der eingebettet wird: diese Spalten, eine pro Zeile. Eine Änderung an einer davon bettet die Zeile neu ein.",
|
|
1085
|
+
'vec.distance': "Distanz",
|
|
1086
|
+
'vec.distance_help': "cosine für die meisten Embedding-Modelle; die Suchkonfiguration verwendet dieselbe Distanz.",
|
|
1087
|
+
'vec.column': "Vektorspalte",
|
|
1088
|
+
'vec.no_key': "Die Tabelle braucht einen Primärschlüssel aus einer Spalte: Die Warteschlange findet Zeilen darüber.",
|
|
1089
|
+
'vec.pick_provider': "Wählen Sie einen Vektoranbieter.",
|
|
1090
|
+
'vec.no_providers': "Noch keine Anwendung dieses Arbeitsbereichs hat einen Vektoranbieter: Fügen Sie einen unter Gemeinsame Komponenten › Vektoranbieter hinzu.",
|
|
1091
|
+
'vec.missing': "Die Erweiterung pgvector ist in dieser Datenbank nicht installiert, daher kann noch keine Vektorspalte hinzugefügt werden.",
|
|
1092
|
+
'vec.missing_create': "Der Datenbankserver hat pgvector {version}: Erstellen Sie die Erweiterung unter Arbeitsbereich-Dienstprogramme › Vektoranbieter.",
|
|
1093
|
+
'vec.missing_admin': "Der Datenbankserver hat pgvector {version}: Ein Administrator erstellt die Erweiterung (Arbeitsbereich-Dienstprogramme › Vektoranbieter).",
|
|
1094
|
+
'vec.missing_package': "Der Datenbankserver hat das Paket pgvector nicht: Installieren Sie es (z. B. apt install postgresql-17-pgvector oder das Docker-Image pgvector/pgvector) und erstellen Sie dann die Erweiterung.",
|
|
1095
|
+
'vec.utilities': "Arbeitsbereich-Dienstprogramme › Vektoranbieter",
|
|
1096
|
+
'vec.kept_by': "gepflegt von {provider} aus {columns}",
|
|
1097
|
+
'vec.not_kept': "kein Trigger hält sie aktuell",
|
|
1098
|
+
'vec.progress': "Eingebettete Zeilen von {column}",
|
|
1099
|
+
'vec.counts': "{done} von {total} Zeilen eingebettet, {queued} eingereiht",
|
|
1100
|
+
'vec.failing': "{n} fehlgeschlagen",
|
|
1101
|
+
'vec.reembed_all': "alle Zeilen, nicht nur die ohne Embedding",
|
|
1102
|
+
'vec.backfill': "Nachfüllen",
|
|
1103
|
+
'vec.refresh': "Aktualisieren",
|
|
1104
|
+
'vec.backfill_queued': "{n} Zeilen von {column} für ihre Embeddings eingereiht.",
|
|
1105
|
+
'vec.added': "Vektorspalte {column} zu {table} hinzugefügt; {n} Zeilen für ihre Embeddings eingereiht.",
|
|
1068
1106
|
// ------------------------------------------------------------ Shared Components (sprint 124)
|
|
1069
1107
|
'comp.nav_entry': 'Navigationseintrag',
|
|
1070
1108
|
'comp.nav_entry.plural': 'Navigationsmenü',
|
package/src/builder/i18n/en.ts
CHANGED
|
@@ -288,6 +288,7 @@ export const en = {
|
|
|
288
288
|
'app.tab.api': 'REST API',
|
|
289
289
|
'app.tab.settings': 'Settings',
|
|
290
290
|
'app.tab.advisor': 'Advisor',
|
|
291
|
+
'app.tab.utilities': 'Utilities',
|
|
291
292
|
'app.lock_refused': 'Changes are refused until it is unlocked.',
|
|
292
293
|
'app.search_pages': 'Search pages',
|
|
293
294
|
'app.search_placeholder': 'Page name or number',
|
|
@@ -321,6 +322,9 @@ export const en = {
|
|
|
321
322
|
'app.table_or_view': 'Table or view (master table for master detail)',
|
|
322
323
|
'app.no_access': '{table} (no access for {role})',
|
|
323
324
|
'app.ai_pages': 'Create pages with AI',
|
|
325
|
+
'app.all_page_types': "All page types, features and copies",
|
|
326
|
+
'app.task.add_feature': "Add feature page",
|
|
327
|
+
'app.task.copy_page': "Create page as copy",
|
|
324
328
|
'app.ai_pages_note': '(describe them in your own words)',
|
|
325
329
|
'app.blank': 'Create blank page',
|
|
326
330
|
'app.page_number': 'Page number',
|
|
@@ -1069,6 +1073,40 @@ export const en = {
|
|
|
1069
1073
|
'fts.gin_yes': "GIN index",
|
|
1070
1074
|
'fts.gin_no': "no GIN index",
|
|
1071
1075
|
'fts.added': "Added full-text search to {table}: column {column} with index {index}.",
|
|
1076
|
+
// (sprint 136, AP-1) Table Properties › Add vector column
|
|
1077
|
+
'vec.add': "Add vector column",
|
|
1078
|
+
'vec.title': "Vector column on {table}",
|
|
1079
|
+
'vec.intro': "Adds an embedding column vector(n) (n: the vector provider's dimensions), an HNSW index for the distance, and a trigger that re-embeds a row when its source columns change. A search configuration of type vector or hybrid, or an AI agent's Retrieve tool, then finds rows by meaning. The rows there are now are queued for their embeddings at once.",
|
|
1080
|
+
'vec.existing': "Vector columns now",
|
|
1081
|
+
'vec.provider': "Vector provider",
|
|
1082
|
+
'vec.provider_help': "Shared Components › Vector providers of this workspace's applications. Search with the same provider.",
|
|
1083
|
+
'vec.keep': "Keep current",
|
|
1084
|
+
'vec.keep_help': "A function provider can set the embedding in the trigger, at once. An AI service is called by the server: the trigger queues the row and the server embeds queued rows in batches, within the provider's requests per minute and the application's daily AI limits.",
|
|
1085
|
+
'vec.mode_set': "set by the trigger, at once",
|
|
1086
|
+
'vec.mode_queue': "queued for the server",
|
|
1087
|
+
'vec.sources': "Source columns",
|
|
1088
|
+
'vec.sources_help': "The text that is embedded: these columns, one per line. A change to one of them re-embeds the row.",
|
|
1089
|
+
'vec.distance': "Distance",
|
|
1090
|
+
'vec.distance_help': "cosine for most embedding models; the search configuration uses the same distance.",
|
|
1091
|
+
'vec.column': "Vector column",
|
|
1092
|
+
'vec.no_key': "The table needs a primary key of one column: the queue finds rows by it.",
|
|
1093
|
+
'vec.pick_provider': "Choose a vector provider.",
|
|
1094
|
+
'vec.no_providers': "No application of this workspace has a vector provider yet: add one under Shared Components › Vector providers.",
|
|
1095
|
+
'vec.missing': "The pgvector extension is not installed in this database, so a vector column can't be added yet.",
|
|
1096
|
+
'vec.missing_create': "The database server has pgvector {version}: create the extension under Workspace utilities › Vector providers.",
|
|
1097
|
+
'vec.missing_admin': "The database server has pgvector {version}: an administrator creates the extension (Workspace utilities › Vector providers).",
|
|
1098
|
+
'vec.missing_package': "The database server doesn't have the pgvector package: install it (e.g. apt install postgresql-17-pgvector, or the pgvector/pgvector Docker image), then create the extension.",
|
|
1099
|
+
'vec.utilities': "Workspace utilities › Vector providers",
|
|
1100
|
+
'vec.kept_by': "kept by {provider} from {columns}",
|
|
1101
|
+
'vec.not_kept': "no trigger keeps it current",
|
|
1102
|
+
'vec.progress': "Embedded rows of {column}",
|
|
1103
|
+
'vec.counts': "{done} of {total} rows embedded, {queued} queued",
|
|
1104
|
+
'vec.failing': "{n} failing",
|
|
1105
|
+
'vec.reembed_all': "all rows, not only those without an embedding",
|
|
1106
|
+
'vec.backfill': "Backfill",
|
|
1107
|
+
'vec.refresh': "Refresh",
|
|
1108
|
+
'vec.backfill_queued': "{n} rows of {column} queued for their embeddings.",
|
|
1109
|
+
'vec.added': "Added the vector column {column} to {table}; {n} rows queued for their embeddings.",
|
|
1072
1110
|
// ------------------------------------------------------------ Shared Components (sprint 124)
|
|
1073
1111
|
'comp.nav_entry': 'Navigation entry',
|
|
1074
1112
|
'comp.nav_entry.plural': 'Navigation menu',
|
package/src/builder/i18n/es.ts
CHANGED
|
@@ -285,6 +285,7 @@ export const es: BuilderMessages = {
|
|
|
285
285
|
'app.tab.api': 'API REST',
|
|
286
286
|
'app.tab.settings': 'Configuración',
|
|
287
287
|
'app.tab.advisor': 'Asesor',
|
|
288
|
+
'app.tab.utilities': 'Utilidades',
|
|
288
289
|
'app.lock_refused': 'Los cambios se rechazan hasta que se desbloquee.',
|
|
289
290
|
'app.search_pages': 'Buscar páginas',
|
|
290
291
|
'app.search_placeholder': 'Nombre o número de página',
|
|
@@ -318,6 +319,9 @@ export const es: BuilderMessages = {
|
|
|
318
319
|
'app.table_or_view': 'Tabla o vista (tabla maestra para maestro-detalle)',
|
|
319
320
|
'app.no_access': '{table} (sin acceso para {role})',
|
|
320
321
|
'app.ai_pages': 'Crear páginas con IA',
|
|
322
|
+
'app.all_page_types': "Todos los tipos de página, funciones y copias",
|
|
323
|
+
'app.task.add_feature': "Agregar página de función",
|
|
324
|
+
'app.task.copy_page': "Crear página como copia",
|
|
321
325
|
'app.ai_pages_note': '(descríbalas con sus propias palabras)',
|
|
322
326
|
'app.blank': 'Crear página en blanco',
|
|
323
327
|
'app.page_number': 'Número de página',
|
|
@@ -1065,6 +1069,40 @@ export const es: BuilderMessages = {
|
|
|
1065
1069
|
'fts.gin_yes': "índice GIN",
|
|
1066
1070
|
'fts.gin_no': "sin índice GIN",
|
|
1067
1071
|
'fts.added': "Búsqueda de texto completo añadida a {table}: columna {column} con el índice {index}.",
|
|
1072
|
+
// (sprint 136, AP-1) Table Properties › Add vector column
|
|
1073
|
+
'vec.add': "Añadir columna vectorial",
|
|
1074
|
+
'vec.title': "Columna vectorial en {table}",
|
|
1075
|
+
'vec.intro': "Añade una columna de embedding vector(n) (n: las dimensiones del proveedor de vectores), un índice HNSW para la distancia y un disparador que vuelve a calcular el embedding de una fila cuando cambian sus columnas de origen. Una configuración de búsqueda de tipo vector o hybrid, o la herramienta Retrieve de un agente de IA, encuentra entonces las filas por su significado. Las filas que ya existen se ponen en cola enseguida para sus embeddings.",
|
|
1076
|
+
'vec.existing': "Columnas vectoriales actuales",
|
|
1077
|
+
'vec.provider': "Proveedor de vectores",
|
|
1078
|
+
'vec.provider_help': "Componentes compartidos › Proveedores de vectores de las aplicaciones de este espacio de trabajo. Busque con el mismo proveedor.",
|
|
1079
|
+
'vec.keep': "Mantener al día",
|
|
1080
|
+
'vec.keep_help': "Un proveedor de tipo función puede fijar el embedding en el disparador, enseguida. A un servicio de IA lo llama el servidor: el disparador pone la fila en cola y el servidor calcula los embeddings por lotes, dentro de las solicitudes por minuto del proveedor y los límites diarios de IA de la aplicación.",
|
|
1081
|
+
'vec.mode_set': "fijado por el disparador, enseguida",
|
|
1082
|
+
'vec.mode_queue': "en cola para el servidor",
|
|
1083
|
+
'vec.sources': "Columnas de origen",
|
|
1084
|
+
'vec.sources_help': "El texto del que se calcula el embedding: estas columnas, una por línea. Un cambio en una de ellas vuelve a calcular el embedding de la fila.",
|
|
1085
|
+
'vec.distance': "Distancia",
|
|
1086
|
+
'vec.distance_help': "cosine para la mayoría de los modelos de embedding; la configuración de búsqueda usa la misma distancia.",
|
|
1087
|
+
'vec.column': "Columna vectorial",
|
|
1088
|
+
'vec.no_key': "La tabla necesita una clave primaria de una sola columna: la cola encuentra las filas por ella.",
|
|
1089
|
+
'vec.pick_provider': "Elija un proveedor de vectores.",
|
|
1090
|
+
'vec.no_providers': "Ninguna aplicación de este espacio de trabajo tiene aún un proveedor de vectores: añada uno en Componentes compartidos › Proveedores de vectores.",
|
|
1091
|
+
'vec.missing': "La extensión pgvector no está instalada en esta base de datos, así que todavía no se puede añadir una columna vectorial.",
|
|
1092
|
+
'vec.missing_create': "El servidor de base de datos tiene pgvector {version}: cree la extensión en Utilidades del espacio de trabajo › Proveedores de vectores.",
|
|
1093
|
+
'vec.missing_admin': "El servidor de base de datos tiene pgvector {version}: un administrador crea la extensión (Utilidades del espacio de trabajo › Proveedores de vectores).",
|
|
1094
|
+
'vec.missing_package': "El servidor de base de datos no tiene el paquete pgvector: instálelo (p. ej. apt install postgresql-17-pgvector, o la imagen de Docker pgvector/pgvector) y cree luego la extensión.",
|
|
1095
|
+
'vec.utilities': "Utilidades del espacio de trabajo › Proveedores de vectores",
|
|
1096
|
+
'vec.kept_by': "mantenida por {provider} a partir de {columns}",
|
|
1097
|
+
'vec.not_kept': "ningún disparador la mantiene al día",
|
|
1098
|
+
'vec.progress': "Filas de {column} con embedding",
|
|
1099
|
+
'vec.counts': "{done} de {total} filas con embedding, {queued} en cola",
|
|
1100
|
+
'vec.failing': "{n} con error",
|
|
1101
|
+
'vec.reembed_all': "todas las filas, no solo las que no tienen embedding",
|
|
1102
|
+
'vec.backfill': "Completar",
|
|
1103
|
+
'vec.refresh': "Actualizar",
|
|
1104
|
+
'vec.backfill_queued': "{n} filas de {column} en cola para sus embeddings.",
|
|
1105
|
+
'vec.added': "Columna vectorial {column} añadida a {table}; {n} filas en cola para sus embeddings.",
|
|
1068
1106
|
// ------------------------------------------------------------ Shared Components (sprint 124)
|
|
1069
1107
|
'comp.nav_entry': 'Entrada de navegación',
|
|
1070
1108
|
'comp.nav_entry.plural': 'Menú de navegación',
|
package/src/builder/i18n/fr.ts
CHANGED
|
@@ -285,6 +285,7 @@ export const fr: BuilderMessages = {
|
|
|
285
285
|
'app.tab.api': 'API REST',
|
|
286
286
|
'app.tab.settings': 'Paramètres',
|
|
287
287
|
'app.tab.advisor': 'Conseiller',
|
|
288
|
+
'app.tab.utilities': 'Utilitaires',
|
|
288
289
|
'app.lock_refused': "Les modifications sont refusées jusqu'à son déverrouillage.",
|
|
289
290
|
'app.search_pages': 'Rechercher des pages',
|
|
290
291
|
'app.search_placeholder': 'Nom ou numéro de page',
|
|
@@ -318,6 +319,9 @@ export const fr: BuilderMessages = {
|
|
|
318
319
|
'app.table_or_view': 'Table ou vue (table maître pour maître-détail)',
|
|
319
320
|
'app.no_access': '{table} (aucun accès pour {role})',
|
|
320
321
|
'app.ai_pages': "Créer des pages avec l'IA",
|
|
322
|
+
'app.all_page_types': "Tous les types de page, fonctionnalités et copies",
|
|
323
|
+
'app.task.add_feature': "Ajouter une page de fonctionnalité",
|
|
324
|
+
'app.task.copy_page': "Créer une page par copie",
|
|
321
325
|
'app.ai_pages_note': '(décrivez-les avec vos propres mots)',
|
|
322
326
|
'app.blank': 'Créer une page vierge',
|
|
323
327
|
'app.page_number': 'Numéro de page',
|
|
@@ -1065,6 +1069,40 @@ export const fr: BuilderMessages = {
|
|
|
1065
1069
|
'fts.gin_yes': "index GIN",
|
|
1066
1070
|
'fts.gin_no': "pas d'index GIN",
|
|
1067
1071
|
'fts.added': "Recherche plein texte ajoutée à {table} : colonne {column} avec l'index {index}.",
|
|
1072
|
+
// (sprint 136, AP-1) Table Properties › Add vector column
|
|
1073
|
+
'vec.add': "Ajouter une colonne vectorielle",
|
|
1074
|
+
'vec.title': "Colonne vectorielle sur {table}",
|
|
1075
|
+
'vec.intro': "Ajoute une colonne d'embedding vector(n) (n : les dimensions du fournisseur de vecteurs), un index HNSW pour la distance et un déclencheur qui recalcule l'embedding d'une ligne quand ses colonnes sources changent. Une configuration de recherche de type vector ou hybrid, ou l'outil Retrieve d'un agent IA, trouve alors les lignes par leur sens. Les lignes existantes sont mises en file d'attente tout de suite pour leurs embeddings.",
|
|
1076
|
+
'vec.existing': "Colonnes vectorielles actuelles",
|
|
1077
|
+
'vec.provider': "Fournisseur de vecteurs",
|
|
1078
|
+
'vec.provider_help': "Composants partagés › Fournisseurs de vecteurs des applications de cet espace de travail. Recherchez avec le même fournisseur.",
|
|
1079
|
+
'vec.keep': "Tenir à jour",
|
|
1080
|
+
'vec.keep_help': "Un fournisseur de type fonction peut définir l'embedding dans le déclencheur, tout de suite. Un service IA est appelé par le serveur : le déclencheur met la ligne en file d'attente et le serveur calcule les embeddings par lots, dans la limite des requêtes par minute du fournisseur et des limites IA quotidiennes de l'application.",
|
|
1081
|
+
'vec.mode_set': "défini par le déclencheur, tout de suite",
|
|
1082
|
+
'vec.mode_queue': "en file d'attente pour le serveur",
|
|
1083
|
+
'vec.sources': "Colonnes sources",
|
|
1084
|
+
'vec.sources_help': "Le texte dont on calcule l'embedding : ces colonnes, une par ligne. Une modification de l'une d'elles recalcule l'embedding de la ligne.",
|
|
1085
|
+
'vec.distance': "Distance",
|
|
1086
|
+
'vec.distance_help': "cosine pour la plupart des modèles d'embedding ; la configuration de recherche utilise la même distance.",
|
|
1087
|
+
'vec.column': "Colonne vectorielle",
|
|
1088
|
+
'vec.no_key': "La table a besoin d'une clé primaire d'une seule colonne : la file d'attente retrouve les lignes par elle.",
|
|
1089
|
+
'vec.pick_provider': "Choisissez un fournisseur de vecteurs.",
|
|
1090
|
+
'vec.no_providers': "Aucune application de cet espace de travail n'a encore de fournisseur de vecteurs : ajoutez-en un sous Composants partagés › Fournisseurs de vecteurs.",
|
|
1091
|
+
'vec.missing': "L'extension pgvector n'est pas installée dans cette base de données : impossible d'ajouter une colonne vectorielle pour l'instant.",
|
|
1092
|
+
'vec.missing_create': "Le serveur de base de données dispose de pgvector {version} : créez l'extension sous Utilitaires de l'espace de travail › Fournisseurs de vecteurs.",
|
|
1093
|
+
'vec.missing_admin': "Le serveur de base de données dispose de pgvector {version} : un administrateur crée l'extension (Utilitaires de l'espace de travail › Fournisseurs de vecteurs).",
|
|
1094
|
+
'vec.missing_package': "Le serveur de base de données n'a pas le paquet pgvector : installez-le (p. ex. apt install postgresql-17-pgvector, ou l'image Docker pgvector/pgvector), puis créez l'extension.",
|
|
1095
|
+
'vec.utilities': "Utilitaires de l'espace de travail › Fournisseurs de vecteurs",
|
|
1096
|
+
'vec.kept_by': "tenue par {provider} à partir de {columns}",
|
|
1097
|
+
'vec.not_kept': "aucun déclencheur ne la tient à jour",
|
|
1098
|
+
'vec.progress': "Lignes de {column} avec embedding",
|
|
1099
|
+
'vec.counts': "{done} lignes sur {total} avec embedding, {queued} en file d'attente",
|
|
1100
|
+
'vec.failing': "{n} en échec",
|
|
1101
|
+
'vec.reembed_all': "toutes les lignes, pas seulement celles sans embedding",
|
|
1102
|
+
'vec.backfill': "Compléter",
|
|
1103
|
+
'vec.refresh': "Actualiser",
|
|
1104
|
+
'vec.backfill_queued': "{n} lignes de {column} mises en file d'attente pour leurs embeddings.",
|
|
1105
|
+
'vec.added': "Colonne vectorielle {column} ajoutée à {table} ; {n} lignes en file d'attente pour leurs embeddings.",
|
|
1068
1106
|
// ------------------------------------------------------------ Shared Components (sprint 124)
|
|
1069
1107
|
'comp.nav_entry': 'Entrée de navigation',
|
|
1070
1108
|
'comp.nav_entry.plural': 'Menu de navigation',
|
package/src/builder/i18n/it.ts
CHANGED
|
@@ -285,6 +285,7 @@ export const it: BuilderMessages = {
|
|
|
285
285
|
'app.tab.api': 'API REST',
|
|
286
286
|
'app.tab.settings': 'Impostazioni',
|
|
287
287
|
'app.tab.advisor': 'Advisor',
|
|
288
|
+
'app.tab.utilities': 'Utilità',
|
|
288
289
|
'app.lock_refused': 'Le modifiche vengono rifiutate finché non viene sbloccata.',
|
|
289
290
|
'app.search_pages': 'Cerca pagine',
|
|
290
291
|
'app.search_placeholder': 'Nome o numero della pagina',
|
|
@@ -318,6 +319,9 @@ export const it: BuilderMessages = {
|
|
|
318
319
|
'app.table_or_view': 'Tabella o vista (tabella master per master-dettaglio)',
|
|
319
320
|
'app.no_access': '{table} (nessun accesso per {role})',
|
|
320
321
|
'app.ai_pages': 'Crea pagine con l’AI',
|
|
322
|
+
'app.all_page_types': "Tutti i tipi di pagina, funzionalità e copie",
|
|
323
|
+
'app.task.add_feature': "Aggiungi pagina di funzionalità",
|
|
324
|
+
'app.task.copy_page': "Crea pagina come copia",
|
|
321
325
|
'app.ai_pages_note': '(descriverle con parole proprie)',
|
|
322
326
|
'app.blank': 'Crea pagina vuota',
|
|
323
327
|
'app.page_number': 'Numero di pagina',
|
|
@@ -1065,6 +1069,40 @@ export const it: BuilderMessages = {
|
|
|
1065
1069
|
'fts.gin_yes': "indice GIN",
|
|
1066
1070
|
'fts.gin_no': "nessun indice GIN",
|
|
1067
1071
|
'fts.added': "Ricerca full-text aggiunta a {table}: colonna {column} con l'indice {index}.",
|
|
1072
|
+
// (sprint 136, AP-1) Table Properties › Add vector column
|
|
1073
|
+
'vec.add': "Aggiungi colonna vettoriale",
|
|
1074
|
+
'vec.title': "Colonna vettoriale su {table}",
|
|
1075
|
+
'vec.intro': "Aggiunge una colonna di embedding vector(n) (n: le dimensioni del provider di vettori), un indice HNSW per la distanza e un trigger che ricalcola l'embedding di una riga quando cambiano le sue colonne di origine. Una configurazione di ricerca di tipo vector o hybrid, o lo strumento Retrieve di un agente IA, trova allora le righe per significato. Le righe già presenti vengono messe subito in coda per i loro embedding.",
|
|
1076
|
+
'vec.existing': "Colonne vettoriali attuali",
|
|
1077
|
+
'vec.provider': "Provider di vettori",
|
|
1078
|
+
'vec.provider_help': "Componenti condivisi › Provider di vettori delle applicazioni di questo workspace. Cerca con lo stesso provider.",
|
|
1079
|
+
'vec.keep': "Mantieni aggiornata",
|
|
1080
|
+
'vec.keep_help': "Un provider di tipo funzione può impostare l'embedding nel trigger, subito. Un servizio IA è chiamato dal server: il trigger mette in coda la riga e il server calcola gli embedding a lotti, entro le richieste al minuto del provider e i limiti IA giornalieri dell'applicazione.",
|
|
1081
|
+
'vec.mode_set': "impostato dal trigger, subito",
|
|
1082
|
+
'vec.mode_queue': "in coda per il server",
|
|
1083
|
+
'vec.sources': "Colonne di origine",
|
|
1084
|
+
'vec.sources_help': "Il testo di cui si calcola l'embedding: queste colonne, una per riga. Una modifica a una di esse ricalcola l'embedding della riga.",
|
|
1085
|
+
'vec.distance': "Distanza",
|
|
1086
|
+
'vec.distance_help': "cosine per la maggior parte dei modelli di embedding; la configurazione di ricerca usa la stessa distanza.",
|
|
1087
|
+
'vec.column': "Colonna vettoriale",
|
|
1088
|
+
'vec.no_key': "La tabella ha bisogno di una chiave primaria di una sola colonna: la coda trova le righe tramite essa.",
|
|
1089
|
+
'vec.pick_provider': "Scegli un provider di vettori.",
|
|
1090
|
+
'vec.no_providers': "Nessuna applicazione di questo workspace ha ancora un provider di vettori: aggiungine uno in Componenti condivisi › Provider di vettori.",
|
|
1091
|
+
'vec.missing': "L'estensione pgvector non è installata in questo database, quindi non è ancora possibile aggiungere una colonna vettoriale.",
|
|
1092
|
+
'vec.missing_create': "Il server del database ha pgvector {version}: crea l'estensione in Utilità del workspace › Provider di vettori.",
|
|
1093
|
+
'vec.missing_admin': "Il server del database ha pgvector {version}: un amministratore crea l'estensione (Utilità del workspace › Provider di vettori).",
|
|
1094
|
+
'vec.missing_package': "Il server del database non ha il pacchetto pgvector: installalo (ad es. apt install postgresql-17-pgvector, o l'immagine Docker pgvector/pgvector), poi crea l'estensione.",
|
|
1095
|
+
'vec.utilities': "Utilità del workspace › Provider di vettori",
|
|
1096
|
+
'vec.kept_by': "mantenuta da {provider} a partire da {columns}",
|
|
1097
|
+
'vec.not_kept': "nessun trigger la mantiene aggiornata",
|
|
1098
|
+
'vec.progress': "Righe di {column} con embedding",
|
|
1099
|
+
'vec.counts': "{done} righe su {total} con embedding, {queued} in coda",
|
|
1100
|
+
'vec.failing': "{n} non riuscite",
|
|
1101
|
+
'vec.reembed_all': "tutte le righe, non solo quelle senza embedding",
|
|
1102
|
+
'vec.backfill': "Completa",
|
|
1103
|
+
'vec.refresh': "Aggiorna",
|
|
1104
|
+
'vec.backfill_queued': "{n} righe di {column} messe in coda per i loro embedding.",
|
|
1105
|
+
'vec.added': "Colonna vettoriale {column} aggiunta a {table}; {n} righe in coda per i loro embedding.",
|
|
1068
1106
|
// ------------------------------------------------------------ Shared Components (sprint 124)
|
|
1069
1107
|
'comp.nav_entry': 'Voce di navigazione',
|
|
1070
1108
|
'comp.nav_entry.plural': 'Menu di navigazione',
|