pict-section-prompteditor 1.0.0 → 1.1.1
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/README.md
CHANGED
|
@@ -9,6 +9,7 @@ A prompt crafting and management section for the [Pict](https://github.com/fable
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Typed prompts with markdown segments (Context / Request / Success Criteria and friends), plus **fixed preamble** segments for locked team standards
|
|
12
|
+
- A per-prompt "segment headings in the output" toggle, saved with the prompt: on for structured documents (`## Context` above each block), off for bare bodies
|
|
12
13
|
- Weighted word list matrices: `[["Tyrannosaurus", 3], ["Diplodocus", 1]]` draws 75/25, with live share percentages as you curate
|
|
13
14
|
- The `{~WordListEntry:Name~}` template expression (short form `{~WLE:~}`), with an optional miss default: `{~WLE:Name:fallback~}`
|
|
14
15
|
- Generation runs the **real pict template engine**, so every pict expression works inside a prompt
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
2
|
+
"name": "pict-section-prompteditor",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Prompt crafting and management section for Pict: typed prompts with segments (context, request, success criteria), curated word list matrices with weights, a {~WordListEntry:Name~} template expression for weighted-random generation, batch generation with zip download, and a pluggable data provider that defaults to in-memory AppData.",
|
|
5
|
+
"main": "source/Pict-Section-PromptEditor.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"source"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/fable-retold/pict-section-prompteditor.git"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "npx mocha -u tdd -R spec --exit test/*_tests.js",
|
|
15
|
+
"start": "node source/Pict-Section-PromptEditor.js",
|
|
16
|
+
"build": "npx quack build",
|
|
17
|
+
"build:codemirror": "node build/build-codemirror-bundle.js",
|
|
18
|
+
"example": "npx quack examples",
|
|
19
|
+
"docs": "npx quack prepare-docs ./docs"
|
|
20
|
+
},
|
|
21
|
+
"author": "steven velozo <steven@velozo.com>",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"jszip": "^3.10.1",
|
|
25
|
+
"pict-section-content": "^1.0.15",
|
|
26
|
+
"pict-section-markdowneditor": "^1.0.17",
|
|
27
|
+
"pict-section-modal": "^1.1.4",
|
|
28
|
+
"pict-section-picker": "^1.4.0",
|
|
29
|
+
"pict-template": "^1.0.15",
|
|
30
|
+
"pict-view": "^1.0.68"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@codemirror/lang-markdown": "^6.5.0",
|
|
34
|
+
"@codemirror/state": "^6.6.0",
|
|
35
|
+
"@codemirror/view": "^6.43.0",
|
|
36
|
+
"chai": "^4.5.0",
|
|
37
|
+
"codemirror": "^6.0.2",
|
|
38
|
+
"esbuild": "^0.28.0",
|
|
39
|
+
"jsdom": "^25.0.1",
|
|
40
|
+
"mocha": "^11.0.1",
|
|
41
|
+
"pict": "^1.0.381",
|
|
42
|
+
"pict-docuserve": "^1.4.19",
|
|
43
|
+
"quackage": "^1.3.0"
|
|
44
|
+
}
|
|
45
45
|
}
|
|
@@ -31,6 +31,12 @@ function slug(pText)
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Assemble a prompt's markdown source from its type's segments.
|
|
34
|
+
*
|
|
35
|
+
* Segment headings (## Context and friends) are on by default; a prompt that
|
|
36
|
+
* carries IncludeSegmentHeadings: false compiles bare segment bodies instead,
|
|
37
|
+
* so the author decides per prompt whether the structure shows in the output.
|
|
38
|
+
* The document title heading stays governed by the IncludeTitleHeading option.
|
|
39
|
+
*
|
|
34
40
|
* @param {object} pPrompt - the prompt record
|
|
35
41
|
* @param {object} pType - the resolved prompt type
|
|
36
42
|
* @param {object} [pOptions] - { IncludeTitleHeading: true, SegmentHeadingLevel: 2 }
|
|
@@ -40,6 +46,7 @@ function assembleSource(pPrompt, pType, pOptions)
|
|
|
40
46
|
{
|
|
41
47
|
let tmpOptions = pOptions || {};
|
|
42
48
|
let tmpIncludeTitle = (typeof tmpOptions.IncludeTitleHeading === 'undefined') ? true : !!tmpOptions.IncludeTitleHeading;
|
|
49
|
+
let tmpIncludeSegmentHeadings = !(pPrompt && pPrompt.IncludeSegmentHeadings === false);
|
|
43
50
|
let tmpHeading = '#'.repeat(Math.max(1, Math.min(6, Number(tmpOptions.SegmentHeadingLevel) || 2)));
|
|
44
51
|
let tmpSegments = (pType && Array.isArray(pType.Segments)) ? pType.Segments : [];
|
|
45
52
|
let tmpBodies = (pPrompt && pPrompt.Segments) ? pPrompt.Segments : {};
|
|
@@ -60,7 +67,9 @@ function assembleSource(pPrompt, pType, pOptions)
|
|
|
60
67
|
if (tmpSegment.Optional || tmpSegment.Fixed) { continue; }
|
|
61
68
|
tmpBody = '(nothing written for this segment yet)';
|
|
62
69
|
}
|
|
63
|
-
tmpParts.push(
|
|
70
|
+
tmpParts.push(tmpIncludeSegmentHeadings
|
|
71
|
+
? (tmpHeading + ' ' + String(tmpSegment.Name || tmpSegment.Key) + '\n\n' + tmpBody)
|
|
72
|
+
: tmpBody);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
return tmpParts.join('\n\n') + '\n';
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
* drawn by {~WordListEntry:Name~} is weight / sum(weights).
|
|
17
17
|
*
|
|
18
18
|
* Prompt { Key, Title, TypeKey, Segments: { segmentKey: markdown },
|
|
19
|
-
* Author, Meta, CreatedAt, UpdatedAt }
|
|
19
|
+
* IncludeSegmentHeadings, Author, Meta, CreatedAt, UpdatedAt }
|
|
20
20
|
* A crafted prompt. Segments hold the markdown for each of the prompt
|
|
21
|
-
* type's segments, keyed by segment Key.
|
|
21
|
+
* type's segments, keyed by segment Key. IncludeSegmentHeadings (default
|
|
22
|
+
* true) decides whether the compiler writes the ## segment headings above
|
|
23
|
+
* each body in the assembled output. Meta is an opaque object the
|
|
22
24
|
* provider round-trips untouched -- the seam where a host hangs ratings,
|
|
23
25
|
* version pointers, or anything else.
|
|
24
26
|
*
|
|
@@ -208,6 +210,7 @@ class InMemoryPromptProvider extends PromptDataProvider
|
|
|
208
210
|
Title: String(tmpDraft.Title || 'Untitled prompt'),
|
|
209
211
|
TypeKey: String(tmpDraft.TypeKey),
|
|
210
212
|
Segments: (tmpDraft.Segments && typeof tmpDraft.Segments === 'object') ? this._clone(tmpDraft.Segments) : {},
|
|
213
|
+
IncludeSegmentHeadings: (tmpDraft.IncludeSegmentHeadings === false) ? false : true,
|
|
211
214
|
Author: tmpDraft.Author || null,
|
|
212
215
|
Meta: (typeof tmpDraft.Meta === 'undefined') ? {} : this._clone(tmpDraft.Meta),
|
|
213
216
|
CreatedAt: tmpNow,
|
|
@@ -225,6 +228,7 @@ class InMemoryPromptProvider extends PromptDataProvider
|
|
|
225
228
|
if (typeof tmpPatch.Title !== 'undefined') { tmpPrompt.Title = String(tmpPatch.Title); }
|
|
226
229
|
if (typeof tmpPatch.TypeKey !== 'undefined') { tmpPrompt.TypeKey = String(tmpPatch.TypeKey); }
|
|
227
230
|
if (typeof tmpPatch.Segments !== 'undefined') { tmpPrompt.Segments = this._clone(tmpPatch.Segments || {}); }
|
|
231
|
+
if (typeof tmpPatch.IncludeSegmentHeadings !== 'undefined') { tmpPrompt.IncludeSegmentHeadings = (tmpPatch.IncludeSegmentHeadings === false) ? false : true; }
|
|
228
232
|
if (typeof tmpPatch.Meta !== 'undefined') { tmpPrompt.Meta = this._clone(tmpPatch.Meta); }
|
|
229
233
|
tmpPrompt.UpdatedAt = this._now();
|
|
230
234
|
return Promise.resolve(this._clone(tmpPrompt));
|
|
@@ -83,13 +83,25 @@ const _DefaultConfiguration =
|
|
|
83
83
|
CSS: /*css*/`
|
|
84
84
|
.pspe { font-size: 14px; color: var(--theme-color-text-primary, #1f2430); }
|
|
85
85
|
.pspe-header { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; flex-wrap: wrap; }
|
|
86
|
-
|
|
86
|
+
/* Structural titles take the theme's optional display face + treatment.
|
|
87
|
+
The fallbacks (inherit / none / normal / 650) keep the look unchanged
|
|
88
|
+
for hosts whose theme does not define a display family. A host whose
|
|
89
|
+
brand carries one (e.g. a drafting/engraving face) gets it here with
|
|
90
|
+
no app-side override. */
|
|
91
|
+
.pspe-title { font-size: 16px; margin-right: auto;
|
|
92
|
+
font-family: var(--theme-typography-family-display, inherit);
|
|
93
|
+
text-transform: var(--theme-typography-display-transform, none);
|
|
94
|
+
letter-spacing: var(--theme-typography-display-tracking, normal);
|
|
95
|
+
font-weight: var(--theme-typography-display-weight, 650); }
|
|
87
96
|
.pspe-tabs { display: flex; gap: 4px; }
|
|
88
97
|
.pspe-tab {
|
|
89
98
|
font: inherit; font-size: 13px; padding: 6px 12px; cursor: pointer;
|
|
90
99
|
border: 1px solid var(--theme-color-border-default, #d6dde3);
|
|
91
100
|
background: var(--theme-color-background-panel, #fff);
|
|
92
101
|
color: var(--theme-color-text-secondary, #5b6470); border-radius: 8px;
|
|
102
|
+
font-family: var(--theme-typography-family-display, inherit);
|
|
103
|
+
text-transform: var(--theme-typography-display-transform, none);
|
|
104
|
+
letter-spacing: var(--theme-typography-display-tracking, normal);
|
|
93
105
|
}
|
|
94
106
|
.pspe-tab-on { background: var(--theme-color-brand-primary, #2880a6); border-color: var(--theme-color-brand-primary, #2880a6); color: var(--theme-color-text-on-brand, #fff); }
|
|
95
107
|
.pspe-tab-count { opacity: 0.75; font-size: 11px; margin-left: 4px; }
|
|
@@ -136,13 +148,19 @@ const _DefaultConfiguration =
|
|
|
136
148
|
.pspe-editor-head { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
|
|
137
149
|
.pspe-editor-title { flex: 1; min-width: 200px; font-weight: 650; font-size: 15px; }
|
|
138
150
|
.pspe-type-desc { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin: -6px 0 12px; }
|
|
151
|
+
.pspe-editor-opts { margin: -4px 0 12px; }
|
|
152
|
+
.pspe-editor-opt { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); cursor: pointer; user-select: none; }
|
|
139
153
|
|
|
140
154
|
.pspe-segment {
|
|
141
155
|
border: 1px solid var(--theme-color-border-light, #e7ecf0); border-radius: 10px;
|
|
142
156
|
background: var(--theme-color-background-panel, #fff); padding: 12px 14px; margin-bottom: 10px;
|
|
143
157
|
}
|
|
144
158
|
.pspe-segment-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
|
|
145
|
-
.pspe-segment-name { font-
|
|
159
|
+
.pspe-segment-name { font-size: 13.5px;
|
|
160
|
+
font-family: var(--theme-typography-family-display, inherit);
|
|
161
|
+
text-transform: var(--theme-typography-display-transform, none);
|
|
162
|
+
letter-spacing: var(--theme-typography-display-tracking, normal);
|
|
163
|
+
font-weight: var(--theme-typography-display-weight, 650); }
|
|
146
164
|
.pspe-segment-guidance { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-right: auto; }
|
|
147
165
|
.pspe-pill {
|
|
148
166
|
font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; padding: 1px 8px; border-radius: 999px;
|
|
@@ -170,7 +188,11 @@ const _DefaultConfiguration =
|
|
|
170
188
|
.pspe-generate-note { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-left: auto; }
|
|
171
189
|
.pspe-preview-panel { margin-top: 12px; }
|
|
172
190
|
.pspe-preview-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
|
|
173
|
-
.pspe-preview-title { font-
|
|
191
|
+
.pspe-preview-title { font-size: 13.5px;
|
|
192
|
+
font-family: var(--theme-typography-family-display, inherit);
|
|
193
|
+
text-transform: var(--theme-typography-display-transform, none);
|
|
194
|
+
letter-spacing: var(--theme-typography-display-tracking, normal);
|
|
195
|
+
font-weight: var(--theme-typography-display-weight, 650); }
|
|
174
196
|
.pspe-preview-note { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-right: auto; }
|
|
175
197
|
.pspe-btn-on, .pspe-btn-on:hover { background: var(--theme-color-brand-primary, #2880a6); border-color: var(--theme-color-brand-primary, #2880a6); color: var(--theme-color-text-on-brand, #fff); }
|
|
176
198
|
.pspe-preview-raw {
|
|
@@ -254,6 +276,7 @@ const _DefaultConfiguration =
|
|
|
254
276
|
{~TS:PromptEditor-PromptActions:Record.ActionsSlot~}
|
|
255
277
|
</div>
|
|
256
278
|
<div class="pspe-type-desc">{~D:Record.TypeDescription~}</div>
|
|
279
|
+
{~TS:PromptEditor-EditorOptions:Record.OptionsSlot~}
|
|
257
280
|
{~TS:PromptEditor-Segment:Record.Segments~}
|
|
258
281
|
{~TS:PromptEditor-GenerateBar:Record.GenerateSlot~}
|
|
259
282
|
{~TS:PromptEditor-PreviewPanel:Record.PreviewPanelSlot~}`
|
|
@@ -282,6 +305,17 @@ const _DefaultConfiguration =
|
|
|
282
305
|
Hash: 'PromptEditor-PromptActions',
|
|
283
306
|
Template: /*html*/`<button class="pspe-btn pspe-btn-sm" title="Duplicate this prompt" onclick="_Pict.views['{~D:Record.ViewHash~}'].duplicatePrompt()">Duplicate</button><button class="pspe-btn pspe-btn-sm pspe-btn-danger" onclick="_Pict.views['{~D:Record.ViewHash~}'].deletePrompt()">Delete</button>`
|
|
284
307
|
},
|
|
308
|
+
{
|
|
309
|
+
Hash: 'PromptEditor-EditorOptions',
|
|
310
|
+
Template: /*html*/`
|
|
311
|
+
<div class="pspe-editor-opts">
|
|
312
|
+
<label class="pspe-editor-opt" title="When off, the assembled prompt is just the segment bodies, no ## headings above them.">
|
|
313
|
+
<input type="checkbox" {~D:Record.SegmentHeadingsChecked~}
|
|
314
|
+
onchange="_Pict.views['{~D:Record.ViewHash~}'].setSegmentHeadings(this.checked)">
|
|
315
|
+
Segment headings in the output
|
|
316
|
+
</label>
|
|
317
|
+
</div>`
|
|
318
|
+
},
|
|
285
319
|
{
|
|
286
320
|
Hash: 'PromptEditor-Segment',
|
|
287
321
|
Template: /*html*/`
|
|
@@ -909,6 +943,11 @@ class PictViewPromptEditor extends libPictView
|
|
|
909
943
|
SelectedAttr: (pTypeOption.Key === pPrompt.TypeKey) ? 'selected' : ''
|
|
910
944
|
})),
|
|
911
945
|
ActionsSlot: pReadOnly ? [] : [{ ViewHash: this.Hash }],
|
|
946
|
+
OptionsSlot: pReadOnly ? [] :
|
|
947
|
+
[{
|
|
948
|
+
ViewHash: this.Hash,
|
|
949
|
+
SegmentHeadingsChecked: (pPrompt.IncludeSegmentHeadings === false) ? '' : 'checked'
|
|
950
|
+
}],
|
|
912
951
|
Segments: tmpSegments,
|
|
913
952
|
GenerateSlot: pReadOnly ? [] :
|
|
914
953
|
[{
|
|
@@ -1077,6 +1116,7 @@ class PictViewPromptEditor extends libPictView
|
|
|
1077
1116
|
TypeKey: tmpPrompt.TypeKey,
|
|
1078
1117
|
Title: tmpPrompt.Title + ' (copy)',
|
|
1079
1118
|
Segments: JSON.parse(JSON.stringify(tmpPrompt.Segments || {})),
|
|
1119
|
+
IncludeSegmentHeadings: tmpPrompt.IncludeSegmentHeadings !== false,
|
|
1080
1120
|
Meta: JSON.parse(JSON.stringify(tmpPrompt.Meta || {})),
|
|
1081
1121
|
Author: this.options.CurrentUser
|
|
1082
1122
|
})
|
|
@@ -1123,6 +1163,24 @@ class PictViewPromptEditor extends libPictView
|
|
|
1123
1163
|
.catch((pError) => this._toast('Segment save failed: ' + pError.message, 'error'));
|
|
1124
1164
|
}
|
|
1125
1165
|
|
|
1166
|
+
// The per-prompt "segment headings in the output" toggle: saves with the
|
|
1167
|
+
// prompt, and the compiler reads it on every assemble (preview, generate,
|
|
1168
|
+
// zip). No re-render needed - the checkbox itself holds the new state -
|
|
1169
|
+
// but an open preview re-rolls so what you see matches what you would get.
|
|
1170
|
+
setSegmentHeadings(pChecked)
|
|
1171
|
+
{
|
|
1172
|
+
let tmpPrompt = this._activePrompt();
|
|
1173
|
+
if (!tmpPrompt) { return; }
|
|
1174
|
+
tmpPrompt.IncludeSegmentHeadings = !!pChecked;
|
|
1175
|
+
this._provider.updatePrompt(tmpPrompt.Key, { IncludeSegmentHeadings: !!pChecked })
|
|
1176
|
+
.then((pSaved) =>
|
|
1177
|
+
{
|
|
1178
|
+
this._fire('onPromptSaved', pSaved);
|
|
1179
|
+
if (this._ui.Preview && this._ui.Preview.PromptKey === tmpPrompt.Key) { this.previewOnce(); }
|
|
1180
|
+
})
|
|
1181
|
+
.catch((pError) => this._toast('Save failed: ' + pError.message, 'error'));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1126
1184
|
toggleSegmentPreview(pSegmentKey)
|
|
1127
1185
|
{
|
|
1128
1186
|
let tmpPrompt = this._activePrompt();
|