pgkiln 0.68.0 → 0.70.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.
Files changed (65) hide show
  1. package/db/migrations/135_fulltext_search.sql +60 -0
  2. package/db/migrations/136_vector_search.sql +252 -0
  3. package/docs/apex-feature-parity.md +5 -4
  4. package/docs/guide/03-builder.md +76 -0
  5. package/docs/guide/04-pages-and-regions.md +86 -1
  6. package/docs/guide/09-reference.md +19 -0
  7. package/docs/guide/12-development.md +41 -2
  8. package/docs/guide/15-extensions.md +1 -1
  9. package/examples/hr/README.md +2 -0
  10. package/examples/hr/hr_71_fulltext_search.sql +43 -0
  11. package/examples/hr/hr_72_vector_search.sql +158 -0
  12. package/package.json +1 -1
  13. package/public/builder.css +3 -0
  14. package/src/ai/embeddings.ts +197 -0
  15. package/src/ai/service.ts +13 -9
  16. package/src/app.ts +3 -0
  17. package/src/builder/advisor.ts +79 -3
  18. package/src/builder/ai-agents.ts +13 -5
  19. package/src/builder/components.ts +32 -1
  20. package/src/builder/fulltext.ts +160 -0
  21. package/src/builder/home.ts +1 -0
  22. package/src/builder/i18n/de.ts +54 -0
  23. package/src/builder/i18n/en.ts +55 -0
  24. package/src/builder/i18n/es.ts +54 -0
  25. package/src/builder/i18n/fr.ts +54 -0
  26. package/src/builder/i18n/it.ts +54 -0
  27. package/src/builder/i18n/ja.ts +54 -0
  28. package/src/builder/i18n/ko.ts +54 -0
  29. package/src/builder/i18n/nl.ts +54 -0
  30. package/src/builder/i18n/pt.ts +54 -0
  31. package/src/builder/i18n/zh-cn.ts +54 -0
  32. package/src/builder/i18n/zh-tw.ts +54 -0
  33. package/src/builder/object-actions.ts +2 -0
  34. package/src/builder/shared.ts +1 -1
  35. package/src/builder/sql.ts +6 -0
  36. package/src/builder/table-editor.ts +2 -0
  37. package/src/builder/vector-column.ts +229 -0
  38. package/src/builder/vector-providers.ts +286 -0
  39. package/src/cli/replace.ts +3 -1
  40. package/src/i18n/ar.ts +1 -0
  41. package/src/i18n/cs.ts +1 -0
  42. package/src/i18n/da.ts +1 -0
  43. package/src/i18n/de.ts +1 -0
  44. package/src/i18n/el.ts +1 -0
  45. package/src/i18n/es.ts +1 -0
  46. package/src/i18n/fi.ts +1 -0
  47. package/src/i18n/fr.ts +1 -0
  48. package/src/i18n/he.ts +1 -0
  49. package/src/i18n/it.ts +1 -0
  50. package/src/i18n/ja.ts +1 -0
  51. package/src/i18n/ko.ts +1 -0
  52. package/src/i18n/nb.ts +1 -0
  53. package/src/i18n/pl.ts +1 -0
  54. package/src/i18n/pt.ts +1 -0
  55. package/src/i18n/ru.ts +1 -0
  56. package/src/i18n/sv.ts +1 -0
  57. package/src/i18n/tr.ts +1 -0
  58. package/src/i18n/uk.ts +1 -0
  59. package/src/i18n/zh.ts +1 -0
  60. package/src/i18n.ts +2 -0
  61. package/src/runtime/ai-agents.ts +53 -2
  62. package/src/runtime/report.ts +26 -2
  63. package/src/runtime/search.ts +290 -23
  64. package/src/server.ts +3 -0
  65. package/src/vector.ts +288 -0
@@ -0,0 +1,160 @@
1
+ import type { FastifyInstance, FastifyReply } from 'fastify';
2
+ import pg from 'pg';
3
+ import { html, type Raw } from '../html.ts';
4
+ import { icon } from '../icons.ts';
5
+ import { clientIp, logActivity } from '../session.ts';
6
+ import { joinStatements, recordChanges } from '../changelog.ts';
7
+ import { readTable } from '../tabledef.ts';
8
+ import { tsConfigFor } from '../runtime/search.ts';
9
+ import { builderLanguage, tr } from './i18n.ts';
10
+ import { SYSTEM_SCHEMAS } from './object-browser.ts';
11
+ import { clearCompletions } from './code-editor.ts';
12
+ import { back, BASE, csrf, developer, flash, send, shell, workshopTabs, type Body, type Req } from './ui.ts';
13
+ import { currentWorkspace, usableSchemas, workshopDb } from './workspaces.ts';
14
+
15
+ // (sprint 135, AP-2) SQL Workshop › Table Properties › Add full-text search: a stored generated tsvector column over
16
+ // the text columns the developer picks (each with a weight A–D) in a text search configuration from pg_ts_config,
17
+ // and a GIN index on it. Preview DDL shows the statements; Apply runs them in one transaction with the workshop's
18
+ // connection and records them in the Change Log; Open in the worksheet puts them there. The table, its columns and
19
+ // the configuration are read from the catalog on every request; only names found there reach the DDL (quoted).
20
+
21
+ type Db = Awaited<ReturnType<typeof workshopDb>>;
22
+ const NAME = /^[a-z_][a-z0-9_]{0,62}$/;
23
+ const WEIGHTS = ['A', 'B', 'C', 'D'] as const;
24
+ const ident = (n: string) => (/^[a-z_][a-z0-9_$]*$/.test(n) ? n : pg.escapeIdentifier(n));
25
+
26
+ interface Col { name: string; type: string; texty: boolean; tsv: boolean; gin: boolean }
27
+
28
+ async function columnsOf(db: Db, oid: number): Promise<Col[]> {
29
+ return (await db.query<Col>(
30
+ `select a.attname as name, format_type(a.atttypid, a.atttypmod) as type,
31
+ t.typcategory = 'S' and a.attgenerated = '' as texty, a.atttypid = 'tsvector'::regtype as tsv,
32
+ exists (select from pg_index i join pg_class ic on ic.oid = i.indexrelid join pg_am am on am.oid = ic.relam
33
+ where i.indrelid = a.attrelid and am.amname = 'gin' and a.attnum = any (i.indkey::int2[])) as gin
34
+ from pg_attribute a join pg_type t on t.oid = a.atttypid
35
+ where a.attrelid = $1 and a.attnum > 0 and not a.attisdropped order by a.attnum`, [oid])).rows;
36
+ }
37
+
38
+ /** The text search configurations the connection sees by name (english, dutch, simple…). */
39
+ async function tsConfigs(db: Db) {
40
+ return (await db.query<{ n: string }>(`select cfgname as n from pg_ts_config where pg_ts_config_is_visible(oid) order by 1`)).rows.map((r) => r.n);
41
+ }
42
+
43
+ /** The statements: the generated column and its GIN index. */
44
+ export function fulltextDdl(table: string, column: string, index: string, config: string, cols: { name: string; weight: string }[]) {
45
+ const cfg = `${pg.escapeLiteral(config)}::regconfig`;
46
+ const parts = cols.map((c) => `setweight(to_tsvector(${cfg}, coalesce(${ident(c.name)}::text, '')), '${c.weight}')`);
47
+ return [
48
+ `alter table ${table}\n add column ${ident(column)} tsvector generated always as (\n ${parts.join(' ||\n ')}) stored`,
49
+ `create index ${ident(index)} on ${table} using gin (${ident(column)})`,
50
+ ];
51
+ }
52
+
53
+ async function page(req: Req, reply: FastifyReply) {
54
+ const s = await developer(req, reply);
55
+ if (!s) return;
56
+ const tx = tr(s);
57
+ const db = await workshopDb(s);
58
+ const isPost = req.method === 'POST';
59
+ const b: Body = (isPost ? req.body : req.query) ?? {};
60
+ const ref = String(b.o ?? '');
61
+ const t = ref ? await readTable(db, ref) : null;
62
+ const schemas = (await usableSchemas(db)).filter((n) => !SYSTEM_SCHEMAS.includes(n));
63
+ if (!t || !schemas.includes(t.schema)) return reply.code(404).send('Not found');
64
+ const table = `${ident(t.schema)}.${ident(t.name)}`;
65
+ const oid = (await db.one<{ oid: number }>('select $1::regclass::oid as oid', [table]))!.oid;
66
+ const cols = await columnsOf(db, oid);
67
+ const texty = cols.filter((c) => c.texty);
68
+ const configs = await tsConfigs(db);
69
+ const defaultConfig = configs.includes(tsConfigFor(builderLanguage(s))) ? tsConfigFor(builderLanguage(s)) : configs.includes('simple') ? 'simple' : configs[0] ?? 'simple';
70
+ // the form's values (defaults: every text column, A for the first, B for the rest)
71
+ const picked = new Map<string, string>();
72
+ const column = isPost ? String(b.column ?? '').trim() : 'search_vector';
73
+ const index = isPost ? String(b.index ?? '').trim() : `${t.name.replace(/[^a-z0-9_]+/gi, '_').toLowerCase().slice(0, 40)}_search_vector_idx`;
74
+ const config = isPost ? String(b.config ?? '') : defaultConfig;
75
+ if (isPost) {
76
+ for (const c of texty) {
77
+ const i = texty.indexOf(c);
78
+ if (b[`c.${i}.use`] === 'on' && b[`c.${i}.name`] === c.name) picked.set(c.name, WEIGHTS.find((w) => w === b[`c.${i}.w`]) ?? 'B');
79
+ }
80
+ } else texty.forEach((c, i) => picked.set(c.name, i === 0 ? 'A' : 'B'));
81
+
82
+ const errors: string[] = [];
83
+ let ddl: string[] = [];
84
+ let output: Raw | '' = '';
85
+ if (isPost) {
86
+ if (!picked.size) errors.push(tx('fts.pick'));
87
+ for (const n of [column, index]) if (!NAME.test(n)) errors.push(tx('fts.bad_name', { name: n }));
88
+ if (NAME.test(column) && cols.some((c) => c.name === column)) errors.push(tx('fts.exists', { name: column }));
89
+ if (NAME.test(index) && (await db.one('select 1 as x from pg_class where relnamespace = $1::regnamespace and relname = $2', [ident(t.schema), index]))) errors.push(tx('fts.exists', { name: index }));
90
+ if (!configs.includes(config)) errors.push(tx('fts.bad_config', { name: config }));
91
+ if (!errors.length) ddl = fulltextDdl(table, column, index, config, [...picked].map(([name, weight]) => ({ name, weight })));
92
+ if (ddl.length && b.mode === 'worksheet') {
93
+ s.state.__SQLDRAFT = `${ddl.join(';\n\n')};`;
94
+ flash(s, tx('te.review'));
95
+ return back(reply, s, `${BASE}/sql/worksheet`);
96
+ }
97
+ if (ddl.length && b.mode === 'apply') {
98
+ const c = await db.pool.connect();
99
+ let failed = '';
100
+ let at = '';
101
+ try {
102
+ await c.query('begin');
103
+ for (const x of ddl) {
104
+ at = x;
105
+ await c.query(x);
106
+ }
107
+ await c.query('commit');
108
+ } catch (e) {
109
+ failed = (e as Error).message;
110
+ await c.query('rollback').catch(() => {});
111
+ } finally {
112
+ c.release(true);
113
+ }
114
+ await logActivity({ username: s.username, event: 'sql_command', ip: clientIp(req), detail: ddl.join(';\n').slice(0, 2000) });
115
+ clearCompletions();
116
+ if (!failed) {
117
+ await recordChanges({ workspaceId: currentWorkspace(s), author: s.username ?? '?', source: 'table', defaultSchema: t.schema, statements: [joinStatements(ddl)] });
118
+ flash(s, tx('fts.added', { table, column, index }));
119
+ return back(reply, s, `${BASE}/sql/objects/table?${new URLSearchParams({ o: table })}`);
120
+ }
121
+ output = html`<div class="alert alert-error" role="alert"><p><strong>${tx('te.nothing_changed')}</strong>: ${tx('te.rolled_back')}</p><p>${failed}</p><pre class="source" data-code-view="sql" tabindex="0" aria-label="${tx('te.failed_stmt')}">${at}</pre></div>`;
122
+ }
123
+ }
124
+
125
+ const title = tx('fts.title', { table });
126
+ const existing = cols.filter((c) => c.tsv);
127
+ const sel = (a: string, v: string) => (a === v ? ' selected' : '');
128
+ const main = html`<h1 class="u-mb1">${title}</h1>${workshopTabs('objects', s)}
129
+ <p class="muted">${tx('fts.intro')}</p>
130
+ ${existing.length ? html`<p>${tx('fts.existing')} ${existing.map((c) => html`<code>${c.name}</code> (${c.gin ? tx('fts.gin_yes') : tx('fts.gin_no')}) `)}</p>` : ''}
131
+ ${errors.length ? html`<div class="alert alert-error" role="alert"><ul>${errors.map((e) => html`<li>${e}</li>`)}</ul></div>` : ''}
132
+ ${output}
133
+ ${texty.length ? html`<form method="post" action="${BASE}/sql/objects/fulltext" class="fts-form">${csrf(s)}
134
+ <input type="hidden" name="o" value="${table}">
135
+ <fieldset class="prop-group"><legend>${tx('fts.columns')}</legend>
136
+ <div class="table-wrap"><table class="report"><thead><tr><th>${tx('fts.include')}</th><th>${tx('te.name')}</th><th>${tx('te.type')}</th><th>${tx('fts.weight')}</th></tr></thead><tbody>
137
+ ${texty.map((c, i) => html`<tr><td class="te-c"><input type="hidden" name="c.${i}.name" value="${c.name}"><input type="checkbox" id="fts-c${i}" name="c.${i}.use"${picked.has(c.name) ? ' checked' : ''}></td>
138
+ <td><label for="fts-c${i}">${c.name}</label></td><td><code>${c.type}</code></td>
139
+ <td><select name="c.${i}.w" aria-label="${tx('fts.weight')} ${c.name}">${WEIGHTS.map((w) => html`<option${sel(w, picked.get(c.name) ?? 'B')}>${w}</option>`)}</select></td></tr>`)}
140
+ </tbody></table></div>
141
+ <small class="help">${tx('fts.weight_help')}</small></fieldset>
142
+ <div class="form-grid u-mt1">
143
+ <div class="field"><label class="label" for="fts-config">${tx('fts.config')}</label><select id="fts-config" name="config">${configs.map((n) => html`<option${sel(n, config)}>${n}</option>`)}</select><small class="help">${tx('fts.config_help')}</small></div>
144
+ <div class="field"><label class="label" for="fts-column">${tx('fts.column')}</label><input id="fts-column" name="column" value="${column}" required autocomplete="off" spellcheck="false" class="code"></div>
145
+ <div class="field"><label class="label" for="fts-index">${tx('fts.index')}</label><input id="fts-index" name="index" value="${index}" required autocomplete="off" spellcheck="false" class="code"></div>
146
+ </div>
147
+ ${ddl.length ? html`<h2>DDL</h2><pre class="source te-ddl" data-code-view="sql" tabindex="0" aria-label="DDL">${ddl.map((x) => `${x};`).join('\n\n')}</pre>` : ''}
148
+ <div class="buttons te-buttons">
149
+ <button class="btn" name="mode" value="preview">${icon('eye')} ${tx('te.preview')}</button>
150
+ <button class="btn btn-hot" name="mode" value="apply">${icon('check')} ${tx('te.apply')}</button>
151
+ <button class="btn" name="mode" value="worksheet">${icon('code')} ${tx('te.open_ws')}</button>
152
+ <a class="btn" href="${BASE}/sql/objects/table?${new URLSearchParams({ o: table })}">${tx('common.cancel')}</a>
153
+ </div></form>` : html`<p class="alert alert-info">${tx('fts.no_text')}</p>`}`;
154
+ return send(reply, s, shell(s, title, [['SQL Workshop', `${BASE}/sql`], [tx('ob.title'), `${BASE}/sql/objects`], [tx('te.title_table', { table }), `${BASE}/sql/objects/table?${new URLSearchParams({ o: table })}`], [tx('fts.add')]], main, 'sql'));
155
+ }
156
+
157
+ export async function fulltextRoutes(app: FastifyInstance) {
158
+ app.get(`${BASE}/sql/objects/fulltext`, page);
159
+ app.post(`${BASE}/sql/objects/fulltext`, page);
160
+ }
@@ -295,6 +295,7 @@ 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}/vector-providers`, icon('search'), 'Vector providers', 'Embeddings for search by meaning: the applications\' vector providers, their vector columns, and whether pgvector is installed.')}
298
299
  ${(await isAdmin(s.username)) ? card(`${BASE}/ai`, icon('bolt'), 'AI services', 'Claude and OpenAI for the applications: models, keys, limits and usage (administrators).') : ''}
299
300
  ${(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
301
  ${(await isAdmin(s.username)) ? card(`${BASE}/installation`, icon('history'), 'Installation', 'Version, install and upgrade runs, applied migrations (administrators).') : ''}
@@ -1045,6 +1045,60 @@ export const de: BuilderMessages = {
1045
1045
  'te.failed_stmt': 'Fehlgeschlagene Anweisung',
1046
1046
  'te.title_table': 'Tabelle {table}',
1047
1047
  'te.title_create': 'Tabelle erstellen',
1048
+ 'fts.add': "Volltextsuche hinzufügen",
1049
+ 'fts.title': "Volltextsuche für {table}",
1050
+ 'fts.intro': "Fügt eine gespeicherte generierte tsvector-Spalte über die gewählten Textspalten hinzu, mit einem GIN-Index darauf. Eine Suchkonfiguration vom Suchtyp fulltext (Gemeinsame Komponenten › Suchkonfigurationen) oder die Suche eines Berichts findet dann Wortformen (\"running\" findet \"run\") schnell. Die Spalte aktualisiert sich selbst; das Hinzufügen schreibt die Tabelle neu.",
1051
+ 'fts.columns': "Textspalten",
1052
+ 'fts.include': "Einbeziehen",
1053
+ 'fts.weight': "Gewichtung",
1054
+ 'fts.weight_help': "A zählt in der Rangfolge am meisten, D am wenigsten: z. B. A für einen Titel, B für eine Beschreibung.",
1055
+ 'fts.config': "Textsuchkonfiguration",
1056
+ 'fts.config_help': "Die Sprache, deren Wortstämme verwendet werden (german: Läufer → läuf); simple: keine Stammformen. Mit derselben Konfiguration suchen.",
1057
+ 'fts.column': "tsvector-Spalte",
1058
+ 'fts.index': "Indexname",
1059
+ 'fts.no_text': "Diese Tabelle hat keine Textspalten zum Durchsuchen.",
1060
+ 'fts.pick': "Wählen Sie mindestens eine Textspalte.",
1061
+ 'fts.bad_name': "\"{name}\" ist kein gültiger Name: Kleinbuchstaben, Ziffern und _.",
1062
+ 'fts.exists': "{name} existiert bereits.",
1063
+ 'fts.bad_config': "{name} ist keine Textsuchkonfiguration.",
1064
+ 'fts.existing': "Vorhandene tsvector-Spalten:",
1065
+ 'fts.gin_yes': "GIN-Index",
1066
+ 'fts.gin_no': "kein GIN-Index",
1067
+ 'fts.added': "Volltextsuche zu {table} hinzugefügt: Spalte {column} mit Index {index}.",
1068
+ // (sprint 136, AP-1) Table Properties › Add vector column
1069
+ 'vec.add': "Vektorspalte hinzufügen",
1070
+ 'vec.title': "Vektorspalte für {table}",
1071
+ '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.",
1072
+ 'vec.existing': "Vektorspalten jetzt",
1073
+ 'vec.provider': "Vektoranbieter",
1074
+ 'vec.provider_help': "Gemeinsame Komponenten › Vektoranbieter der Anwendungen dieses Arbeitsbereichs. Suchen Sie mit demselben Anbieter.",
1075
+ 'vec.keep': "Aktuell halten",
1076
+ '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.",
1077
+ 'vec.mode_set': "vom Trigger gesetzt, sofort",
1078
+ 'vec.mode_queue': "für den Server eingereiht",
1079
+ 'vec.sources': "Quellspalten",
1080
+ 'vec.sources_help': "Der Text, der eingebettet wird: diese Spalten, eine pro Zeile. Eine Änderung an einer davon bettet die Zeile neu ein.",
1081
+ 'vec.distance': "Distanz",
1082
+ 'vec.distance_help': "cosine für die meisten Embedding-Modelle; die Suchkonfiguration verwendet dieselbe Distanz.",
1083
+ 'vec.column': "Vektorspalte",
1084
+ 'vec.no_key': "Die Tabelle braucht einen Primärschlüssel aus einer Spalte: Die Warteschlange findet Zeilen darüber.",
1085
+ 'vec.pick_provider': "Wählen Sie einen Vektoranbieter.",
1086
+ 'vec.no_providers': "Noch keine Anwendung dieses Arbeitsbereichs hat einen Vektoranbieter: Fügen Sie einen unter Gemeinsame Komponenten › Vektoranbieter hinzu.",
1087
+ 'vec.missing': "Die Erweiterung pgvector ist in dieser Datenbank nicht installiert, daher kann noch keine Vektorspalte hinzugefügt werden.",
1088
+ 'vec.missing_create': "Der Datenbankserver hat pgvector {version}: Erstellen Sie die Erweiterung unter Arbeitsbereich-Dienstprogramme › Vektoranbieter.",
1089
+ 'vec.missing_admin': "Der Datenbankserver hat pgvector {version}: Ein Administrator erstellt die Erweiterung (Arbeitsbereich-Dienstprogramme › Vektoranbieter).",
1090
+ '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.",
1091
+ 'vec.utilities': "Arbeitsbereich-Dienstprogramme › Vektoranbieter",
1092
+ 'vec.kept_by': "gepflegt von {provider} aus {columns}",
1093
+ 'vec.not_kept': "kein Trigger hält sie aktuell",
1094
+ 'vec.progress': "Eingebettete Zeilen von {column}",
1095
+ 'vec.counts': "{done} von {total} Zeilen eingebettet, {queued} eingereiht",
1096
+ 'vec.failing': "{n} fehlgeschlagen",
1097
+ 'vec.reembed_all': "alle Zeilen, nicht nur die ohne Embedding",
1098
+ 'vec.backfill': "Nachfüllen",
1099
+ 'vec.refresh': "Aktualisieren",
1100
+ 'vec.backfill_queued': "{n} Zeilen von {column} für ihre Embeddings eingereiht.",
1101
+ 'vec.added': "Vektorspalte {column} zu {table} hinzugefügt; {n} Zeilen für ihre Embeddings eingereiht.",
1048
1102
  // ------------------------------------------------------------ Shared Components (sprint 124)
1049
1103
  'comp.nav_entry': 'Navigationseintrag',
1050
1104
  'comp.nav_entry.plural': 'Navigationsmenü',
@@ -1048,6 +1048,61 @@ export const en = {
1048
1048
  'te.failed_stmt': 'Failed statement',
1049
1049
  'te.title_table': 'Table {table}',
1050
1050
  'te.title_create': 'Create table',
1051
+ // (sprint 135, AP-2) Table Properties › Add full-text search
1052
+ 'fts.add': "Add full-text search",
1053
+ 'fts.title': "Full-text search on {table}",
1054
+ 'fts.intro': "Adds a stored generated tsvector column over the text columns you choose, and a GIN index on it. A search configuration of search type fulltext (Shared Components › Search configurations) or a report search then finds stemmed words (\"running\" finds \"run\") fast. The column keeps itself up to date; adding it rewrites the table.",
1055
+ 'fts.columns': "Text columns",
1056
+ 'fts.include': "Include",
1057
+ 'fts.weight': "Weight",
1058
+ 'fts.weight_help': "A counts most in the ranking, D least: e.g. A for a title, B for a description.",
1059
+ 'fts.config': "Text search configuration",
1060
+ 'fts.config_help': "The language whose stemming is used (english: running → run); simple: no stemming. Search with the same configuration.",
1061
+ 'fts.column': "tsvector column",
1062
+ 'fts.index': "Index name",
1063
+ 'fts.no_text': "This table has no text columns to search.",
1064
+ 'fts.pick': "Choose at least one text column.",
1065
+ 'fts.bad_name': "\"{name}\" is not a valid name: lowercase letters, digits and _.",
1066
+ 'fts.exists': "{name} already exists.",
1067
+ 'fts.bad_config': "{name} is not a text search configuration.",
1068
+ 'fts.existing': "tsvector columns now:",
1069
+ 'fts.gin_yes': "GIN index",
1070
+ 'fts.gin_no': "no GIN index",
1071
+ 'fts.added': "Added full-text search to {table}: column {column} with index {index}.",
1072
+ // (sprint 136, AP-1) Table Properties › Add vector column
1073
+ 'vec.add': "Add vector column",
1074
+ 'vec.title': "Vector column on {table}",
1075
+ '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.",
1076
+ 'vec.existing': "Vector columns now",
1077
+ 'vec.provider': "Vector provider",
1078
+ 'vec.provider_help': "Shared Components › Vector providers of this workspace's applications. Search with the same provider.",
1079
+ 'vec.keep': "Keep current",
1080
+ '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.",
1081
+ 'vec.mode_set': "set by the trigger, at once",
1082
+ 'vec.mode_queue': "queued for the server",
1083
+ 'vec.sources': "Source columns",
1084
+ 'vec.sources_help': "The text that is embedded: these columns, one per line. A change to one of them re-embeds the row.",
1085
+ 'vec.distance': "Distance",
1086
+ 'vec.distance_help': "cosine for most embedding models; the search configuration uses the same distance.",
1087
+ 'vec.column': "Vector column",
1088
+ 'vec.no_key': "The table needs a primary key of one column: the queue finds rows by it.",
1089
+ 'vec.pick_provider': "Choose a vector provider.",
1090
+ 'vec.no_providers': "No application of this workspace has a vector provider yet: add one under Shared Components › Vector providers.",
1091
+ 'vec.missing': "The pgvector extension is not installed in this database, so a vector column can't be added yet.",
1092
+ 'vec.missing_create': "The database server has pgvector {version}: create the extension under Workspace utilities › Vector providers.",
1093
+ 'vec.missing_admin': "The database server has pgvector {version}: an administrator creates the extension (Workspace utilities › Vector providers).",
1094
+ '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.",
1095
+ 'vec.utilities': "Workspace utilities › Vector providers",
1096
+ 'vec.kept_by': "kept by {provider} from {columns}",
1097
+ 'vec.not_kept': "no trigger keeps it current",
1098
+ 'vec.progress': "Embedded rows of {column}",
1099
+ 'vec.counts': "{done} of {total} rows embedded, {queued} queued",
1100
+ 'vec.failing': "{n} failing",
1101
+ 'vec.reembed_all': "all rows, not only those without an embedding",
1102
+ 'vec.backfill': "Backfill",
1103
+ 'vec.refresh': "Refresh",
1104
+ 'vec.backfill_queued': "{n} rows of {column} queued for their embeddings.",
1105
+ 'vec.added': "Added the vector column {column} to {table}; {n} rows queued for their embeddings.",
1051
1106
  // ------------------------------------------------------------ Shared Components (sprint 124)
1052
1107
  'comp.nav_entry': 'Navigation entry',
1053
1108
  'comp.nav_entry.plural': 'Navigation menu',
@@ -1045,6 +1045,60 @@ export const es: BuilderMessages = {
1045
1045
  'te.failed_stmt': 'Sentencia con error',
1046
1046
  'te.title_table': 'Tabla {table}',
1047
1047
  'te.title_create': 'Crear tabla',
1048
+ 'fts.add': "Añadir búsqueda de texto completo",
1049
+ 'fts.title': "Búsqueda de texto completo en {table}",
1050
+ 'fts.intro': "Añade una columna tsvector generada y almacenada sobre las columnas de texto que elija, con un índice GIN. Una configuración de búsqueda de tipo fulltext (Componentes compartidos › Configuraciones de búsqueda) o la búsqueda de un informe encuentra entonces rápidamente las formas de las palabras (\"running\" encuentra \"run\"). La columna se mantiene al día; añadirla reescribe la tabla.",
1051
+ 'fts.columns': "Columnas de texto",
1052
+ 'fts.include': "Incluir",
1053
+ 'fts.weight': "Peso",
1054
+ 'fts.weight_help': "A cuenta más en la clasificación, D menos: p. ej. A para un título, B para una descripción.",
1055
+ 'fts.config': "Configuración de búsqueda de texto",
1056
+ 'fts.config_help': "El idioma cuya lematización se usa (spanish: corriendo → corr); simple: sin lematización. Busque con la misma configuración.",
1057
+ 'fts.column': "Columna tsvector",
1058
+ 'fts.index': "Nombre del índice",
1059
+ 'fts.no_text': "Esta tabla no tiene columnas de texto en las que buscar.",
1060
+ 'fts.pick': "Elija al menos una columna de texto.",
1061
+ 'fts.bad_name': "\"{name}\" no es un nombre válido: minúsculas, dígitos y _.",
1062
+ 'fts.exists': "{name} ya existe.",
1063
+ 'fts.bad_config': "{name} no es una configuración de búsqueda de texto.",
1064
+ 'fts.existing': "Columnas tsvector actuales:",
1065
+ 'fts.gin_yes': "índice GIN",
1066
+ 'fts.gin_no': "sin índice GIN",
1067
+ 'fts.added': "Búsqueda de texto completo añadida a {table}: columna {column} con el índice {index}.",
1068
+ // (sprint 136, AP-1) Table Properties › Add vector column
1069
+ 'vec.add': "Añadir columna vectorial",
1070
+ 'vec.title': "Columna vectorial en {table}",
1071
+ '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.",
1072
+ 'vec.existing': "Columnas vectoriales actuales",
1073
+ 'vec.provider': "Proveedor de vectores",
1074
+ 'vec.provider_help': "Componentes compartidos › Proveedores de vectores de las aplicaciones de este espacio de trabajo. Busque con el mismo proveedor.",
1075
+ 'vec.keep': "Mantener al día",
1076
+ '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.",
1077
+ 'vec.mode_set': "fijado por el disparador, enseguida",
1078
+ 'vec.mode_queue': "en cola para el servidor",
1079
+ 'vec.sources': "Columnas de origen",
1080
+ '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.",
1081
+ 'vec.distance': "Distancia",
1082
+ 'vec.distance_help': "cosine para la mayoría de los modelos de embedding; la configuración de búsqueda usa la misma distancia.",
1083
+ 'vec.column': "Columna vectorial",
1084
+ 'vec.no_key': "La tabla necesita una clave primaria de una sola columna: la cola encuentra las filas por ella.",
1085
+ 'vec.pick_provider': "Elija un proveedor de vectores.",
1086
+ '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.",
1087
+ '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.",
1088
+ '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.",
1089
+ '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).",
1090
+ '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.",
1091
+ 'vec.utilities': "Utilidades del espacio de trabajo › Proveedores de vectores",
1092
+ 'vec.kept_by': "mantenida por {provider} a partir de {columns}",
1093
+ 'vec.not_kept': "ningún disparador la mantiene al día",
1094
+ 'vec.progress': "Filas de {column} con embedding",
1095
+ 'vec.counts': "{done} de {total} filas con embedding, {queued} en cola",
1096
+ 'vec.failing': "{n} con error",
1097
+ 'vec.reembed_all': "todas las filas, no solo las que no tienen embedding",
1098
+ 'vec.backfill': "Completar",
1099
+ 'vec.refresh': "Actualizar",
1100
+ 'vec.backfill_queued': "{n} filas de {column} en cola para sus embeddings.",
1101
+ 'vec.added': "Columna vectorial {column} añadida a {table}; {n} filas en cola para sus embeddings.",
1048
1102
  // ------------------------------------------------------------ Shared Components (sprint 124)
1049
1103
  'comp.nav_entry': 'Entrada de navegación',
1050
1104
  'comp.nav_entry.plural': 'Menú de navegación',
@@ -1045,6 +1045,60 @@ export const fr: BuilderMessages = {
1045
1045
  'te.failed_stmt': 'Instruction en échec',
1046
1046
  'te.title_table': 'Table {table}',
1047
1047
  'te.title_create': 'Créer une table',
1048
+ 'fts.add': "Ajouter la recherche plein texte",
1049
+ 'fts.title': "Recherche plein texte sur {table}",
1050
+ 'fts.intro': "Ajoute une colonne tsvector générée et stockée sur les colonnes de texte choisies, avec un index GIN. Une configuration de recherche de type fulltext (Composants partagés › Configurations de recherche) ou la recherche d'un rapport trouve alors rapidement les formes des mots (\"running\" trouve \"run\"). La colonne se tient à jour ; l'ajouter réécrit la table.",
1051
+ 'fts.columns': "Colonnes de texte",
1052
+ 'fts.include': "Inclure",
1053
+ 'fts.weight': "Poids",
1054
+ 'fts.weight_help': "A compte le plus dans le classement, D le moins : par ex. A pour un titre, B pour une description.",
1055
+ 'fts.config': "Configuration de recherche de texte",
1056
+ 'fts.config_help': "La langue dont la racinisation est utilisée (french : courir → cour) ; simple : sans racinisation. Recherchez avec la même configuration.",
1057
+ 'fts.column': "Colonne tsvector",
1058
+ 'fts.index': "Nom de l'index",
1059
+ 'fts.no_text': "Cette table n'a pas de colonnes de texte à rechercher.",
1060
+ 'fts.pick': "Choisissez au moins une colonne de texte.",
1061
+ 'fts.bad_name': "« {name} » n'est pas un nom valide : minuscules, chiffres et _.",
1062
+ 'fts.exists': "{name} existe déjà.",
1063
+ 'fts.bad_config': "{name} n'est pas une configuration de recherche de texte.",
1064
+ 'fts.existing': "Colonnes tsvector actuelles :",
1065
+ 'fts.gin_yes': "index GIN",
1066
+ 'fts.gin_no': "pas d'index GIN",
1067
+ 'fts.added': "Recherche plein texte ajoutée à {table} : colonne {column} avec l'index {index}.",
1068
+ // (sprint 136, AP-1) Table Properties › Add vector column
1069
+ 'vec.add': "Ajouter une colonne vectorielle",
1070
+ 'vec.title': "Colonne vectorielle sur {table}",
1071
+ '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.",
1072
+ 'vec.existing': "Colonnes vectorielles actuelles",
1073
+ 'vec.provider': "Fournisseur de vecteurs",
1074
+ 'vec.provider_help': "Composants partagés › Fournisseurs de vecteurs des applications de cet espace de travail. Recherchez avec le même fournisseur.",
1075
+ 'vec.keep': "Tenir à jour",
1076
+ '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.",
1077
+ 'vec.mode_set': "défini par le déclencheur, tout de suite",
1078
+ 'vec.mode_queue': "en file d'attente pour le serveur",
1079
+ 'vec.sources': "Colonnes sources",
1080
+ '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.",
1081
+ 'vec.distance': "Distance",
1082
+ 'vec.distance_help': "cosine pour la plupart des modèles d'embedding ; la configuration de recherche utilise la même distance.",
1083
+ 'vec.column': "Colonne vectorielle",
1084
+ 'vec.no_key': "La table a besoin d'une clé primaire d'une seule colonne : la file d'attente retrouve les lignes par elle.",
1085
+ 'vec.pick_provider': "Choisissez un fournisseur de vecteurs.",
1086
+ '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.",
1087
+ '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.",
1088
+ '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.",
1089
+ '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).",
1090
+ '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.",
1091
+ 'vec.utilities': "Utilitaires de l'espace de travail › Fournisseurs de vecteurs",
1092
+ 'vec.kept_by': "tenue par {provider} à partir de {columns}",
1093
+ 'vec.not_kept': "aucun déclencheur ne la tient à jour",
1094
+ 'vec.progress': "Lignes de {column} avec embedding",
1095
+ 'vec.counts': "{done} lignes sur {total} avec embedding, {queued} en file d'attente",
1096
+ 'vec.failing': "{n} en échec",
1097
+ 'vec.reembed_all': "toutes les lignes, pas seulement celles sans embedding",
1098
+ 'vec.backfill': "Compléter",
1099
+ 'vec.refresh': "Actualiser",
1100
+ 'vec.backfill_queued': "{n} lignes de {column} mises en file d'attente pour leurs embeddings.",
1101
+ 'vec.added': "Colonne vectorielle {column} ajoutée à {table} ; {n} lignes en file d'attente pour leurs embeddings.",
1048
1102
  // ------------------------------------------------------------ Shared Components (sprint 124)
1049
1103
  'comp.nav_entry': 'Entrée de navigation',
1050
1104
  'comp.nav_entry.plural': 'Menu de navigation',
@@ -1045,6 +1045,60 @@ export const it: BuilderMessages = {
1045
1045
  'te.failed_stmt': 'Istruzione non riuscita',
1046
1046
  'te.title_table': 'Tabella {table}',
1047
1047
  'te.title_create': 'Crea tabella',
1048
+ 'fts.add': "Aggiungi ricerca full-text",
1049
+ 'fts.title': "Ricerca full-text su {table}",
1050
+ 'fts.intro': "Aggiunge una colonna tsvector generata e memorizzata sulle colonne di testo scelte, con un indice GIN. Una configurazione di ricerca di tipo fulltext (Componenti condivisi › Configurazioni di ricerca) o la ricerca di un report trova allora rapidamente le forme delle parole (\"running\" trova \"run\"). La colonna si aggiorna da sola; aggiungerla riscrive la tabella.",
1051
+ 'fts.columns': "Colonne di testo",
1052
+ 'fts.include': "Includi",
1053
+ 'fts.weight': "Peso",
1054
+ 'fts.weight_help': "A conta di più nella classifica, D di meno: ad es. A per un titolo, B per una descrizione.",
1055
+ 'fts.config': "Configurazione di ricerca testuale",
1056
+ 'fts.config_help': "La lingua di cui si usa lo stemming (italian: correndo → corr); simple: nessuno stemming. Cercate con la stessa configurazione.",
1057
+ 'fts.column': "Colonna tsvector",
1058
+ 'fts.index': "Nome dell'indice",
1059
+ 'fts.no_text': "Questa tabella non ha colonne di testo in cui cercare.",
1060
+ 'fts.pick': "Scegliete almeno una colonna di testo.",
1061
+ 'fts.bad_name': "\"{name}\" non è un nome valido: lettere minuscole, cifre e _.",
1062
+ 'fts.exists': "{name} esiste già.",
1063
+ 'fts.bad_config': "{name} non è una configurazione di ricerca testuale.",
1064
+ 'fts.existing': "Colonne tsvector attuali:",
1065
+ 'fts.gin_yes': "indice GIN",
1066
+ 'fts.gin_no': "nessun indice GIN",
1067
+ 'fts.added': "Ricerca full-text aggiunta a {table}: colonna {column} con l'indice {index}.",
1068
+ // (sprint 136, AP-1) Table Properties › Add vector column
1069
+ 'vec.add': "Aggiungi colonna vettoriale",
1070
+ 'vec.title': "Colonna vettoriale su {table}",
1071
+ '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.",
1072
+ 'vec.existing': "Colonne vettoriali attuali",
1073
+ 'vec.provider': "Provider di vettori",
1074
+ 'vec.provider_help': "Componenti condivisi › Provider di vettori delle applicazioni di questo workspace. Cerca con lo stesso provider.",
1075
+ 'vec.keep': "Mantieni aggiornata",
1076
+ '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.",
1077
+ 'vec.mode_set': "impostato dal trigger, subito",
1078
+ 'vec.mode_queue': "in coda per il server",
1079
+ 'vec.sources': "Colonne di origine",
1080
+ '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.",
1081
+ 'vec.distance': "Distanza",
1082
+ 'vec.distance_help': "cosine per la maggior parte dei modelli di embedding; la configurazione di ricerca usa la stessa distanza.",
1083
+ 'vec.column': "Colonna vettoriale",
1084
+ 'vec.no_key': "La tabella ha bisogno di una chiave primaria di una sola colonna: la coda trova le righe tramite essa.",
1085
+ 'vec.pick_provider': "Scegli un provider di vettori.",
1086
+ 'vec.no_providers': "Nessuna applicazione di questo workspace ha ancora un provider di vettori: aggiungine uno in Componenti condivisi › Provider di vettori.",
1087
+ 'vec.missing': "L'estensione pgvector non è installata in questo database, quindi non è ancora possibile aggiungere una colonna vettoriale.",
1088
+ 'vec.missing_create': "Il server del database ha pgvector {version}: crea l'estensione in Utilità del workspace › Provider di vettori.",
1089
+ 'vec.missing_admin': "Il server del database ha pgvector {version}: un amministratore crea l'estensione (Utilità del workspace › Provider di vettori).",
1090
+ '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.",
1091
+ 'vec.utilities': "Utilità del workspace › Provider di vettori",
1092
+ 'vec.kept_by': "mantenuta da {provider} a partire da {columns}",
1093
+ 'vec.not_kept': "nessun trigger la mantiene aggiornata",
1094
+ 'vec.progress': "Righe di {column} con embedding",
1095
+ 'vec.counts': "{done} righe su {total} con embedding, {queued} in coda",
1096
+ 'vec.failing': "{n} non riuscite",
1097
+ 'vec.reembed_all': "tutte le righe, non solo quelle senza embedding",
1098
+ 'vec.backfill': "Completa",
1099
+ 'vec.refresh': "Aggiorna",
1100
+ 'vec.backfill_queued': "{n} righe di {column} messe in coda per i loro embedding.",
1101
+ 'vec.added': "Colonna vettoriale {column} aggiunta a {table}; {n} righe in coda per i loro embedding.",
1048
1102
  // ------------------------------------------------------------ Shared Components (sprint 124)
1049
1103
  'comp.nav_entry': 'Voce di navigazione',
1050
1104
  'comp.nav_entry.plural': 'Menu di navigazione',
@@ -1045,6 +1045,60 @@ export const ja: BuilderMessages = {
1045
1045
  'te.failed_stmt': '失敗した文',
1046
1046
  'te.title_table': '表 {table}',
1047
1047
  'te.title_create': '表の作成',
1048
+ 'fts.add': "全文検索の追加",
1049
+ 'fts.title': "{table} の全文検索",
1050
+ 'fts.intro': "選択したテキスト列にまたがる格納生成 tsvector 列と、その GIN 索引を追加します。これにより、検索タイプ fulltext の検索構成(共有コンポーネント › 検索構成)やレポートの検索で、語形変化(\"running\" で \"run\")をすばやく見つけられます。列は自動的に更新されます。追加すると表が書き換えられます。",
1051
+ 'fts.columns': "テキスト列",
1052
+ 'fts.include': "含める",
1053
+ 'fts.weight': "重み",
1054
+ 'fts.weight_help': "ランキングでは A が最も重く、D が最も軽くなります。例:タイトルは A、説明は B。",
1055
+ 'fts.config': "テキスト検索構成",
1056
+ 'fts.config_help': "語幹処理に使う言語(english:running → run)。simple:語幹処理なし。検索にも同じ構成を使ってください。",
1057
+ 'fts.column': "tsvector 列",
1058
+ 'fts.index': "索引名",
1059
+ 'fts.no_text': "この表には検索できるテキスト列がありません。",
1060
+ 'fts.pick': "テキスト列を 1 つ以上選択してください。",
1061
+ 'fts.bad_name': "\"{name}\" は有効な名前ではありません:小文字、数字、_ のみ。",
1062
+ 'fts.exists': "{name} はすでに存在します。",
1063
+ 'fts.bad_config': "{name} はテキスト検索構成ではありません。",
1064
+ 'fts.existing': "現在の tsvector 列:",
1065
+ 'fts.gin_yes': "GIN 索引",
1066
+ 'fts.gin_no': "GIN 索引なし",
1067
+ 'fts.added': "{table} に全文検索を追加しました:列 {column}、索引 {index}。",
1068
+ // (sprint 136, AP-1) Table Properties › Add vector column
1069
+ 'vec.add': "ベクトル列を追加",
1070
+ 'vec.title': "{table} のベクトル列",
1071
+ 'vec.intro': "埋め込み列 vector(n)(n はベクトル・プロバイダの次元数)、距離用の HNSW 索引、およびソース列が変わったときに行を再埋め込みするトリガーを追加します。その後、検索タイプ vector または hybrid の検索構成や AI エージェントの Retrieve ツールで、意味によって行を検索できます。既存の行はすぐに埋め込みのキューに入れられます。",
1072
+ 'vec.existing': "現在のベクトル列",
1073
+ 'vec.provider': "ベクトル・プロバイダ",
1074
+ 'vec.provider_help': "このワークスペースのアプリケーションの共有コンポーネント › ベクトル・プロバイダ。検索にも同じプロバイダを使用します。",
1075
+ 'vec.keep': "最新に保つ方法",
1076
+ 'vec.keep_help': "関数プロバイダは、トリガー内ですぐに埋め込みを設定できます。AI サービスはサーバーが呼び出します。トリガーが行をキューに入れ、サーバーがプロバイダの 1 分あたりのリクエスト数とアプリケーションの 1 日の AI 制限の範囲内で、キューの行をまとめて埋め込みます。",
1077
+ 'vec.mode_set': "トリガーがすぐに設定",
1078
+ 'vec.mode_queue': "サーバー用のキューに入れる",
1079
+ 'vec.sources': "ソース列",
1080
+ 'vec.sources_help': "埋め込むテキスト:これらの列を 1 行に 1 つずつ。いずれかが変わると行が再埋め込みされます。",
1081
+ 'vec.distance': "距離",
1082
+ 'vec.distance_help': "ほとんどの埋め込みモデルでは cosine。検索構成も同じ距離を使用します。",
1083
+ 'vec.column': "ベクトル列",
1084
+ 'vec.no_key': "表には 1 列の主キーが必要です。キューはそれで行を見つけます。",
1085
+ 'vec.pick_provider': "ベクトル・プロバイダを選択してください。",
1086
+ 'vec.no_providers': "このワークスペースのアプリケーションにはまだベクトル・プロバイダがありません。共有コンポーネント › ベクトル・プロバイダで追加してください。",
1087
+ 'vec.missing': "このデータベースには pgvector 拡張機能がインストールされていないため、まだベクトル列を追加できません。",
1088
+ 'vec.missing_create': "データベース・サーバーには pgvector {version} があります:ワークスペース・ユーティリティ › ベクトル・プロバイダで拡張機能を作成してください。",
1089
+ 'vec.missing_admin': "データベース・サーバーには pgvector {version} があります:管理者が拡張機能を作成します(ワークスペース・ユーティリティ › ベクトル・プロバイダ)。",
1090
+ 'vec.missing_package': "データベース・サーバーに pgvector パッケージがありません:インストールしてから(例:apt install postgresql-17-pgvector、または Docker イメージ pgvector/pgvector)、拡張機能を作成してください。",
1091
+ 'vec.utilities': "ワークスペース・ユーティリティ › ベクトル・プロバイダ",
1092
+ 'vec.kept_by': "{provider} が {columns} から維持",
1093
+ 'vec.not_kept': "最新に保つトリガーはありません",
1094
+ 'vec.progress': "{column} の埋め込み済みの行",
1095
+ 'vec.counts': "{total} 行中 {done} 行を埋め込み済み、{queued} 行がキュー内",
1096
+ 'vec.failing': "{n} 件失敗",
1097
+ 'vec.reembed_all': "埋め込みのない行だけでなく、すべての行",
1098
+ 'vec.backfill': "埋め戻し",
1099
+ 'vec.refresh': "更新",
1100
+ 'vec.backfill_queued': "{column} の {n} 行を埋め込みのキューに入れました。",
1101
+ 'vec.added': "{table} にベクトル列 {column} を追加しました。{n} 行を埋め込みのキューに入れました。",
1048
1102
  // ------------------------------------------------------------ Shared Components (sprint 124)
1049
1103
  'comp.nav_entry': 'ナビゲーション・エントリ',
1050
1104
  'comp.nav_entry.plural': 'ナビゲーション・メニュー',