@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.
@@ -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
@@ -0,0 +1,5 @@
1
+ import { CoerceURI, Result, loadAsset } from "@adviser/cement";
2
+ export declare function loadDocs(localPath: string, fallBackUrl: CoerceURI, deps?: {
3
+ pathOps: import("@adviser/cement").PathOps;
4
+ loadAsset: typeof loadAsset;
5
+ }): Promise<Result<string>>;
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
@@ -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