@saltcorn/copilot 0.8.1 → 0.8.3
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/actions/generate-page.js +6 -2
- package/actions/generate-tables.js +70 -6
- package/actions/generate-workflow.js +54 -3
- package/agent-skills/pagegen.js +171 -59
- package/agent-skills/registry-editor.js +30 -2
- package/agent-skills/triggergen.js +1 -5
- package/agent-skills/viewgen.js +49 -7
- package/app-constructor/common.js +7 -1
- package/app-constructor/errors.js +749 -61
- package/app-constructor/feedback-action.js +62 -60
- package/app-constructor/feedback.js +1294 -67
- package/app-constructor/fixed-prompts.js +829 -0
- package/app-constructor/phases.js +1485 -0
- package/app-constructor/prompt-generator.js +587 -0
- package/app-constructor/requirements.js +171 -50
- package/app-constructor/research.js +350 -0
- package/app-constructor/run_task.js +234 -73
- package/app-constructor/schema.js +173 -169
- package/app-constructor/tasks.js +96 -537
- package/app-constructor/tools.js +17 -4
- package/app-constructor/view.js +314 -54
- package/builder-gen.js +90 -41
- package/builder-schema.js +6 -0
- package/copilot-as-agent.js +1 -0
- package/index.js +0 -1
- package/js-code-gen.js +1 -0
- package/package.json +1 -1
- package/relation-paths.js +73 -40
- package/standard-prompt.js +1 -0
- package/user-copilot.js +2 -0
- package/workflow-gen.js +1 -0
- package/app-constructor/prompts.js +0 -120
- package/chat-copilot.js +0 -770
package/builder-gen.js
CHANGED
|
@@ -27,13 +27,9 @@ designated as ownership fields (set automatically from the logged-in user). \
|
|
|
27
27
|
All other foreign key fields — including parent-context keys like trip_id — MUST appear as \
|
|
28
28
|
selector inputs; Saltcorn auto-fills them from the URL state when opened in context.`;
|
|
29
29
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Only use fieldview "edit_day" as a fallback when the field has day_only=true and flatpickr is not installed. \
|
|
34
|
-
For String fields that have an options attribute (a comma-separated list of fixed choices), \
|
|
35
|
-
use fieldview "select" — this renders a dropdown with those options. \
|
|
36
|
-
Do not use "select_by_code" for fields with fixed options.`;
|
|
30
|
+
const {
|
|
31
|
+
fieldview_selection_rules: EDIT_FIELDVIEW_SELECTION,
|
|
32
|
+
} = require("./app-constructor/prompts");
|
|
37
33
|
|
|
38
34
|
const EDIT_LAYOUT_STRUCTURE = `\
|
|
39
35
|
Every field MUST be preceded by a label. \
|
|
@@ -79,7 +75,9 @@ only use "show_with_html" when the task explicitly requires rendering HTML conte
|
|
|
79
75
|
|
|
80
76
|
const LISTCOLUMNS_VIEW_LINK = `\
|
|
81
77
|
A view_link that opens the detail of a row MUST point to a Show-type view (viewtemplate "Show"). \
|
|
82
|
-
Only include such a view_link if a Show view for this table is listed in the available views
|
|
78
|
+
Only include such a view_link if a Show view for this table is listed in the available views. \
|
|
79
|
+
Every view_link requires a "relation" string — you MUST call get_relation_paths before generating the layout \
|
|
80
|
+
whenever the layout will contain any view_link or embedded view segment.`;
|
|
83
81
|
|
|
84
82
|
// ── Composed MODE_GUIDANCE ────────────────────────────────────────────────────
|
|
85
83
|
|
|
@@ -805,7 +803,8 @@ const normalizeSegment = (segment, ctx) => {
|
|
|
805
803
|
let viewLabel = clone.view_label || clone.view;
|
|
806
804
|
let isFormula = clone.isFormula || {};
|
|
807
805
|
const tmplMatch =
|
|
808
|
-
typeof viewLabel === "string" &&
|
|
806
|
+
typeof viewLabel === "string" &&
|
|
807
|
+
viewLabel.match(/^\{\{\s*(.+?)\s*\}\}$/);
|
|
809
808
|
if (tmplMatch) {
|
|
810
809
|
viewLabel = tmplMatch[1];
|
|
811
810
|
isFormula = { ...isFormula, label: true };
|
|
@@ -843,7 +842,9 @@ const normalizeSegment = (segment, ctx) => {
|
|
|
843
842
|
child == null ? null : normalizeSegment(child, ctx)
|
|
844
843
|
);
|
|
845
844
|
if (!besides.some(Boolean)) return null;
|
|
846
|
-
|
|
845
|
+
// Drop 'type' — List viewtemplate expects { besides, list_columns: true } with no type field
|
|
846
|
+
const { type: _t, ...rest } = clone;
|
|
847
|
+
return { ...rest, besides, list_columns: true };
|
|
847
848
|
}
|
|
848
849
|
case "list_column": {
|
|
849
850
|
let raw = clone.contents;
|
|
@@ -961,7 +962,21 @@ const normalizeLayoutCandidate = (candidate, ctx) => {
|
|
|
961
962
|
normalized = convertForeignLayout(candidate, ctx);
|
|
962
963
|
}
|
|
963
964
|
if (!normalized) return null;
|
|
964
|
-
|
|
965
|
+
let layout = Array.isArray(normalized) ? { above: normalized } : normalized;
|
|
966
|
+
|
|
967
|
+
// For listcolumns mode: if the LLM wrapped the list in a stack, unwrap it
|
|
968
|
+
if (
|
|
969
|
+
ctx.mode === "listcolumns" &&
|
|
970
|
+
layout.type === "stack" &&
|
|
971
|
+
Array.isArray(layout.above)
|
|
972
|
+
) {
|
|
973
|
+
const listChild = layout.above.find((c) => c?.list_columns);
|
|
974
|
+
if (listChild) {
|
|
975
|
+
const { type: _t, ...rest } = listChild;
|
|
976
|
+
layout = rest;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
965
980
|
return sanitizeNoHtmlSegments(layout);
|
|
966
981
|
};
|
|
967
982
|
|
|
@@ -1000,6 +1015,11 @@ const buildPromptText = (userPrompt, ctx, schema) => {
|
|
|
1000
1015
|
schema
|
|
1001
1016
|
)}`
|
|
1002
1017
|
);
|
|
1018
|
+
parts.push(
|
|
1019
|
+
`TOOL CALL REQUIRED: If the layout you are about to generate contains any view_link or embedded view (type "view") segment, ` +
|
|
1020
|
+
`you MUST call get_relation_paths with all required source_table/target_view pairs BEFORE returning JSON. ` +
|
|
1021
|
+
`Do NOT guess or omit the "relation" field — return the tool call first and use the result in the layout.`
|
|
1022
|
+
);
|
|
1003
1023
|
parts.push(
|
|
1004
1024
|
`Based on the schema above, process the following user request and generate the layout JSON. Reminder: ONLY output valid JSON starting with { and ending with }, no markdown fences.\nUser request:\n"${userPrompt}"`
|
|
1005
1025
|
);
|
|
@@ -1220,7 +1240,6 @@ const buildBracketObject = (node) => {
|
|
|
1220
1240
|
return obj;
|
|
1221
1241
|
};
|
|
1222
1242
|
|
|
1223
|
-
|
|
1224
1243
|
const buildContext = async (mode, tableName) => {
|
|
1225
1244
|
const normalizedMode = (mode || "show").toLowerCase();
|
|
1226
1245
|
const ctx = {
|
|
@@ -1286,9 +1305,16 @@ const buildContext = async (mode, tableName) => {
|
|
|
1286
1305
|
if (!fieldviews.length && String(typeName).startsWith("Key")) {
|
|
1287
1306
|
fieldviews = ["select", "show"];
|
|
1288
1307
|
}
|
|
1308
|
+
// File fields store their fieldviews in state.fileviews, not on the type object
|
|
1309
|
+
let fileFvDefs = {};
|
|
1310
|
+
if (String(typeName) === "File") {
|
|
1311
|
+
fileFvDefs = getState().fileviews || {};
|
|
1312
|
+
fieldviews = Object.keys(fileFvDefs);
|
|
1313
|
+
}
|
|
1289
1314
|
// editFieldviews: only fieldviews where isEdit is not explicitly false
|
|
1315
|
+
const effectiveFvDefs = String(typeName) === "File" ? fileFvDefs : fvDefs;
|
|
1290
1316
|
const editFieldviews = fieldviews.filter(
|
|
1291
|
-
(fv) =>
|
|
1317
|
+
(fv) => effectiveFvDefs[fv]?.isEdit !== false
|
|
1292
1318
|
);
|
|
1293
1319
|
|
|
1294
1320
|
const isPkName =
|
|
@@ -1400,16 +1426,19 @@ const buildErrorLayout = ({ message, mode, table }) => {
|
|
|
1400
1426
|
};
|
|
1401
1427
|
};
|
|
1402
1428
|
|
|
1403
|
-
const GET_RELATION_PATHS_TOOL = {
|
|
1429
|
+
const GET_RELATION_PATHS_TOOL = {
|
|
1430
|
+
type: "function",
|
|
1431
|
+
function: GET_RELATION_PATHS_FUNCTION,
|
|
1432
|
+
};
|
|
1404
1433
|
|
|
1405
1434
|
/**
|
|
1406
|
-
* Run the LLM
|
|
1407
|
-
*
|
|
1435
|
+
* Run the LLM allowing up to MAX_TOOL_ROUNDS get_relation_paths calls (depth
|
|
1436
|
+
* escalation: 2 → 4 → 6) before producing the final layout JSON.
|
|
1408
1437
|
*/
|
|
1438
|
+
const MAX_TOOL_ROUNDS = 4;
|
|
1409
1439
|
const runWithRelationTools = async (llm, mainPrompt, opts) => {
|
|
1410
1440
|
const llm_add_message = getState().functions.llm_add_message;
|
|
1411
1441
|
|
|
1412
|
-
// Local chat copy with the main prompt pre-loaded so all iterations see it.
|
|
1413
1442
|
const runChat = Array.isArray(opts.chat) ? [...opts.chat] : [];
|
|
1414
1443
|
runChat.push({ role: "user", content: mainPrompt });
|
|
1415
1444
|
|
|
@@ -1418,35 +1447,54 @@ const runWithRelationTools = async (llm, mainPrompt, opts) => {
|
|
|
1418
1447
|
delete toolOpts.response_format;
|
|
1419
1448
|
delete toolOpts.chat;
|
|
1420
1449
|
|
|
1421
|
-
|
|
1422
|
-
const raw = await llm.run(null, { ...toolOpts, chat: runChat });
|
|
1423
|
-
if (!raw?.hasToolCalls) {
|
|
1424
|
-
return typeof raw === "string" ? raw : raw?.content ?? "";
|
|
1425
|
-
}
|
|
1450
|
+
const schemaData = await build_schema_data();
|
|
1426
1451
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
raw.messages.filter((m) => m.role === "assistant").forEach((m) => runChat.push(m));
|
|
1430
|
-
} else if (raw.tool_calls) {
|
|
1431
|
-
runChat.push({ role: "assistant", content: raw.content || null, tool_calls: raw.tool_calls });
|
|
1432
|
-
}
|
|
1452
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
1453
|
+
const raw = await llm.run(null, { ...toolOpts, chat: runChat });
|
|
1433
1454
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
} else {
|
|
1444
|
-
|
|
1455
|
+
if (!raw?.hasToolCalls) {
|
|
1456
|
+
return typeof raw === "string" ? raw : raw?.content ?? "";
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Append the assistant message so the next round has full context.
|
|
1460
|
+
if (raw.ai_sdk && Array.isArray(raw.messages)) {
|
|
1461
|
+
raw.messages
|
|
1462
|
+
.filter((m) => m.role === "assistant")
|
|
1463
|
+
.forEach((m) => runChat.push(m));
|
|
1464
|
+
} else if (raw.tool_calls) {
|
|
1465
|
+
runChat.push({
|
|
1466
|
+
role: "assistant",
|
|
1467
|
+
content: raw.content || null,
|
|
1468
|
+
tool_calls: raw.tool_calls,
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
for (const tc of raw.getToolCalls()) {
|
|
1473
|
+
let resultText;
|
|
1474
|
+
if (tc.tool_name === "get_relation_paths") {
|
|
1475
|
+
const maxDepth = tc.input.max_depth ?? 2;
|
|
1476
|
+
const sections = getRelationPathsForPairs(
|
|
1477
|
+
tc.input.pairs || [],
|
|
1478
|
+
schemaData,
|
|
1479
|
+
maxDepth
|
|
1480
|
+
);
|
|
1481
|
+
resultText =
|
|
1482
|
+
sections.join("\n\n") +
|
|
1483
|
+
`\n\nFor each pair above: analyse whether the listed paths include a suitable one for the intended relation type (ChildList, ParentShow, Own, etc.). ` +
|
|
1484
|
+
`If a suitable path exists, set the "relation" property to the chosen path string for all types including Own. ` +
|
|
1485
|
+
`If no suitable path exists for a pair (no paths listed, or none match the intended type), call get_relation_paths again with a higher max_depth (4, then 6). ` +
|
|
1486
|
+
`Do not escalate just because multiple paths are listed — only escalate when none is appropriate.`;
|
|
1487
|
+
} else {
|
|
1488
|
+
resultText = `Unknown tool: ${tc.tool_name}`;
|
|
1489
|
+
}
|
|
1490
|
+
await llm_add_message.run("tool_response", resultText, {
|
|
1491
|
+
chat: runChat,
|
|
1492
|
+
tool_call: tc,
|
|
1493
|
+
});
|
|
1445
1494
|
}
|
|
1446
|
-
await llm_add_message.run("tool_response", resultText, { chat: runChat, tool_call: tc });
|
|
1447
1495
|
}
|
|
1448
1496
|
|
|
1449
|
-
//
|
|
1497
|
+
// Exhausted tool rounds — force final JSON answer.
|
|
1450
1498
|
const final = await llm.run(null, { ...opts, chat: runChat });
|
|
1451
1499
|
return typeof final === "string" ? final : final?.content ?? "";
|
|
1452
1500
|
};
|
|
@@ -1564,6 +1612,7 @@ module.exports = {
|
|
|
1564
1612
|
},
|
|
1565
1613
|
isAsync: true,
|
|
1566
1614
|
description: "Generate a builder layout",
|
|
1615
|
+
hidden: true,
|
|
1567
1616
|
arguments: [
|
|
1568
1617
|
{ name: "prompt", type: "String" },
|
|
1569
1618
|
{ name: "mode", type: "String" },
|
package/builder-schema.js
CHANGED
|
@@ -327,7 +327,13 @@ const buildBuilderSchema = ({ mode, ctx }) => {
|
|
|
327
327
|
const actionDef = buildSegmentDef({
|
|
328
328
|
type: "action",
|
|
329
329
|
description: "Action button segment.",
|
|
330
|
+
required: ["rndid"],
|
|
330
331
|
properties: {
|
|
332
|
+
rndid: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description:
|
|
335
|
+
"Unique short identifier for this action (e.g. 'act1', 'del2'). REQUIRED — omitting it causes a server crash.",
|
|
336
|
+
},
|
|
331
337
|
action_name: defs.action_name_enum
|
|
332
338
|
? { $ref: "#/$defs/action_name_enum", description: "Action name." }
|
|
333
339
|
: { type: "string", description: "Action name." },
|
package/copilot-as-agent.js
CHANGED
package/index.js
CHANGED
|
@@ -18,7 +18,6 @@ module.exports = {
|
|
|
18
18
|
dependencies: ["@saltcorn/large-language-model", "@saltcorn/agents"],
|
|
19
19
|
viewtemplates: features.workflows
|
|
20
20
|
? [
|
|
21
|
-
require("./chat-copilot"),
|
|
22
21
|
require("./user-copilot"),
|
|
23
22
|
require("./copilot-as-agent"),
|
|
24
23
|
require("./app-constructor/view.js"),
|
package/js-code-gen.js
CHANGED
package/package.json
CHANGED
package/relation-paths.js
CHANGED
|
@@ -36,66 +36,72 @@ const { RelationsFinder, RelationType } = require("@saltcorn/common-code");
|
|
|
36
36
|
const RELATION_PATH_DOC = `
|
|
37
37
|
## Relation paths
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
Every view_link and embedded view segment requires two fields:
|
|
40
|
+
- \`view\`: the view name (plain string, e.g. \`"packing_items_list"\`)
|
|
41
|
+
- \`relation\`: a dot-separated path string (e.g. \`".trips.packing_items$trip_id"\`)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
**Always use this format. Never generate anything else.**
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
---
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
- \`view\`: just the view name, e.g. \`"packing_items_list"\`
|
|
48
|
-
- \`relation\`: a dot-separated path string, e.g. \`".trips.packing_items$trip_id"\`
|
|
47
|
+
### Relation path format
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
\`.sourcetable.segment1.segment2...\`
|
|
51
50
|
|
|
52
51
|
Segment types:
|
|
53
|
-
- **Outbound FK** (
|
|
54
|
-
- **Inbound FK** (
|
|
52
|
+
- **Outbound FK** (to a parent): FK field name alone — e.g. \`trip_id\`
|
|
53
|
+
- **Inbound FK** (child rows): \`childtable$fkfield\` — e.g. \`packing_items$trip_id\`
|
|
54
|
+
- **Same table** (no FK traversal): just the source table, no segments — e.g. \`".invoice_line_items"\`
|
|
55
55
|
|
|
56
56
|
Examples:
|
|
57
|
-
| relation string |
|
|
58
|
-
|
|
59
|
-
| \`.
|
|
60
|
-
| \`.packing_items
|
|
61
|
-
| \`.
|
|
62
|
-
| \`.
|
|
57
|
+
| relation string | meaning |
|
|
58
|
+
|---|---|
|
|
59
|
+
| \`.invoice_line_items\` | same-table link (no FK traversal) |
|
|
60
|
+
| \`.trips.packing_items$trip_id\` | all packing_items for a trip |
|
|
61
|
+
| \`.packing_items.trip_id\` | the trip that owns a packing_item |
|
|
62
|
+
| \`.artists.artist_plays_on_album$artist.album\` | albums via join table |
|
|
63
|
+
| \`.users.orders$user_id.order_lines$order_id\` | multi-level |
|
|
63
64
|
|
|
64
65
|
---
|
|
65
66
|
|
|
66
|
-
### Legacy format
|
|
67
|
+
### Legacy format — read-only, never generate
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
Any \`view\` field value that contains a colon (e.g. \`"ChildList:trips_list.packing_items.trip_id"\`,
|
|
70
|
+
\`"Own:viewname"\`, \`"ParentShow:viewname.table.fkfield"\`) is legacy. You may encounter it in
|
|
71
|
+
existing configs. Parse it to understand the relation, then always write back in the new format.
|
|
70
72
|
|
|
71
|
-
| legacy view field |
|
|
73
|
+
| legacy view field | new format equivalent |
|
|
72
74
|
|---|---|
|
|
73
|
-
| \`"Own:viewname"\` | \`
|
|
75
|
+
| \`"Own:viewname"\` | \`relation: ".sourcetable"\` |
|
|
76
|
+
| \`"Independent:viewname"\` | no \`relation\` field needed |
|
|
74
77
|
| \`"ParentShow:viewname.table.fkfield"\` | \`relation: ".sourcetable.fkfield"\` |
|
|
75
78
|
| \`"ChildList:viewname.table.inbkey"\` | \`relation: ".sourcetable.childtable$inbkey"\` |
|
|
76
79
|
| \`"OneToOneShow:viewname.table.inbkey"\` | \`relation: ".sourcetable.childtable$inbkey"\` |
|
|
77
|
-
| \`"Independent:viewname"\` | \`view: "viewname"\`, no relation |
|
|
78
|
-
|
|
79
|
-
When you read an existing view config and see a legacy \`view\` value like \`"ChildList:trips_list.packing_items.trip_id"\`, parse it as: type=ChildList, view=trips_list, relation=.sourcetable.packing_items$trip_id. When writing back, convert to the new format.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
83
83
|
### Using get_relation_paths
|
|
84
84
|
|
|
85
|
-
Call it **once** with all source_table/target_view pairs you need
|
|
85
|
+
Call it **once** with all source_table/target_view pairs you need. The tool always returns new-format
|
|
86
|
+
path strings — use them as the \`relation\` field directly.
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
**Depth escalation:** Start with \`max_depth=2\`. After receiving results, analyse each pair: does the
|
|
89
|
+
result contain a path that matches the intended relation type? If yes, use it. If a pair has no
|
|
90
|
+
suitable path (no paths at all, or none of the right type), call again with \`max_depth=4\`, then
|
|
91
|
+
\`max_depth=6\` if still none. Do not escalate just because multiple paths are listed.
|
|
92
|
+
|
|
93
|
+
Selecting among returned paths:
|
|
94
|
+
- **ChildList** — target view shows multiple rows belonging to the current row.
|
|
95
|
+
- **ParentShow** — target view shows the single parent the current row belongs to.
|
|
90
96
|
- **OneToOneShow** — exactly one related child row via a unique FK.
|
|
91
|
-
- **Own** —
|
|
92
|
-
- If multiple paths of the same type exist
|
|
97
|
+
- **Own** — same table, no FK traversal. Relation string is just \`.sourcetable\`.
|
|
98
|
+
- If multiple paths of the same type exist, pick the one whose FK field name best matches the task.
|
|
93
99
|
- Prefer shorter paths (fewer segments) unless a longer one is clearly more appropriate.
|
|
94
100
|
`;
|
|
95
101
|
|
|
96
102
|
const typeToLabel = (type) => {
|
|
97
103
|
if (type === RelationType.OWN)
|
|
98
|
-
return "Own – source and target are the same table (no
|
|
104
|
+
return "Own – source and target are the same table. Use this relation string as-is (no extra segments after the table name).";
|
|
99
105
|
if (type === RelationType.INDEPENDENT)
|
|
100
106
|
return "Independent – no FK relationship exists";
|
|
101
107
|
if (type === RelationType.PARENT_SHOW)
|
|
@@ -113,10 +119,19 @@ const typeToLabel = (type) => {
|
|
|
113
119
|
* @param {{ tables, views }} schemaData pre-fetched via build_schema_data()
|
|
114
120
|
* @returns {Array<Relation>} raw Relation objects from RelationsFinder
|
|
115
121
|
*/
|
|
116
|
-
function getRelationPaths(
|
|
122
|
+
function getRelationPaths(
|
|
123
|
+
sourceTableName,
|
|
124
|
+
targetViewName,
|
|
125
|
+
schemaData,
|
|
126
|
+
maxDepth = 6
|
|
127
|
+
) {
|
|
117
128
|
if (!schemaData) return [];
|
|
118
129
|
try {
|
|
119
|
-
const finder = new RelationsFinder(
|
|
130
|
+
const finder = new RelationsFinder(
|
|
131
|
+
schemaData.tables,
|
|
132
|
+
schemaData.views,
|
|
133
|
+
maxDepth
|
|
134
|
+
);
|
|
120
135
|
return finder.findRelations(sourceTableName, targetViewName, []);
|
|
121
136
|
} catch {
|
|
122
137
|
return [];
|
|
@@ -130,13 +145,22 @@ function getRelationPaths(sourceTableName, targetViewName, schemaData) {
|
|
|
130
145
|
* @param {{ tables, views }} schemaData
|
|
131
146
|
* @returns {Array<string>} one formatted result string per pair
|
|
132
147
|
*/
|
|
133
|
-
function getRelationPathsForPairs(pairs, schemaData) {
|
|
134
|
-
if (!schemaData)
|
|
135
|
-
|
|
148
|
+
function getRelationPathsForPairs(pairs, schemaData, maxDepth = 6) {
|
|
149
|
+
if (!schemaData)
|
|
150
|
+
return pairs.map(({ source_table, target_view }) =>
|
|
151
|
+
formatRelationPathResult(source_table, target_view, {
|
|
152
|
+
error: "Schema data unavailable",
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
const finder = new RelationsFinder(
|
|
156
|
+
schemaData.tables,
|
|
157
|
+
schemaData.views,
|
|
158
|
+
maxDepth
|
|
136
159
|
);
|
|
137
|
-
const finder = new RelationsFinder(schemaData.tables, schemaData.views, 6);
|
|
138
160
|
return pairs.map(({ source_table, target_view }) => {
|
|
139
|
-
const targetView = (schemaData.views || []).find(
|
|
161
|
+
const targetView = (schemaData.views || []).find(
|
|
162
|
+
(v) => v.name === target_view
|
|
163
|
+
);
|
|
140
164
|
if (!targetView)
|
|
141
165
|
return formatRelationPathResult(source_table, target_view, {
|
|
142
166
|
error: `View "${target_view}" not found in current schema`,
|
|
@@ -198,8 +222,11 @@ const GET_RELATION_PATHS_FUNCTION = {
|
|
|
198
222
|
name: "get_relation_paths",
|
|
199
223
|
description:
|
|
200
224
|
"Get all valid relation path strings for one or more source_table/target_view pairs. " +
|
|
201
|
-
"Call this
|
|
202
|
-
"
|
|
225
|
+
"Call this before setting any 'relation' property on view_link columns or embedded view segments. " +
|
|
226
|
+
"Always start with max_depth=2 to keep the result compact. " +
|
|
227
|
+
"After receiving the results, analyse whether each pair has a suitable path for its intended relation type. " +
|
|
228
|
+
"If a pair has no suitable path, call again with max_depth=4, then max_depth=6 if still none. " +
|
|
229
|
+
"Stop as soon as every pair has a suitable path.",
|
|
203
230
|
parameters: {
|
|
204
231
|
type: "object",
|
|
205
232
|
required: ["pairs"],
|
|
@@ -223,6 +250,12 @@ const GET_RELATION_PATHS_FUNCTION = {
|
|
|
223
250
|
},
|
|
224
251
|
},
|
|
225
252
|
},
|
|
253
|
+
max_depth: {
|
|
254
|
+
type: "integer",
|
|
255
|
+
description:
|
|
256
|
+
"Maximum join depth to search. Start with 2. Escalate to 4, then 6, only if a pair has no suitable path in the current results — not just because paths exist, but because none match the intended relation type.",
|
|
257
|
+
default: 2,
|
|
258
|
+
},
|
|
226
259
|
},
|
|
227
260
|
},
|
|
228
261
|
};
|
package/standard-prompt.js
CHANGED
package/user-copilot.js
CHANGED
package/workflow-gen.js
CHANGED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
const saltcorn_description = `This application will be implemented in Saltcorn, a database application development
|
|
2
|
-
environment.
|
|
3
|
-
|
|
4
|
-
Saltcorn applications contain the following entity types:
|
|
5
|
-
|
|
6
|
-
* Tables: These are relational database tables and consists of fields of specified types and rows
|
|
7
|
-
with a value for each field. Fields optionally can be required and/or unique. Every field has a name,
|
|
8
|
-
which is a an identifier that is balid in both JavaScript and SQL, and a label, which is any short
|
|
9
|
-
user-friendly string. Every table has a primary key
|
|
10
|
-
(composite primary keys are not supported) which by default is an auto-incrementing integer with
|
|
11
|
-
name \`id\` and label ID. Fields can also be of Key type (foreign key) referencing a primary key
|
|
12
|
-
in another table, or its own table for a self-join. Tables can have
|
|
13
|
-
calculated fields, which can be stored or non-stored. Both stored and non-stored fields are
|
|
14
|
-
defined by a JavaScript expression, but only stored fields can reference other tables with join
|
|
15
|
-
fields and aggregations.
|
|
16
|
-
|
|
17
|
-
* Views: Views are elementary user interfaces into a database table. A view is defined by applying a
|
|
18
|
-
view template (also sometime called a view pattern, the two are synonymous) to a table with a certain
|
|
19
|
-
configuration. The view template defines the fundamental relationship between the UI and the table. For
|
|
20
|
-
instance, the Show view template displays a single database row, the Edit view template is a form that
|
|
21
|
-
can create a new row or edit an existing row, the List view template displays multiple rows in a grid.
|
|
22
|
-
Views can embed views, for instance Show can embed another row through a Key field relationship, or
|
|
23
|
-
some views are defined by an underlying view. For instance, the Feed view repeats an underlying view
|
|
24
|
-
for multiple tables. New viewtemplates are provided by plugin modules.
|
|
25
|
-
|
|
26
|
-
* Triggers: Triggers connect elementary actions (provided by plugin modules) to either a button in the
|
|
27
|
-
user interface, or a periodic (hourly, daily etc) or table (for instance insert on specifc table) event.
|
|
28
|
-
The elementary action each has a number of configuration fields that must be filled in after connecting
|
|
29
|
-
the action to an event, table or button.
|
|
30
|
-
|
|
31
|
-
* Page: A page has static content but can also embed views for synamic content. Pages can be either
|
|
32
|
-
defined by a Saltcorn layout, for pages that can be edited with drag and drop, or by HTML for more
|
|
33
|
-
flexible graphic designs. HTML pages should be used for landing pages.
|
|
34
|
-
|
|
35
|
-
* Plugin modules: plugin modules can supply new field types, view templates or actions. Before they can be used,
|
|
36
|
-
they need to be installed before they can be used. A plugin may also have a configuration that sets options
|
|
37
|
-
for that plugin. Layout themes is Saltcorn are plugin modules.
|
|
38
|
-
`;
|
|
39
|
-
|
|
40
|
-
const existing_tables_list = (tables) => {
|
|
41
|
-
const tableLines = [];
|
|
42
|
-
tables.forEach((table) => {
|
|
43
|
-
const fieldLines = table.fields.map(
|
|
44
|
-
(f) =>
|
|
45
|
-
` * ${f.name} with type: ${f.pretty_type}.${
|
|
46
|
-
f.description ? ` ${f.description}` : ""
|
|
47
|
-
}`
|
|
48
|
-
);
|
|
49
|
-
tableLines.push(
|
|
50
|
-
`${table.name}${
|
|
51
|
-
table.description ? `: ${table.description}.` : "."
|
|
52
|
-
} Contains the following fields:\n${fieldLines.join("\n")}`
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
return `The database already contains the following tables:
|
|
56
|
-
|
|
57
|
-
${tableLines.join("\n\n")}`;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const existing_entities_list = ({ views, triggers, pages, tableById = {} }) => {
|
|
61
|
-
const sections = [];
|
|
62
|
-
if (views.length)
|
|
63
|
-
sections.push(
|
|
64
|
-
`The following views are already implemented — do NOT plan tasks to create them:\n` +
|
|
65
|
-
views
|
|
66
|
-
.map((v) => {
|
|
67
|
-
const tablePart =
|
|
68
|
-
v.table?.name ||
|
|
69
|
-
(v.table_id && tableById[v.table_id]) ||
|
|
70
|
-
v.exttable_name;
|
|
71
|
-
return `- ${v.name} (${v.viewtemplate}${
|
|
72
|
-
tablePart ? ` on ${tablePart}` : ""
|
|
73
|
-
})`;
|
|
74
|
-
})
|
|
75
|
-
.join("\n")
|
|
76
|
-
);
|
|
77
|
-
if (triggers.length)
|
|
78
|
-
sections.push(
|
|
79
|
-
`The following triggers are already implemented — do NOT plan tasks to create them:\n` +
|
|
80
|
-
triggers
|
|
81
|
-
.map(
|
|
82
|
-
(t) =>
|
|
83
|
-
`- ${t.name} (${t.action}${
|
|
84
|
-
t.when_trigger ? `, ${t.when_trigger}` : ""
|
|
85
|
-
})`
|
|
86
|
-
)
|
|
87
|
-
.join("\n")
|
|
88
|
-
);
|
|
89
|
-
if (pages.length)
|
|
90
|
-
sections.push(
|
|
91
|
-
`The following pages are already implemented — do NOT plan tasks to create them:\n` +
|
|
92
|
-
pages.map((p) => `- ${p.name}`).join("\n")
|
|
93
|
-
);
|
|
94
|
-
return sections.join("\n\n");
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const available_plugins_list = (storePlugins, installedNames) => {
|
|
98
|
-
const uninstalled = storePlugins.filter((p) => !installedNames.has(p.name));
|
|
99
|
-
if (!uninstalled.length) return "";
|
|
100
|
-
const lines = uninstalled.map((p) => {
|
|
101
|
-
let line = `### ${p.name}`;
|
|
102
|
-
if (p.description) line += `\n${p.description}`;
|
|
103
|
-
if (p.contents) line += `\n${p.contents}`;
|
|
104
|
-
return line;
|
|
105
|
-
});
|
|
106
|
-
return (
|
|
107
|
-
`The following plugins are available in the Saltcorn store but not yet installed. ` +
|
|
108
|
-
`If a task requires functionality provided by one of these plugins (e.g. a specific view template, field type, or action), ` +
|
|
109
|
-
`include an explicit "Install plugin <name>" task before it with the exact plugin name as listed here. ` +
|
|
110
|
-
`The executor will use that name directly without needing to look it up.\n\n` +
|
|
111
|
-
lines.join("\n\n")
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
saltcorn_description,
|
|
117
|
-
existing_tables_list,
|
|
118
|
-
existing_entities_list,
|
|
119
|
-
available_plugins_list,
|
|
120
|
-
};
|