openvibers/openvibe.ai Commits

Let the Space builder prompt carry the OpenVibe component registry (#387)

OP OpenVibers committed 4 days ago · 5fd137763b · 3 files, +18 −5

Changes

ddiff --git a/README.md b/README.md
iindex 308d57a..99c6e53 100644
---- a/README.md
++++ b/README.md
@@@ -844,9 +844,11 @@ through an OpenAI-compatible listener and registers it on the existing endpoint-
is a space's own AI (`OV.ai.ask` in the space SDK): the maker's instructions under the platform's rules, on route
`space.ask` (an alias of `default.chat`), on behalf of the visitor, so it counts against their daily allowance.
-- `overview.source` says which (`local_model`, `paid`, `extractive`), and `gaps` lists what is missing (no local
-- model, no speech-to-text, no budget). The output is not cached: callers keep it. Live's grant names
-- `media.analyze`.
++ The builder's prompt can carry OpenVibe.Design's public `design.component@1` registry in a `components` input
++ (Space reads and caches it): the definitions are then the interface vocabulary and the answer marks each major
++ part with `data-ov-component`. `overview.source` says which (`local_model`, `paid`, `extractive`), and `gaps`
++ lists what is missing (no local model, no speech-to-text, no budget). The output is not cached: callers keep it.
++ Live's grant names `media.analyze`.
- **Direct operations** — `ai.chat|generate|summarize|classify|extract|enrich|embed|image`.
## MegaSource research
ddiff --git a/server/workflows/space.js b/server/workflows/space.js
iindex 4df882c..065246a 100644
---- a/server/workflows/space.js
++++ b/server/workflows/space.js
@@@ -60,6 +60,10 @@ const templates = [
SDK_REFERENCE,
FUNCTIONS_REFERENCE,
'',
++ '{{#components}}The OpenVibe component registry (design.component@1) is your interface vocabulary. Build the interface from these parts, keep their anatomy, states and keyboard behaviour, and mark each major part with data-ov-component="<name>":',
++ '{{components}}',
++ '{{/components}}',
++ '',
'How you build:',
'- Prefer ONE self-contained index.html with inline <style> and <script>. Use more files only when asked; never more than 6, and index.html is always one of them.',
'- Make it work completely: real logic, no placeholders, no "TODO", no lorem ipsum. Handle loading, empty and error states, and the signed-out visitor (show what works without signing in and a clear "Sign in with OpenVibe" button for the rest).',
@@@ -99,7 +103,7 @@ const workflows = [
{
key: 'space.app.build', name: 'Space: build or change a space', namespace: 'space',
description: 'From what a person asks for (and the current files when changing a space): name, description, AI instructions, the SDK features used, and the files.',
-- input_schema: OBJ({ prompt: STR(4000), name: STR(40), current: ARR(FILE, MAX_FILES) }, ['prompt']),
++ input_schema: OBJ({ prompt: STR(4000), name: STR(40), current: ARR(FILE, MAX_FILES), components: STR(20000) }, ['prompt']),
output_schema: OBJ({ name: STR(40), description: STR(400), instructions: STR(4000), capabilities: ARR({ type: 'string', enum: CAPABILITIES }, 4), files: ARR(FILE, MAX_FILES) }),
steps: [{ kind: 'llm', template: 'space.app.build', operation: 'generate', output: 'json', prepare: 'space.app.build', postprocess: 'space.app.build', params: { max_tokens: 16000, timeout_ms: 180000 } }],
default_route: 'space.build', cache_mode: 'none', metadata: { seed: true },
@@@ -155,7 +159,7 @@ const uncapped = (schema) => JSON.parse(JSON.stringify(schema, (k, v) => (k ===
const PREPARE = {
'space.app.build'(input) {
const current = (input.current || []).map((f) => `--- ${f.path} ---\n${f.content}`).join('\n\n');
-- return { vars: { prompt: input.prompt, name: input.name || '', current }, jsonSchema: uncapped(workflows.find((w) => w.key === 'space.app.build').output_schema) };
++ return { vars: { prompt: input.prompt, name: input.name || '', current, components: input.components || '' }, jsonSchema: uncapped(workflows.find((w) => w.key === 'space.app.build').output_schema) };
},
};
// A JSON answer that came back as text (a fenced block, or prose around the object).
ddiff --git a/test/space-workflows.test.js b/test/space-workflows.test.js
iindex eea1494..a4509da 100644
---- a/test/space-workflows.test.js
++++ b/test/space-workflows.test.js
@@@ -23,6 +23,13 @@ t.test('the builder\'s prompt: the SDK reference, and the current files when cha
assert.match(sys, /OV\.fn\(name, body\)/);
assert.match(sys, /never request\.user or request\.ov/, 'the visitor is on ov, not on the request');
assert.ok(!/There is no server code/.test(sys));
++ assert.match(sys, /design\.component@1/, 'the registry is named as the interface vocabulary');
++ const withComponents = render(sys, { components: 'sheet — A modal task surface. Web: kit.sheet()' });
++ assert.match(withComponents, /mark each major part with data-ov-component/);
++ assert.match(withComponents, /sheet — A modal task surface/);
++ assert.ok(!/\{\{#components\}\}/.test(withComponents), 'the section is rendered away');
++ const prepWithRegistry = PREPARE['space.app.build']({ prompt: 'a pizza poll', components: 'sheet — A modal task surface.' });
++ assert.strictEqual(prepWithRegistry.vars.components, 'sheet — A modal task surface.');
const prep = PREPARE['space.app.build']({ prompt: 'a poll about pizza' });
assert.ok(prep.jsonSchema && prep.jsonSchema.properties.files && !/maxLength|maxItems/.test(JSON.stringify(prep.jsonSchema)), 'the model answers in the uncapped, non-strict schema');
const fresh = render(tpl('space.app.build').user_prompt, prep.vars);