pict-section-prompteditor 1.0.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/LICENSE +21 -0
- package/README.md +118 -0
- package/package.json +45 -0
- package/source/Pict-Section-PromptEditor.js +56 -0
- package/source/compiler/PromptCompiler.js +120 -0
- package/source/providers/PromptProvider-Base.js +302 -0
- package/source/templates/Pict-Template-WordListEntry.js +140 -0
- package/source/types/PromptEditor-DefaultTypes.js +116 -0
- package/source/views/PictView-PromptEditor.js +1465 -0
- package/source/zip/PromptZip.js +65 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const libPictTemplate = require('pict-template');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The {~WordListEntry:Name~} template expression (short form {~WLE:Name~}).
|
|
7
|
+
*
|
|
8
|
+
* Resolves to one entry drawn from the named word list by weighted random:
|
|
9
|
+
* an entry's chance is weight / sum(weights). With Dinosaurs holding
|
|
10
|
+
* [["Tyrannosaurus", 3], ["Diplodocus", 1]], the expression renders
|
|
11
|
+
* Tyrannosaurus 75% of the time and Diplodocus 25%.
|
|
12
|
+
*
|
|
13
|
+
* Word lists are found two ways, in order:
|
|
14
|
+
*
|
|
15
|
+
* 1. The data record: a generation run passes the lists in the parse data as
|
|
16
|
+
* __PromptEditorWordLists ({ lowercased name -> Entries }) along with an
|
|
17
|
+
* optional __PromptEditorRandom function. This is what the prompt editor
|
|
18
|
+
* view does, and it keeps multiple editor instances exact: each
|
|
19
|
+
* generation resolves against precisely the lists it was given.
|
|
20
|
+
*
|
|
21
|
+
* 2. The pict-level registry: any prompt editor view (or a host) can push a
|
|
22
|
+
* resolver function onto pict.__PictSectionPromptEditorResolvers, so the
|
|
23
|
+
* expression also works in ordinary application templates once a section
|
|
24
|
+
* is mounted. Resolvers take the lowercased list name and return Entries
|
|
25
|
+
* or null.
|
|
26
|
+
*
|
|
27
|
+
* A second optional parameter is the default for a miss:
|
|
28
|
+
* {~WordListEntry:Dinosaurs:a large lizard~} renders the default when the list
|
|
29
|
+
* does not resolve (or has nothing drawable). The split is on the first colon,
|
|
30
|
+
* so defaults may contain colons; an empty default ({~WLE:Name:~}) renders
|
|
31
|
+
* nothing on a miss. With no default, a miss renders the expression back out
|
|
32
|
+
* literally, so a typo is visible in the generated prompt instead of silently
|
|
33
|
+
* vanishing. Each occurrence draws independently; two references to the same
|
|
34
|
+
* list in one prompt can (and should be able to) land on different words.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Draw one word from [[word, weight], ...] by weighted random. Entries with a
|
|
39
|
+
* non-positive weight never draw. Returns null for an empty or all-zero list.
|
|
40
|
+
* @param {Array<Array>} pEntries
|
|
41
|
+
* @param {function} [pRandom] - returns [0, 1); defaults to Math.random
|
|
42
|
+
* @returns {string|null}
|
|
43
|
+
*/
|
|
44
|
+
function weightedPick(pEntries, pRandom)
|
|
45
|
+
{
|
|
46
|
+
if (!Array.isArray(pEntries) || !pEntries.length) { return null; }
|
|
47
|
+
let tmpRandom = (typeof pRandom === 'function') ? pRandom : Math.random;
|
|
48
|
+
let tmpTotal = 0;
|
|
49
|
+
for (let i = 0; i < pEntries.length; i++)
|
|
50
|
+
{
|
|
51
|
+
let tmpWeight = Number(pEntries[i][1]);
|
|
52
|
+
if (isFinite(tmpWeight) && tmpWeight > 0) { tmpTotal += tmpWeight; }
|
|
53
|
+
}
|
|
54
|
+
if (tmpTotal <= 0) { return null; }
|
|
55
|
+
let tmpRoll = tmpRandom() * tmpTotal;
|
|
56
|
+
for (let i = 0; i < pEntries.length; i++)
|
|
57
|
+
{
|
|
58
|
+
let tmpWeight = Number(pEntries[i][1]);
|
|
59
|
+
if (!isFinite(tmpWeight) || tmpWeight <= 0) { continue; }
|
|
60
|
+
tmpRoll -= tmpWeight;
|
|
61
|
+
if (tmpRoll < 0) { return String(pEntries[i][0]); }
|
|
62
|
+
}
|
|
63
|
+
// Floating point edge: the roll landed exactly on the total.
|
|
64
|
+
for (let i = pEntries.length - 1; i >= 0; i--)
|
|
65
|
+
{
|
|
66
|
+
if (Number(pEntries[i][1]) > 0) { return String(pEntries[i][0]); }
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class PictTemplateWordListEntry extends libPictTemplate
|
|
72
|
+
{
|
|
73
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
74
|
+
{
|
|
75
|
+
super(pFable, pOptions, pServiceHash);
|
|
76
|
+
|
|
77
|
+
this.addPattern('{~WordListEntry:', '~}');
|
|
78
|
+
this.addPattern('{~WLE:', '~}');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
render(pTemplateHash, pRecord)
|
|
82
|
+
{
|
|
83
|
+
let tmpBody = String(pTemplateHash || '').trim();
|
|
84
|
+
if (!tmpBody) { return ''; }
|
|
85
|
+
|
|
86
|
+
// {~WordListEntry:ListName:Default~} -- everything after the FIRST colon
|
|
87
|
+
// is the default rendered when the list does not resolve (or resolves
|
|
88
|
+
// with nothing drawable). The default may contain colons, and may be
|
|
89
|
+
// empty: {~WLE:Name:~} renders nothing on a miss. With no default at
|
|
90
|
+
// all, a miss echoes the expression so a typo stays findable.
|
|
91
|
+
let tmpListName = tmpBody;
|
|
92
|
+
let tmpDefault = null;
|
|
93
|
+
let tmpColon = tmpBody.indexOf(':');
|
|
94
|
+
if (tmpColon > -1)
|
|
95
|
+
{
|
|
96
|
+
tmpListName = tmpBody.slice(0, tmpColon).trim();
|
|
97
|
+
tmpDefault = tmpBody.slice(tmpColon + 1).trim();
|
|
98
|
+
}
|
|
99
|
+
if (!tmpListName) { return (tmpDefault === null) ? '' : tmpDefault; }
|
|
100
|
+
let tmpWanted = tmpListName.toLowerCase();
|
|
101
|
+
let tmpMiss = (tmpDefault === null) ? ('{~WordListEntry:' + tmpListName + '~}') : tmpDefault;
|
|
102
|
+
|
|
103
|
+
let tmpEntries = null;
|
|
104
|
+
let tmpRandom = null;
|
|
105
|
+
|
|
106
|
+
// 1. Lists riding in the parse data (a generation run).
|
|
107
|
+
if (pRecord && pRecord.__PromptEditorWordLists && pRecord.__PromptEditorWordLists[tmpWanted])
|
|
108
|
+
{
|
|
109
|
+
tmpEntries = pRecord.__PromptEditorWordLists[tmpWanted];
|
|
110
|
+
}
|
|
111
|
+
if (pRecord && typeof pRecord.__PromptEditorRandom === 'function')
|
|
112
|
+
{
|
|
113
|
+
tmpRandom = pRecord.__PromptEditorRandom;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2. The pict-level resolver registry (mounted sections, host overrides).
|
|
117
|
+
if (!tmpEntries && this.pict && Array.isArray(this.pict.__PictSectionPromptEditorResolvers))
|
|
118
|
+
{
|
|
119
|
+
for (let i = 0; i < this.pict.__PictSectionPromptEditorResolvers.length; i++)
|
|
120
|
+
{
|
|
121
|
+
let tmpResolved = null;
|
|
122
|
+
try { tmpResolved = this.pict.__PictSectionPromptEditorResolvers[i](tmpWanted); }
|
|
123
|
+
catch (pError) { tmpResolved = null; }
|
|
124
|
+
if (tmpResolved && tmpResolved.length) { tmpEntries = tmpResolved; break; }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!tmpEntries || !tmpEntries.length)
|
|
129
|
+
{
|
|
130
|
+
return tmpMiss;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let tmpWord = weightedPick(tmpEntries, tmpRandom);
|
|
134
|
+
return (tmpWord === null) ? tmpMiss : tmpWord;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = PictTemplateWordListEntry;
|
|
139
|
+
module.exports.template_hash = 'WordListEntry';
|
|
140
|
+
module.exports.weightedPick = weightedPick;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in prompt types.
|
|
5
|
+
*
|
|
6
|
+
* A prompt type is a named arrangement of segments. Each segment:
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* Key: stable identifier; a prompt's Segments object is keyed by this
|
|
10
|
+
* Name: the heading the compiler writes (## Name)
|
|
11
|
+
* Guidance: one line shown to the author about what belongs here
|
|
12
|
+
* Fixed: true for locked text (a team preamble); Body below is used and
|
|
13
|
+
* the section renders it read-only instead of an editor
|
|
14
|
+
* Body: the fixed text (only read when Fixed is true)
|
|
15
|
+
* Optional: empty optional segments are skipped at compile time; empty
|
|
16
|
+
* required segments compile with a placeholder note
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* These cover the common shapes of working with an AI pair. They are defaults,
|
|
20
|
+
* not a registry: pass your own array as the PromptTypes option and it replaces
|
|
21
|
+
* this set entirely. To extend instead of replace, spread DefaultPromptTypes
|
|
22
|
+
* into your own array.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const DefaultPromptTypes =
|
|
26
|
+
[
|
|
27
|
+
{
|
|
28
|
+
Key: 'feature-request',
|
|
29
|
+
Name: 'Feature Request',
|
|
30
|
+
Description: 'Ask for a new capability: what exists, what you want, and how you will know it worked.',
|
|
31
|
+
Segments:
|
|
32
|
+
[
|
|
33
|
+
{ Key: 'context', Name: 'Context', Guidance: 'What exists today, where it lives, and anything the reader needs to know cold.' },
|
|
34
|
+
{ Key: 'request', Name: 'Request', Guidance: 'The capability you want, stated plainly.' },
|
|
35
|
+
{ Key: 'success-criteria', Name: 'Success Criteria', Guidance: 'Observable outcomes that prove it works.' }
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
Key: 'bug-report',
|
|
40
|
+
Name: 'Bug Report',
|
|
41
|
+
Description: 'Report something broken with enough signal to reproduce and fix it.',
|
|
42
|
+
Segments:
|
|
43
|
+
[
|
|
44
|
+
{ Key: 'context', Name: 'Context', Guidance: 'The system, version, and environment where the problem shows.' },
|
|
45
|
+
{ Key: 'observed', Name: 'Observed Behavior', Guidance: 'What actually happens, including exact errors.' },
|
|
46
|
+
{ Key: 'expected', Name: 'Expected Behavior', Guidance: 'What should happen instead.' },
|
|
47
|
+
{ Key: 'reproduction', Name: 'Reproduction', Guidance: 'Steps that make it happen, numbered.', Optional: true }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
Key: 'research',
|
|
52
|
+
Name: 'Research',
|
|
53
|
+
Description: 'Send the AI to learn something and come back with a usable answer.',
|
|
54
|
+
Segments:
|
|
55
|
+
[
|
|
56
|
+
{ Key: 'context', Name: 'Context', Guidance: 'Why you are asking and what you already know.' },
|
|
57
|
+
{ Key: 'question', Name: 'Question', Guidance: 'The question, sharp enough to answer.' },
|
|
58
|
+
{ Key: 'constraints', Name: 'Constraints', Guidance: 'Boundaries: scope, sources, time period, format.', Optional: true },
|
|
59
|
+
{ Key: 'deliverable', Name: 'Deliverable', Guidance: 'The shape of the answer you want back.' }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
Key: 'code-review',
|
|
64
|
+
Name: 'Code Review',
|
|
65
|
+
Description: 'Ask for a review with the focus stated up front.',
|
|
66
|
+
Segments:
|
|
67
|
+
[
|
|
68
|
+
{ Key: 'context', Name: 'Context', Guidance: 'What the change does and why it exists.' },
|
|
69
|
+
{ Key: 'focus', Name: 'Focus', Guidance: 'What kind of problems matter most here.' },
|
|
70
|
+
{ Key: 'out-of-scope', Name: 'Out of Scope', Guidance: 'What not to spend attention on.', Optional: true }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
Key: 'freeform',
|
|
75
|
+
Name: 'Freeform',
|
|
76
|
+
Description: 'One open segment, no structure imposed.',
|
|
77
|
+
Segments:
|
|
78
|
+
[
|
|
79
|
+
{ Key: 'body', Name: 'Prompt', Guidance: 'Anything goes.' }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the effective prompt type set from the view options: a provided
|
|
86
|
+
* array wins outright; null/undefined means the defaults.
|
|
87
|
+
* @param {Array|null} pConfigured
|
|
88
|
+
* @returns {Array}
|
|
89
|
+
*/
|
|
90
|
+
function resolvePromptTypes(pConfigured)
|
|
91
|
+
{
|
|
92
|
+
if (Array.isArray(pConfigured) && pConfigured.length) { return pConfigured; }
|
|
93
|
+
return DefaultPromptTypes;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find a type by Key within a resolved set, with a freeform-style fallback so
|
|
98
|
+
* a prompt whose type was removed by the host still renders and compiles.
|
|
99
|
+
* @param {Array} pTypes
|
|
100
|
+
* @param {string} pTypeKey
|
|
101
|
+
* @returns {object}
|
|
102
|
+
*/
|
|
103
|
+
function getPromptType(pTypes, pTypeKey)
|
|
104
|
+
{
|
|
105
|
+
let tmpFound = (pTypes || []).find((pType) => pType.Key === pTypeKey);
|
|
106
|
+
if (tmpFound) { return tmpFound; }
|
|
107
|
+
return (
|
|
108
|
+
{
|
|
109
|
+
Key: pTypeKey || 'unknown',
|
|
110
|
+
Name: (pTypeKey ? pTypeKey : 'Unknown type'),
|
|
111
|
+
Description: 'This prompt type is not in the configured set; editing the raw segments.',
|
|
112
|
+
Segments: [{ Key: 'body', Name: 'Prompt', Guidance: 'Anything goes.' }]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { DefaultPromptTypes, resolvePromptTypes, getPromptType };
|