@fusedashlabs/ui9000-workspace
Version:
UI9000 workspace MCP. One tool, show_workspace, draws a chart from your table. Set TYPESAFE_API_KEY to let Jev choose the chart.
100 lines (99 loc) • 4.12 kB
JavaScript
/**
* Jev question pack. The options are chart ids from chart-roles, never free text.
*
* Step 1 asks which class fits. Step 2 asks which drawing inside that class fits.
*/
import { chartFamiliesFor } from '../catalog/discuss-chart.js';
import { chartRole } from '../catalog/chart-roles.js';
import { intentFrom } from '../spec/intent.js';
export const JEV_MODEL = 'jev-latest';
const NAMED_CHART = {
type: 'noul',
instructions: 'The utterance names a specific chart drawing, such as line, bar, pie, or map.',
};
/** Step 1. One option per eligible family: its main chart, described by the family question. */
export function classChoiceRequest(input) {
const families = chartFamiliesFor(input.profile, input.columns, intentFrom(input.intent));
const criteria = {};
for (const family of families) {
if (!criteria[family.main])
criteria[family.main] = family.question;
}
if (Object.keys(criteria).length === 0) {
throw new Error('No chart family matches this profile, so Jev has no options.');
}
return {
state: jevState(input),
model: JEV_MODEL,
questions: {
chart: {
type: 'choice',
instructions: 'Which of these classes fits the columns and the utterance? Use only these options.',
criteria,
},
...(input.askNamedChart ? { named_chart: NAMED_CHART } : {}),
},
};
}
/** Step 2. Only the drawings of the class Jev already chose. */
export function markChoiceRequest(input) {
const criteria = {};
criteria[input.family.main] = input.family.question;
for (const alt of input.family.alternatives) {
const role = chartRole(alt.id);
const base = role ? `${role.role} ${role.data}` : input.family.question;
criteria[alt.id] = `${base} ${alt.when}`;
}
return {
state: jevState(input),
model: JEV_MODEL,
questions: {
chart: {
type: 'choice',
instructions: 'Which of these drawings of the chosen class fits best? Use only these options.',
criteria,
},
},
};
}
export const JEV_MIN_CONFIDENCE = 0.5;
export const JEV_MIN_LEAD = 0.15;
/** Trust only an id we offered, and only when confidence and lead both clear. */
export function acceptReading(response, allowed, minConfidence = JEV_MIN_CONFIDENCE, minLead = JEV_MIN_LEAD) {
const chart = response.answers?.chart;
const allowedSet = new Set(allowed);
const confidence = typeof chart?.confidence === 'number' ? chart.confidence : 0;
const choice = chart?.choice ?? '';
const top = allowedSet.has(choice) ? choice : null;
const probabilities = chart?.probabilities ?? {};
const ranked = allowed
.filter((id) => id !== top)
.map((id) => ({ id, p: typeof probabilities[id] === 'number' ? probabilities[id] : 0 }))
.sort((a, b) => b.p - a.p || a.id.localeCompare(b.id));
const runner = ranked[0];
const hasProbabilities = allowed.some((id) => typeof probabilities[id] === 'number');
const topP = top && typeof probabilities[top] === 'number' ? probabilities[top] : confidence;
const lead = top ? topP - (runner?.p ?? 0) : 0;
const named = response.answers?.named_chart?.noul;
const leadOk = !hasProbabilities || allowed.length <= 1 || lead >= minLead;
return {
top,
runnerUp: runner && (runner.p > 0 || !hasProbabilities) ? runner.id : null,
confidence,
lead,
clear: top !== null && confidence >= minConfidence && leadOk,
namedChart: typeof named === 'number' && named >= 0.5,
};
}
export function offeredChartIds(request) {
return Object.keys(request.questions.chart.criteria);
}
function jevState(input) {
return {
utterance: input.utterance.trim(),
columns: input.columns.map((column) => ({ name: column.name, role: column.role })),
categoryCardinality: input.profile.categoryCardinality ?? 0,
rowCount: input.profile.rowCount ?? 0,
intent: input.intent,
};
}