openvibers/openvibe.ai Commits

Channel descriptions that read as a person wrote them: a card line of their own (#466)

OP OpenVibers committed 2 days ago · 3f7482b4bf · 2 files, +15 −4

Ask 363 (the owner: "improve the AI description so … not all so generic sounding … more human feeling and nice"; the
cards read "Streams a multi-camera, tech-robot focus with live chats and overlays."):
- live.streamer.overview now answers JSON {line, overview}: `line` is the home card's one line, what a friend who
  watches would say in one breath (40 to 90 characters, no name, starting with the thing itself, not "Streams"),
  with three examples of the voice; `overview` is the channel page's 2 to 4 sentences, concrete over general.
- The words that make every channel sound the same are named and left out (focus, content, engaging, various, a mix
  of, dive into, showcasing, vibrant, dynamic, journey, immersive, a blend of).
- The output schema gains `line` (optional); a plain-prose answer is still taken as the overview alone, so nothing
  that reads this breaks. Live takes `line` for the card next (it reshapes the overview only when there is none).

Changes

ddiff --git a/server/workflows/hooks.js b/server/workflows/hooks.js
iindex 8f8fa0c..b932355 100644
---- a/server/workflows/hooks.js
++++ b/server/workflows/hooks.js
@@@ -554,7 +554,18 @@ const POSTPROCESS = {
const overview = j && j.overview ? String(j.overview).slice(0, 2000) : (extractDescription(r.text, 2000) || r.text.slice(0, 2000));
return { overview, category: normalizeCategory(j && j.category), tags: j && Array.isArray(j.tags) ? j.tags.map(String).slice(0, 6) : [] };
},
-- 'live.streamer.overview'(r) { const overview = plainProse(r.text, 4000); return overview ? { overview } : null; },
++ // The JSON answer (2026-11-11, ask 363: lines that read as a person wrote them): the card's own line and the page's
++ // overview; an answer in plain prose is still taken as the overview alone.
++ 'live.streamer.overview'(r) {
++ const j = r.json || parseJsonLoose(r.text);
++ if (j && typeof j.overview === 'string' && j.overview.trim()) {
++ const overview = plainProse(j.overview, 4000);
++ const line = typeof j.line === 'string' ? plainProse(j.line, 140) : '';
++ return overview ? { overview, ...(line ? { line } : {}) } : null;
++ }
++ const overview = plainProse(r.text, 4000);
++ return overview ? { overview } : null;
++ },
'live.media.overview'(r) { const overview = plainProse(r.text, 2000); return overview ? { overview } : null; },
'live.stream.recap'(r) {
const out = r.json || parseJsonLoose(r.text);
ddiff --git a/server/workflows/live.js b/server/workflows/live.js
iindex 7e9196f..acdb711 100644
---- a/server/workflows/live.js
++++ b/server/workflows/live.js
@@@ -68,7 +68,7 @@ const templates = [
description: 'From ai-analysis.generateStreamerOverview.',
input_schema: { type: 'object' }, output_schema: { type: 'object' },
system_prompt: '',
-- user_prompt: 'You are writing the public description of a livestreamer\'s channel. It is shown on their channel page, in search results and on the home page\'s cards, so the first sentence must stand alone as a one-line summary of what they stream. Using only the signals below (aggregated across their streams, VODs and pastes), write 2-4 sentences of plain prose: what they stream (games, topics, activities), what keeps coming back, and the tone of the streams. Start with the streamer\'s name.\n\nRules:\n- Plain text only: no lists, bullets, headings, markdown, labels ("Handle:", "Overview:") or @handles.\n- Never guess at their private life, health or location.\n- No moderation notes, warnings or ratings.\n- Do not invent specifics the signals do not support; when the signals are thin, one or two short sentences are enough.\n\n{{ctx}}',
++ user_prompt: "You are writing the public description of a livestreamer's channel on OpenVibe. People read it on the channel page, in search results and on the home page's cards. Using only the signals below (gathered across their streams, recordings and pastes), reply ONLY with compact JSON: {\"line\":\"...\",\"overview\":\"...\"}\n\n\"line\" is the home page card's one line: what a friend who watches would say about the channel in one breath. One sentence of 40 to 90 characters, present tense, without the streamer's name (the card shows it), about THIS channel: what you actually see and hear, the projects, the running bits, the mood. Start with the thing itself, not with \"Streams\", \"A channel\" or \"Join\". Warm and plain, the way a person talks. The voice, not content to copy: \"Robots, cameras and late-night tinkering, with chat riding along.\" / \"Cozy acoustic sets and requests from a kitchen table.\" / \"Minecraft builds that turn into long talks about everything else.\"\n\n\"overview\" is 2 to 4 sentences of plain prose for the channel page: what they stream, what keeps coming back, and how the streams feel. Start with the streamer's name. Concrete beats general: name the games, tools, places and topics the signals show.\n\nWords that make every channel sound the same, so leave them out: focus, content, engaging, various, a mix of, dive into, showcasing, vibrant, dynamic, journey, immersive, a blend of, and piles of adjectives. A line that would fit a thousand channels is a line to rewrite.\n\nRules:\n- Plain text inside the JSON: no lists, markdown, labels (\"Handle:\", \"Overview:\") or @handles.\n- Never guess at their private life, health or location.\n- No moderation notes, warnings or ratings.\n- Do not invent specifics the signals do not support; when the signals are thin, keep both short and say only what is there.\n\n{{ctx}}",
default_route: 'background.legacy', owner: 'live',
},
{
@@@ -342,8 +342,8 @@ const structured = [
pastes: { type: 'array', maxItems: 50, items: { type: 'object', properties: { title: STR(300), summary: STR(600) } } },
sources: SOURCES(0, 50),
}, ['streamer']),
-- output_schema: TEXT_OUT({ overview: STR(4000) }, ['overview']),
-- steps: [{ kind: 'llm', template: 'live.streamer.overview', operation: 'summarize', output: 'text', prepare: 'live.streamer.overview', postprocess: 'live.streamer.overview', params: { max_tokens: 550 } }],
++ output_schema: TEXT_OUT({ overview: STR(4000), line: STR(140) }, ['overview']),
++ steps: [{ kind: 'llm', template: 'live.streamer.overview', operation: 'summarize', output: 'text', prepare: 'live.streamer.overview', postprocess: 'live.streamer.overview', params: { max_tokens: 650 } }],
cache_mode: 'private',
},
{