@vibes.diy/prompts 0.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/LICENSE.md +232 -0
- package/README.md +29 -0
- package/catalog.d.ts +1 -0
- package/catalog.js +4 -0
- package/catalog.js.map +1 -0
- package/chat.d.ts +134 -0
- package/chat.js +2 -0
- package/chat.js.map +1 -0
- package/index.d.ts +7 -0
- package/index.js +8 -0
- package/index.js.map +1 -0
- package/json-docs.d.ts +29 -0
- package/json-docs.js +42 -0
- package/json-docs.js.map +1 -0
- package/llms/callai.json +9 -0
- package/llms/callai.txt +455 -0
- package/llms/d3.json +9 -0
- package/llms/d3.md +679 -0
- package/llms/fireproof.json +9 -0
- package/llms/fireproof.txt +451 -0
- package/llms/image-gen.json +9 -0
- package/llms/image-gen.txt +128 -0
- package/llms/three-js.json +9 -0
- package/llms/three-js.md +2232 -0
- package/llms/web-audio.json +8 -0
- package/llms/web-audio.txt +220 -0
- package/load-docs.d.ts +5 -0
- package/load-docs.js +28 -0
- package/load-docs.js.map +1 -0
- package/package.json +38 -0
- package/prompts.d.ts +36 -0
- package/prompts.js +307 -0
- package/prompts.js.map +1 -0
- package/settings.d.ts +16 -0
- package/settings.js +2 -0
- package/settings.js.map +1 -0
- package/tsconfig.json +22 -0
- package/txt-docs.d.ts +16 -0
- package/txt-docs.js +40 -0
- package/txt-docs.js.map +1 -0
- package/view-state.d.ts +17 -0
- package/view-state.js +2 -0
- package/view-state.js.map +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web-audio",
|
|
3
|
+
"label": "Web Audio API",
|
|
4
|
+
"module": "web-audio",
|
|
5
|
+
"description": "Web Audio fundamentals; echo/delay with effects in the feedback path; mic monitoring with a metronome; audio‑clock scheduling; timing design for multi‑channel drum machines and MIDI synths with accurate voice overlap.",
|
|
6
|
+
"importModule": "web-audio",
|
|
7
|
+
"importName": "WebAudioAPI"
|
|
8
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Web Audio API: Fundamentals, Echo with FX-in-Feedback, Mic Monitoring + Metronome, and Timing Architecture
|
|
2
|
+
|
|
3
|
+
Authoritative source: Issue #228 research threads — comments 3192681700, 3192696052, 3192806626.
|
|
4
|
+
|
|
5
|
+
## 1) Fundamentals and Core Nodes
|
|
6
|
+
|
|
7
|
+
- AudioContext — master interface and clock (`audioCtx.currentTime`). Resume on a user gesture.
|
|
8
|
+
- OscillatorNode — synthesis; set `type` and `frequency`.
|
|
9
|
+
- AudioBufferSourceNode — decoded-file playback; schedule with `.start(when, offset?, duration?)`.
|
|
10
|
+
- GainNode — volume control and envelopes.
|
|
11
|
+
- BiquadFilterNode — EQ/tonal shaping (`type`, `frequency`, `Q`, etc.).
|
|
12
|
+
- AnalyserNode — FFT/time-domain visualization.
|
|
13
|
+
|
|
14
|
+
Examples
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
// 1) Context (user gesture required in many browsers)
|
|
18
|
+
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
19
|
+
|
|
20
|
+
// Start/resume only in direct response to a user gesture (e.g., a Play button)
|
|
21
|
+
document.querySelector('#start-audio')?.addEventListener('click', async () => {
|
|
22
|
+
if (audioCtx.state !== 'running') await audioCtx.resume();
|
|
23
|
+
// now safe to create/start nodes
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 2) Simple tone
|
|
27
|
+
const osc = audioCtx.createOscillator();
|
|
28
|
+
osc.type = 'sine';
|
|
29
|
+
osc.frequency.value = 440;
|
|
30
|
+
osc.connect(audioCtx.destination);
|
|
31
|
+
osc.start();
|
|
32
|
+
osc.stop(audioCtx.currentTime + 1);
|
|
33
|
+
|
|
34
|
+
// 3) Load/decode and play a file
|
|
35
|
+
const buf = await fetch('/path/audio.mp3').then(r => r.arrayBuffer()).then(b => audioCtx.decodeAudioData(b));
|
|
36
|
+
const src = audioCtx.createBufferSource();
|
|
37
|
+
src.buffer = buf;
|
|
38
|
+
src.connect(audioCtx.destination);
|
|
39
|
+
src.start();
|
|
40
|
+
|
|
41
|
+
// 4) Gain and Filter in series
|
|
42
|
+
const gain = audioCtx.createGain();
|
|
43
|
+
gain.gain.value = 0.5;
|
|
44
|
+
const filter = audioCtx.createBiquadFilter();
|
|
45
|
+
filter.type = 'lowpass';
|
|
46
|
+
filter.frequency.value = 1000;
|
|
47
|
+
osc.disconnect();
|
|
48
|
+
osc.connect(filter).connect(gain).connect(audioCtx.destination);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Practical: clean up disconnected nodes; check browser support; use headphones to avoid feedback when monitoring.
|
|
52
|
+
|
|
53
|
+
## 2) Echo/Delay with Effects Inside the Feedback Loop
|
|
54
|
+
|
|
55
|
+
Graph (node names are exact):
|
|
56
|
+
|
|
57
|
+
- Dry: `source → dryGain:GainNode → destination`
|
|
58
|
+
- Wet: `source → delay:DelayNode → wetGain:GainNode → destination`
|
|
59
|
+
- Feedback loop with FX: `delay → filter:BiquadFilterNode → distortion:WaveShaperNode → reverb:ConvolverNode → feedbackGain:GainNode → delay`
|
|
60
|
+
|
|
61
|
+
Parameters to expose
|
|
62
|
+
|
|
63
|
+
- `delay.delayTime` (s), `feedbackGain.gain` (0–1, keep < 1.0)
|
|
64
|
+
- `filter.type`, `filter.frequency`
|
|
65
|
+
- `distortion.curve` (Float32Array)
|
|
66
|
+
- `convolver.buffer` (IR AudioBuffer)
|
|
67
|
+
- `wetGain.gain`, `dryGain.gain`
|
|
68
|
+
|
|
69
|
+
Notes: Prevent runaway by capping feedback below 1.0; `ConvolverNode` requires a loaded impulse response; zero-delay cycles are disallowed.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
const delay = audioCtx.createDelay(5.0);
|
|
73
|
+
const feedbackGain = audioCtx.createGain();
|
|
74
|
+
const filter = audioCtx.createBiquadFilter();
|
|
75
|
+
const distortion = audioCtx.createWaveShaper();
|
|
76
|
+
const reverb = audioCtx.createConvolver();
|
|
77
|
+
const wetGain = audioCtx.createGain();
|
|
78
|
+
const dryGain = audioCtx.createGain();
|
|
79
|
+
|
|
80
|
+
delay.delayTime.value = 0.35;
|
|
81
|
+
feedbackGain.gain.value = 0.5; // < 1.0
|
|
82
|
+
filter.type = 'lowpass';
|
|
83
|
+
filter.frequency.value = 8000;
|
|
84
|
+
// distortion.curve = yourFloat32Curve;
|
|
85
|
+
// reverb.buffer = yourImpulseResponseAudioBuffer;
|
|
86
|
+
wetGain.gain.value = 0.4;
|
|
87
|
+
dryGain.gain.value = 1.0;
|
|
88
|
+
|
|
89
|
+
// Dry and wet
|
|
90
|
+
source.connect(dryGain).connect(audioCtx.destination);
|
|
91
|
+
source.connect(delay);
|
|
92
|
+
delay.connect(wetGain).connect(audioCtx.destination);
|
|
93
|
+
|
|
94
|
+
// Feedback with FX
|
|
95
|
+
delay.connect(filter);
|
|
96
|
+
filter.connect(distortion);
|
|
97
|
+
distortion.connect(reverb);
|
|
98
|
+
reverb.connect(feedbackGain);
|
|
99
|
+
feedbackGain.connect(delay);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Helper (load IR):
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
async function loadImpulseResponse(url) {
|
|
106
|
+
const res = await fetch(url, { mode: 'cors' });
|
|
107
|
+
if (!res.ok) throw new Error(`Failed to fetch IR ${url}: ${res.status} ${res.statusText}`);
|
|
108
|
+
const ab = await res.arrayBuffer();
|
|
109
|
+
try {
|
|
110
|
+
return await audioCtx.decodeAudioData(ab);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error('decodeAudioData failed for IR', url, err);
|
|
113
|
+
throw err; // Surface decoding/CORS-related failures clearly
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 3) Microphone Monitoring + Metronome Overlay
|
|
119
|
+
|
|
120
|
+
Mic capture: request permission with `navigator.mediaDevices.getUserMedia({ audio: { echoCancellation, noiseSuppression, autoGainControl } })`. Create `MediaStreamAudioSourceNode` and route to a `GainNode` → destination.
|
|
121
|
+
|
|
122
|
+
Metronome: synthesize a short click (e.g., square/sine burst through a gain envelope). Schedule by audio clock at `AudioContext.currentTime` with lookahead.
|
|
123
|
+
|
|
124
|
+
Mix graph: `micGain + metronomeGain → master → destination`.
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const master = audioCtx.createGain();
|
|
128
|
+
master.connect(audioCtx.destination);
|
|
129
|
+
const micGain = audioCtx.createGain();
|
|
130
|
+
const metronomeGain = audioCtx.createGain();
|
|
131
|
+
micGain.connect(master);
|
|
132
|
+
metronomeGain.connect(master);
|
|
133
|
+
|
|
134
|
+
async function initMic() {
|
|
135
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: false } });
|
|
136
|
+
const micSrc = audioCtx.createMediaStreamSource(stream);
|
|
137
|
+
micSrc.connect(micGain);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function scheduleClick(atTime, downbeat = false) {
|
|
141
|
+
const osc = audioCtx.createOscillator();
|
|
142
|
+
const env = audioCtx.createGain();
|
|
143
|
+
osc.type = 'square';
|
|
144
|
+
osc.frequency.setValueAtTime(downbeat ? 2000 : 1600, atTime);
|
|
145
|
+
env.gain.setValueAtTime(0.0001, atTime);
|
|
146
|
+
env.gain.exponentialRampToValueAtTime(1.0, atTime + 0.001);
|
|
147
|
+
env.gain.exponentialRampToValueAtTime(0.0001, atTime + 0.03);
|
|
148
|
+
osc.connect(env).connect(metronomeGain);
|
|
149
|
+
osc.start(atTime);
|
|
150
|
+
osc.stop(atTime + 0.05);
|
|
151
|
+
// Cleanup to avoid accumulating nodes during long sessions
|
|
152
|
+
osc.onended = () => {
|
|
153
|
+
try { osc.disconnect(); } catch {}
|
|
154
|
+
try { env.disconnect(); } catch {}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function startMetronome({ bpm = 120, beatsPerBar = 4 } = {}) {
|
|
159
|
+
const spb = 60 / bpm; // seconds per beat
|
|
160
|
+
let next = audioCtx.currentTime + 0.1;
|
|
161
|
+
let beat = 0;
|
|
162
|
+
const lookaheadMs = 25, ahead = 0.2;
|
|
163
|
+
const id = setInterval(() => {
|
|
164
|
+
while (next < audioCtx.currentTime + ahead) {
|
|
165
|
+
scheduleClick(next, beat % beatsPerBar === 0);
|
|
166
|
+
next += spb; beat = (beat + 1) % beatsPerBar;
|
|
167
|
+
}
|
|
168
|
+
}, lookaheadMs);
|
|
169
|
+
return () => clearInterval(id);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Latency and safety: start/resume on user gesture; clean up per-tick nodes after `ended` to prevent buildup in long-running metronomes; use headphones while monitoring; mobile devices have higher base latency.
|
|
174
|
+
|
|
175
|
+
## 4) Time Synchronization and Scheduling Model
|
|
176
|
+
|
|
177
|
+
Clocks/time domains
|
|
178
|
+
|
|
179
|
+
- Master: `AudioContext.currentTime` — sample-accurate; schedule everything on this timeline.
|
|
180
|
+
- UI/high-res: `performance.now()` — for UI timers and Web MIDI timestamps.
|
|
181
|
+
- Mapping: capture `(tPerf0 = performance.now(), tAudio0 = audioCtx.currentTime)`, convert MIDI/perf timestamps with `tAudio = tAudio0 + (timeStamp - tPerf0)/1000`.
|
|
182
|
+
- Hints: `audioCtx.baseLatency`, `audioCtx.getOutputTimestamp?.()` — estimate DAC/output delay if aligning to “heard” time.
|
|
183
|
+
|
|
184
|
+
Scheduling primitives
|
|
185
|
+
|
|
186
|
+
- `AudioBufferSourceNode.start(when, offset?, duration?)` for one-shots/loops.
|
|
187
|
+
- `AudioParam` automation (`setValueAtTime`, `linearRampToValueAtTime`, `setTargetAtTime`, `setValueCurveAtTime`).
|
|
188
|
+
- Avoid `requestAnimationFrame`/`setTimeout` for timing; use an AudioWorklet for custom DSP/tight jitter when needed.
|
|
189
|
+
|
|
190
|
+
Tempo transport and lookahead
|
|
191
|
+
|
|
192
|
+
- Tempo mapping: `secondsPerBeat = 60 / bpm`; compute bars:beats:ticks → seconds on the audio clock (choose PPQ, e.g., 480/960).
|
|
193
|
+
- Lookahead window: maintain ~50–200 ms rolling schedule; enqueue with absolute `when` times in audio seconds.
|
|
194
|
+
|
|
195
|
+
Multi‑channel drum machine
|
|
196
|
+
|
|
197
|
+
- Pre‑decode all samples; never decode on hit.
|
|
198
|
+
- Per hit: create a fresh `AudioBufferSourceNode` and call `.start(when)`.
|
|
199
|
+
- For phase‑aligned layers (kick+clap, etc.), schedule all sources with the same `when` to guarantee sample‑accurate overlap.
|
|
200
|
+
- Routing: per‑track `GainNode`/optional FX → master bus; allow overlapping retriggers; compute flams as small `when` offsets.
|
|
201
|
+
- Pattern changes: compute the next bar boundary on the audio clock and enqueue new pattern hits relative to that time.
|
|
202
|
+
|
|
203
|
+
MIDI synth playback
|
|
204
|
+
|
|
205
|
+
- Live input: map `MIDIMessageEvent.timeStamp` (perf.now domain) → audio clock as above; buffer a short lookahead (5–20 ms) to reduce jitter.
|
|
206
|
+
- SMF playback: convert PPQ ticks using the tempo map; schedule noteOn/noteOff separately; sustain (CC64) defers noteOff until pedal release.
|
|
207
|
+
- Voice management: one voice per active note; allow overlapping envelopes; define voice‑steal policy if a polyphony cap is hit.
|
|
208
|
+
|
|
209
|
+
External sync and drift
|
|
210
|
+
|
|
211
|
+
- For MIDI Clock/MTC, derive BPM/phase from incoming ticks, convert to audio time, and drive the transport. Correct small phase error between beats with bounded micro‑nudges—avoid discontinuities.
|
|
212
|
+
|
|
213
|
+
## 5) Practical Notes
|
|
214
|
+
|
|
215
|
+
- User gesture required to start/resume `AudioContext` and to access the mic.
|
|
216
|
+
- Convolver IRs: host with CORS if cross‑origin; decode before use.
|
|
217
|
+
- Latency budget: device `baseLatency` + your lookahead + any Worklet buffering.
|
|
218
|
+
- Headphones recommended for monitoring to avoid acoustic feedback.
|
|
219
|
+
|
|
220
|
+
— End —
|
package/load-docs.d.ts
ADDED
package/load-docs.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { pathOps, loadAsset } from "@adviser/cement";
|
|
2
|
+
function needLocalLlmPath(localPath, pathOpsRef = pathOps) {
|
|
3
|
+
const dirPart = pathOpsRef.dirname(localPath).replace(/^[./]+/, "");
|
|
4
|
+
if (dirPart.startsWith("llms") || localPath.startsWith("llms/")) {
|
|
5
|
+
return "";
|
|
6
|
+
}
|
|
7
|
+
return "llms";
|
|
8
|
+
}
|
|
9
|
+
export async function loadDocs(localPath, fallBackUrl, deps = { pathOps, loadAsset }) {
|
|
10
|
+
return deps.loadAsset(localPath, {
|
|
11
|
+
fallBackUrl,
|
|
12
|
+
basePath: () => import.meta.url,
|
|
13
|
+
pathCleaner: (base, localPath, mode) => {
|
|
14
|
+
switch (mode) {
|
|
15
|
+
case "normal": {
|
|
16
|
+
const llmPath = needLocalLlmPath(localPath, deps.pathOps);
|
|
17
|
+
if (llmPath === "") {
|
|
18
|
+
return deps.pathOps.join(base, localPath);
|
|
19
|
+
}
|
|
20
|
+
return deps.pathOps.join(base, llmPath, localPath);
|
|
21
|
+
}
|
|
22
|
+
case "fallback":
|
|
23
|
+
return localPath;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=load-docs.js.map
|
package/load-docs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-docs.js","sourceRoot":"","sources":["../jsr/load-docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,OAAO,EAAU,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAExE,SAAS,gBAAgB,CAAC,SAAiB,EAAE,UAAU,GAAG,OAAO,EAAU;IACzE,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACf;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,SAAiB,EACjB,WAAsB,EACtB,IAAI,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,EACJ;IACzB,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE;QAC/B,WAAW;QACX,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,IAAI,CAAC,GAAG;QAC/B,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;YACtC,QAAQ,IAAI,EAAE,CAAC;gBACb,KAAK,QAAQ,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC1D,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;wBAEnB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;oBAC5C,CAAC;oBACD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;gBACrD,CAAC;gBACD,KAAK,UAAU;oBACb,OAAO,SAAS,CAAC;YACrB,CAAC;QAAA,CACF;KACF,CAAC,CAAC;AAAA,CACJ"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibes.diy/prompts",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ai",
|
|
8
|
+
"dom",
|
|
9
|
+
"micro-app",
|
|
10
|
+
"generator",
|
|
11
|
+
"web",
|
|
12
|
+
"esm",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"contributors": [
|
|
16
|
+
"J Chris Anderson",
|
|
17
|
+
"Meno Abels"
|
|
18
|
+
],
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@adviser/cement": "^0.4.30",
|
|
22
|
+
"@fireproof/core-types-base": "^0.23.13",
|
|
23
|
+
"call-ai": "^0.1.1",
|
|
24
|
+
"use-vibes": "^0.1.1"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@fireproof/core-cli": "^0.23.13",
|
|
31
|
+
"typescript": "^5.8.2",
|
|
32
|
+
"typescript-eslint": "^8.36.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "core-cli tsc",
|
|
36
|
+
"test": "vitest run"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/prompts.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Mocks } from "call-ai";
|
|
2
|
+
import type { HistoryMessage, UserSettings } from "./settings.js";
|
|
3
|
+
import { CoerceURI } from "@adviser/cement";
|
|
4
|
+
import { LlmCatalogEntry } from "./json-docs.js";
|
|
5
|
+
export declare function defaultCodingModel(): Promise<string>;
|
|
6
|
+
export declare function normalizeModelId(id: unknown): string | undefined;
|
|
7
|
+
export declare function isPermittedModelId(id: unknown): id is string;
|
|
8
|
+
export declare function resolveEffectiveModel(settingsDoc?: {
|
|
9
|
+
model?: string;
|
|
10
|
+
}, vibeDoc?: {
|
|
11
|
+
selectedModel?: string;
|
|
12
|
+
}): Promise<string>;
|
|
13
|
+
export interface LlmSelectionDecisions {
|
|
14
|
+
selected: string[];
|
|
15
|
+
instructionalText: boolean;
|
|
16
|
+
demoData: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface LlmSelectionOptions {
|
|
19
|
+
readonly appMode?: "test" | "production";
|
|
20
|
+
readonly callAiEndpoint?: CoerceURI;
|
|
21
|
+
readonly fallBackUrl?: CoerceURI;
|
|
22
|
+
readonly getAuthToken?: () => Promise<string>;
|
|
23
|
+
readonly mock?: Mocks;
|
|
24
|
+
}
|
|
25
|
+
export type LlmSelectionWithFallbackUrl = Omit<Omit<LlmSelectionOptions, "fallBackUrl">, "callAiEndpoint"> & {
|
|
26
|
+
readonly fallBackUrl: CoerceURI;
|
|
27
|
+
readonly callAiEndpoint?: CoerceURI;
|
|
28
|
+
};
|
|
29
|
+
export declare function selectLlmsAndOptions(model: string, userPrompt: string, history: HistoryMessage[], iopts: LlmSelectionOptions): Promise<LlmSelectionDecisions>;
|
|
30
|
+
export declare function generateImportStatements(llms: LlmCatalogEntry[]): string;
|
|
31
|
+
export declare function makeBaseSystemPrompt(model: string, sessionDoc: Partial<UserSettings> & LlmSelectionOptions, onAiDecisions?: (decisions: {
|
|
32
|
+
selected: string[];
|
|
33
|
+
}) => void): Promise<string>;
|
|
34
|
+
export declare const RESPONSE_FORMAT: {
|
|
35
|
+
structure: string[];
|
|
36
|
+
};
|
package/prompts.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { callAI } from "call-ai";
|
|
2
|
+
import { Lazy, runtimeFn, URI } from "@adviser/cement";
|
|
3
|
+
import { getJsonDocs, getLlmCatalog, getLlmCatalogNames, } from "./json-docs.js";
|
|
4
|
+
import { getDefaultDependencies } from "./catalog.js";
|
|
5
|
+
import { getTexts } from "./txt-docs.js";
|
|
6
|
+
export async function defaultCodingModel() {
|
|
7
|
+
return "anthropic/claude-sonnet-4";
|
|
8
|
+
}
|
|
9
|
+
function normalizeModelIdInternal(id) {
|
|
10
|
+
if (typeof id !== "string")
|
|
11
|
+
return undefined;
|
|
12
|
+
const trimmed = id.trim();
|
|
13
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
14
|
+
}
|
|
15
|
+
export function normalizeModelId(id) {
|
|
16
|
+
return normalizeModelIdInternal(id);
|
|
17
|
+
}
|
|
18
|
+
export function isPermittedModelId(id) {
|
|
19
|
+
return typeof normalizeModelIdInternal(id) === "string";
|
|
20
|
+
}
|
|
21
|
+
export async function resolveEffectiveModel(settingsDoc, vibeDoc) {
|
|
22
|
+
const sessionChoice = normalizeModelIdInternal(vibeDoc?.selectedModel);
|
|
23
|
+
if (sessionChoice)
|
|
24
|
+
return sessionChoice;
|
|
25
|
+
const globalChoice = normalizeModelIdInternal(settingsDoc?.model);
|
|
26
|
+
if (globalChoice)
|
|
27
|
+
return globalChoice;
|
|
28
|
+
return defaultCodingModel();
|
|
29
|
+
}
|
|
30
|
+
function escapeRegExp(str) {
|
|
31
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
const llmImportRegexes = Lazy((fallBackUrl) => {
|
|
34
|
+
return getJsonDocs(fallBackUrl).then((docs) => Object.values(docs)
|
|
35
|
+
.map((d) => d.obj)
|
|
36
|
+
.filter((l) => l.importModule && l.importName)
|
|
37
|
+
.map((l) => {
|
|
38
|
+
const mod = escapeRegExp(l.importModule);
|
|
39
|
+
const name = escapeRegExp(l.importName);
|
|
40
|
+
const importType = l.importType || "named";
|
|
41
|
+
return {
|
|
42
|
+
name: l.name,
|
|
43
|
+
named: new RegExp(`import\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['\\"]${mod}['\\"]`),
|
|
44
|
+
def: new RegExp(`import\\s+${name}\\s+from\\s*['\\"]${mod}['\\"]`),
|
|
45
|
+
namespace: new RegExp(`import\\s*\\*\\s*as\\s+${name}\\s+from\\s*['\\"]${mod}['\\"]`),
|
|
46
|
+
importType,
|
|
47
|
+
};
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
async function detectModulesInHistory(history, opts) {
|
|
51
|
+
const detected = new Set();
|
|
52
|
+
if (!Array.isArray(history))
|
|
53
|
+
return detected;
|
|
54
|
+
for (const msg of history) {
|
|
55
|
+
const content = msg?.content || "";
|
|
56
|
+
if (!content || typeof content !== "string")
|
|
57
|
+
continue;
|
|
58
|
+
for (const { name, named, def, namespace } of await llmImportRegexes(opts.fallBackUrl)) {
|
|
59
|
+
if (named.test(content) || def.test(content) || namespace.test(content)) {
|
|
60
|
+
detected.add(name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return detected;
|
|
65
|
+
}
|
|
66
|
+
const warnOnce = Lazy(() => console.warn("auth_token is not support on node"));
|
|
67
|
+
function defaultGetAuthToken(fn) {
|
|
68
|
+
if (typeof fn === "function") {
|
|
69
|
+
return () => fn();
|
|
70
|
+
}
|
|
71
|
+
const rn = runtimeFn();
|
|
72
|
+
if (rn.isBrowser) {
|
|
73
|
+
return () => Promise.resolve(localStorage.getItem("auth_token") || "");
|
|
74
|
+
}
|
|
75
|
+
return () => {
|
|
76
|
+
warnOnce();
|
|
77
|
+
return Promise.resolve("Unsupported.JWT-Token");
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function sleepReject(ms) {
|
|
81
|
+
return new Promise((_, rj) => setTimeout(rj, ms));
|
|
82
|
+
}
|
|
83
|
+
export async function selectLlmsAndOptions(model, userPrompt, history, iopts) {
|
|
84
|
+
const opts = {
|
|
85
|
+
appMode: "production",
|
|
86
|
+
...iopts,
|
|
87
|
+
callAiEndpoint: iopts.callAiEndpoint ? iopts.callAiEndpoint : undefined,
|
|
88
|
+
fallBackUrl: URI.from(iopts.fallBackUrl ?? "https://esm.sh/use-vibes/prompt-catalog/llms").toString(),
|
|
89
|
+
getAuthToken: defaultGetAuthToken(iopts.getAuthToken),
|
|
90
|
+
};
|
|
91
|
+
const llmsCatalog = await getLlmCatalog(opts.fallBackUrl);
|
|
92
|
+
const catalog = llmsCatalog.map((l) => ({
|
|
93
|
+
name: l.name,
|
|
94
|
+
description: l.description || "",
|
|
95
|
+
}));
|
|
96
|
+
const payload = {
|
|
97
|
+
catalog,
|
|
98
|
+
userPrompt: userPrompt || "",
|
|
99
|
+
history: history || [],
|
|
100
|
+
};
|
|
101
|
+
const messages = [
|
|
102
|
+
{
|
|
103
|
+
role: "system",
|
|
104
|
+
content: 'You select which library modules from a catalog should be included AND whether to include instructional UI text and a demo-data button. First analyze if the user prompt describes specific look & feel requirements. For instructional text and demo data: include them only when asked for. Read the JSON payload and return JSON with properties: "selected" (array of catalog "name" strings), "instructionalText" (boolean), and "demoData" (boolean). Only choose modules from the catalog. Include any libraries already used in history. Respond with JSON only.',
|
|
105
|
+
},
|
|
106
|
+
{ role: "user", content: JSON.stringify(payload) },
|
|
107
|
+
];
|
|
108
|
+
const options = {
|
|
109
|
+
chatUrl: opts.callAiEndpoint
|
|
110
|
+
? opts.callAiEndpoint.toString().replace(/\/+$/, "")
|
|
111
|
+
: undefined,
|
|
112
|
+
apiKey: "sk-vibes-proxy-managed",
|
|
113
|
+
model,
|
|
114
|
+
schema: {
|
|
115
|
+
name: "module_and_options_selection",
|
|
116
|
+
properties: {
|
|
117
|
+
selected: { type: "array", items: { type: "string" } },
|
|
118
|
+
instructionalText: { type: "boolean" },
|
|
119
|
+
demoData: { type: "boolean" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
max_tokens: 2000,
|
|
123
|
+
headers: {
|
|
124
|
+
"HTTP-Referer": "https://vibes.diy",
|
|
125
|
+
"X-Title": "Vibes DIY",
|
|
126
|
+
"X-VIBES-Token": await opts.getAuthToken?.(),
|
|
127
|
+
},
|
|
128
|
+
mock: opts.mock,
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
const withTimeout = (p, ms = 4000) => Promise.race([
|
|
132
|
+
sleepReject(ms).then((val) => {
|
|
133
|
+
console.warn("Module/options selection: API call timed out after", ms, "ms");
|
|
134
|
+
return val;
|
|
135
|
+
}),
|
|
136
|
+
p
|
|
137
|
+
.then((val) => {
|
|
138
|
+
return val;
|
|
139
|
+
})
|
|
140
|
+
.catch((err) => {
|
|
141
|
+
console.warn("Module/options selection: API call failed with error:", err);
|
|
142
|
+
throw err;
|
|
143
|
+
}),
|
|
144
|
+
]);
|
|
145
|
+
const raw = (await withTimeout(callCallAI(options)(messages, options)));
|
|
146
|
+
if (raw === undefined || raw === null) {
|
|
147
|
+
console.warn("Module/options selection: call-ai returned undefined with schema present");
|
|
148
|
+
console.warn("This is a known issue in the prompts package environment");
|
|
149
|
+
return { selected: [], instructionalText: true, demoData: true };
|
|
150
|
+
}
|
|
151
|
+
const parsed = JSON.parse(raw) ?? {};
|
|
152
|
+
const selected = Array.isArray(parsed?.selected)
|
|
153
|
+
? parsed.selected.filter((v) => typeof v === "string")
|
|
154
|
+
: [];
|
|
155
|
+
const instructionalText = typeof parsed?.instructionalText === "boolean"
|
|
156
|
+
? parsed.instructionalText
|
|
157
|
+
: true;
|
|
158
|
+
const demoData = typeof parsed?.demoData === "boolean" ? parsed.demoData : true;
|
|
159
|
+
return { selected, instructionalText, demoData };
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
console.warn("Module/options selection call failed:", err);
|
|
163
|
+
return { selected: [], instructionalText: true, demoData: true };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function callCallAI(option) {
|
|
167
|
+
return option.mock?.callAI || callAI;
|
|
168
|
+
}
|
|
169
|
+
export function generateImportStatements(llms) {
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
return llms
|
|
172
|
+
.slice()
|
|
173
|
+
.sort((a, b) => a.importModule.localeCompare(b.importModule))
|
|
174
|
+
.filter((l) => l.importModule && l.importName)
|
|
175
|
+
.filter((l) => {
|
|
176
|
+
const key = `${l.importModule}:${l.importName}`;
|
|
177
|
+
if (seen.has(key))
|
|
178
|
+
return false;
|
|
179
|
+
seen.add(key);
|
|
180
|
+
return true;
|
|
181
|
+
})
|
|
182
|
+
.map((l) => {
|
|
183
|
+
const importType = l.importType || "named";
|
|
184
|
+
switch (importType) {
|
|
185
|
+
case "namespace":
|
|
186
|
+
return `\nimport * as ${l.importName} from "${l.importModule}"`;
|
|
187
|
+
case "default":
|
|
188
|
+
return `\nimport ${l.importName} from "${l.importModule}"`;
|
|
189
|
+
case "named":
|
|
190
|
+
default:
|
|
191
|
+
return `\nimport { ${l.importName} } from "${l.importModule}"`;
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.join("");
|
|
195
|
+
}
|
|
196
|
+
export async function makeBaseSystemPrompt(model, sessionDoc, onAiDecisions) {
|
|
197
|
+
const userPrompt = sessionDoc?.userPrompt || "";
|
|
198
|
+
const history = Array.isArray(sessionDoc?.history)
|
|
199
|
+
? sessionDoc.history
|
|
200
|
+
: [];
|
|
201
|
+
const useOverride = !!sessionDoc?.dependenciesUserOverride;
|
|
202
|
+
let selectedNames = [];
|
|
203
|
+
let includeInstructional = true;
|
|
204
|
+
let includeDemoData = true;
|
|
205
|
+
const llmsCatalog = await getLlmCatalog(sessionDoc.fallBackUrl);
|
|
206
|
+
const llmsCatalogNames = await getLlmCatalogNames(sessionDoc.fallBackUrl);
|
|
207
|
+
if (useOverride && Array.isArray(sessionDoc?.dependencies)) {
|
|
208
|
+
selectedNames = sessionDoc.dependencies
|
|
209
|
+
.filter((v) => typeof v === "string")
|
|
210
|
+
.filter((name) => llmsCatalogNames.has(name));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const decisions = await selectLlmsAndOptions(model, userPrompt, history, sessionDoc);
|
|
214
|
+
includeInstructional = decisions.instructionalText;
|
|
215
|
+
includeDemoData = decisions.demoData;
|
|
216
|
+
const detected = await detectModulesInHistory(history, sessionDoc);
|
|
217
|
+
const finalNames = new Set([...decisions.selected, ...detected]);
|
|
218
|
+
selectedNames = Array.from(finalNames);
|
|
219
|
+
if (selectedNames.length === 0)
|
|
220
|
+
selectedNames = [...(await getDefaultDependencies())];
|
|
221
|
+
onAiDecisions?.({ selected: selectedNames });
|
|
222
|
+
}
|
|
223
|
+
if (typeof sessionDoc?.instructionalTextOverride === "boolean") {
|
|
224
|
+
includeInstructional = sessionDoc.instructionalTextOverride;
|
|
225
|
+
}
|
|
226
|
+
if (typeof sessionDoc?.demoDataOverride === "boolean") {
|
|
227
|
+
includeDemoData = sessionDoc.demoDataOverride;
|
|
228
|
+
}
|
|
229
|
+
const chosenLlms = llmsCatalog.filter((l) => selectedNames.includes(l.name));
|
|
230
|
+
let concatenatedLlmsTxt = "";
|
|
231
|
+
for (const llm of chosenLlms) {
|
|
232
|
+
const text = await getTexts(llm.name, sessionDoc.fallBackUrl);
|
|
233
|
+
if (!text) {
|
|
234
|
+
console.warn("Failed to load raw LLM text for:", llm.name, sessionDoc.fallBackUrl);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
concatenatedLlmsTxt += `
|
|
238
|
+
<${llm.label}-docs>
|
|
239
|
+
${text || ""}
|
|
240
|
+
</${llm.label}-docs>
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
const defaultStylePrompt = `Create a UI theme inspired by the Memphis Group and Studio Alchimia from the 1980s. Incorporate bold, playful geometric shapes (squiggles, triangles, circles), vibrant primary colors (red, blue, yellow) with contrasting pastels (pink, mint, lavender), and asymmetrical layouts. Use quirky patterns like polka dots, zigzags, and terrazzo textures. Ensure a retro-futuristic vibe with a mix of matte and glossy finishes, evoking a whimsical yet functional design. Secretly name the theme 'Memphis Alchemy' to reflect its roots in Ettore Sotsass’s vision and global 1980s influences. Make sure the app background has some kind of charming patterned background using memphis styled dots or squiggly lines. Use thick "neo-brutalism" style borders for style to enhance legibility. Make sure to retain high contrast in your use of colors. Light background are better than dark ones. Use these colors: #70d6ff #ff70a6 #ff9770 #ffd670 #e9ff70 #242424 #ffffff Never use white text.`;
|
|
244
|
+
const stylePrompt = sessionDoc?.stylePrompt || defaultStylePrompt;
|
|
245
|
+
const instructionalLine = includeInstructional
|
|
246
|
+
? "- In the UI, include a vivid description of the app's purpose and detailed instructions how to use it, in italic text.\n"
|
|
247
|
+
: "";
|
|
248
|
+
const demoDataLines = includeDemoData
|
|
249
|
+
? `- If your app has a function that uses callAI with a schema to save data, include a Demo Data button that calls that function with an example prompt. Don't write an extra function, use real app code so the data illustrates what it looks like to use the app.\n- Never have have an instance of callAI that is only used to generate demo data, always use the same calls that are triggered by user actions in the app.\n`
|
|
250
|
+
: "";
|
|
251
|
+
return `
|
|
252
|
+
You are an AI assistant tasked with creating React components. You should create components that:
|
|
253
|
+
- Use modern React practices and follow the rules of hooks
|
|
254
|
+
- Don't use any TypeScript, just use JavaScript
|
|
255
|
+
- Use Tailwind CSS for mobile-first accessible styling
|
|
256
|
+
- Don't use words from the style prompt in your copy: ${stylePrompt}
|
|
257
|
+
- For dynamic components, like autocomplete, don't use external libraries, implement your own
|
|
258
|
+
- Avoid using external libraries unless they are essential for the component to function
|
|
259
|
+
- Always import the libraries you need at the top of the file
|
|
260
|
+
- Use Fireproof for data persistence
|
|
261
|
+
- Use \`callAI\` to fetch AI (set \`stream: true\` to enable streaming), use Structured JSON Outputs like this: \`callAI(prompt, { schema: { properties: { todos: { type: 'array', items: { type: 'string' } } } } })\` and save final responses as individual Fireproof documents.
|
|
262
|
+
- For file uploads use drag and drop and store using the \`doc._files\` API
|
|
263
|
+
- Don't try to generate png or base64 data, use placeholder image APIs instead, like https://picsum.photos/400 where 400 is the square size
|
|
264
|
+
- Consider and potentially reuse/extend code from previous responses if relevant
|
|
265
|
+
- Always output the full component code, keep the explanation short and concise
|
|
266
|
+
- Never also output a small snippet to change, just the full component code
|
|
267
|
+
- Keep your component file as short as possible for fast updates
|
|
268
|
+
- Keep the database name stable as you edit the code
|
|
269
|
+
- The system can send you crash reports, fix them by simplifying the affected code
|
|
270
|
+
- If you get missing block errors, change the database name to a new name
|
|
271
|
+
- List data items on the main page of your app so users don't have to hunt for them
|
|
272
|
+
- If you save data, make sure it is browseable in the app, eg lists should be clickable for more details
|
|
273
|
+
${instructionalLine}${demoDataLines}
|
|
274
|
+
|
|
275
|
+
${concatenatedLlmsTxt}
|
|
276
|
+
|
|
277
|
+
## ImgGen Component
|
|
278
|
+
|
|
279
|
+
You should use this component in all cases where you need to generate or edit images. It is a React component that provides a UI for image generation and editing. Make sure to pass the database prop to the component. If you generate images, use a live query to list them (with type 'image') in the UI. The best usage is to save a document with a string field called \`prompt\` (which is sent to the generator) and an optional \`doc._files.original\` image and pass the \`doc._id\` to the component via the \`_id\` prop. It will handle the rest.
|
|
280
|
+
|
|
281
|
+
${userPrompt
|
|
282
|
+
? `${userPrompt}
|
|
283
|
+
|
|
284
|
+
`
|
|
285
|
+
: ""}IMPORTANT: You are working in one JavaScript file, use tailwind classes for styling. Remember to use brackets like bg-[#242424] for custom colors.
|
|
286
|
+
|
|
287
|
+
Provide a title and brief explanation followed by the component code. The component should demonstrate proper Fireproof integration with real-time updates and proper data persistence. Follow it with a short description of the app's purpose and instructions how to use it (with occasional bold or italic for emphasis). Then suggest some additional features that could be added to the app.
|
|
288
|
+
|
|
289
|
+
Begin the component with the import statements. Use react and the following libraries:
|
|
290
|
+
|
|
291
|
+
\`\`\`js
|
|
292
|
+
import React, { ... } from "react"${generateImportStatements(chosenLlms)}
|
|
293
|
+
|
|
294
|
+
// other imports only when requested
|
|
295
|
+
\`\`\`
|
|
296
|
+
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
export const RESPONSE_FORMAT = {
|
|
300
|
+
structure: [
|
|
301
|
+
"Brief explanation",
|
|
302
|
+
"Component code with proper Fireproof integration",
|
|
303
|
+
"Real-time updates",
|
|
304
|
+
"Data persistence",
|
|
305
|
+
],
|
|
306
|
+
};
|
|
307
|
+
//# sourceMappingURL=prompts.js.map
|