@absolutejs/ai 0.0.29 → 0.0.31
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/dist/ai/index.js +636 -6
- package/dist/ai/index.js.map +9 -5
- package/dist/ai/providers/anthropic.js.map +2 -2
- package/dist/src/ai/generateAI.d.ts +2 -0
- package/dist/src/ai/index.d.ts +3 -0
- package/dist/src/ai/streamAIWithTools.d.ts +59 -0
- package/dist/src/ai/ui/catalog.d.ts +55 -0
- package/dist/src/ai/ui/index.d.ts +13 -0
- package/dist/src/ai/ui/svg.d.ts +33 -0
- package/dist/src/ai/ui/uiCards.d.ts +46 -0
- package/package.json +1 -1
package/dist/ai/index.js
CHANGED
|
@@ -3501,6 +3501,618 @@ var generateObjectAI = async (options) => {
|
|
|
3501
3501
|
}
|
|
3502
3502
|
throw lastError instanceof Error ? lastError : new Error(`generateObjectAI: failed to produce valid output`);
|
|
3503
3503
|
};
|
|
3504
|
+
|
|
3505
|
+
// src/ai/streamAIWithTools.ts
|
|
3506
|
+
var DEFAULT_STREAM_TOOL_MAX_TURNS = 8;
|
|
3507
|
+
var toolCallKey = (call) => `${call.name}:${JSON.stringify(call.input)}`;
|
|
3508
|
+
var pushText = (blocks, content) => {
|
|
3509
|
+
const last = blocks[blocks.length - 1];
|
|
3510
|
+
if (last && last.type === "text") {
|
|
3511
|
+
last.content += content;
|
|
3512
|
+
return;
|
|
3513
|
+
}
|
|
3514
|
+
blocks.push({ content, type: "text" });
|
|
3515
|
+
};
|
|
3516
|
+
var flushThinking3 = (blocks, thinking) => {
|
|
3517
|
+
if (!thinking)
|
|
3518
|
+
return null;
|
|
3519
|
+
blocks.push({
|
|
3520
|
+
signature: thinking.signature || undefined,
|
|
3521
|
+
thinking: thinking.text,
|
|
3522
|
+
type: "thinking"
|
|
3523
|
+
});
|
|
3524
|
+
return null;
|
|
3525
|
+
};
|
|
3526
|
+
var executeToolHandler = async (tools, call) => {
|
|
3527
|
+
const definition = tools[call.name];
|
|
3528
|
+
if (!definition) {
|
|
3529
|
+
return { ok: false, result: `Error: unknown tool "${call.name}"` };
|
|
3530
|
+
}
|
|
3531
|
+
try {
|
|
3532
|
+
return { ok: true, result: await definition.handler(call.input) };
|
|
3533
|
+
} catch (err) {
|
|
3534
|
+
return {
|
|
3535
|
+
ok: false,
|
|
3536
|
+
result: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
};
|
|
3540
|
+
var streamAIWithTools = async function* (options) {
|
|
3541
|
+
const {
|
|
3542
|
+
maxTurns = DEFAULT_STREAM_TOOL_MAX_TURNS,
|
|
3543
|
+
provider,
|
|
3544
|
+
toolChoice,
|
|
3545
|
+
tools,
|
|
3546
|
+
...base
|
|
3547
|
+
} = options;
|
|
3548
|
+
const providerTools = toProviderTools(tools);
|
|
3549
|
+
const allToolCalls = [];
|
|
3550
|
+
const executedKeys = new Set;
|
|
3551
|
+
const messages = [...options.messages];
|
|
3552
|
+
let usage;
|
|
3553
|
+
let fullText = "";
|
|
3554
|
+
let turn = 0;
|
|
3555
|
+
const streamOneTurn = async function* () {
|
|
3556
|
+
const stream = provider.stream({
|
|
3557
|
+
cacheSystemPrompt: base.cacheSystemPrompt,
|
|
3558
|
+
maxTokens: base.maxTokens,
|
|
3559
|
+
messages,
|
|
3560
|
+
model: base.model,
|
|
3561
|
+
promptCaching: base.promptCaching,
|
|
3562
|
+
reasoning: base.reasoning,
|
|
3563
|
+
signal: base.signal,
|
|
3564
|
+
stopSequences: base.stopSequences,
|
|
3565
|
+
systemPrompt: base.systemPrompt,
|
|
3566
|
+
temperature: base.temperature,
|
|
3567
|
+
toolChoice: toolChoice ?? "auto",
|
|
3568
|
+
tools: providerTools,
|
|
3569
|
+
topP: base.topP
|
|
3570
|
+
});
|
|
3571
|
+
const blocks = [];
|
|
3572
|
+
const pending = [];
|
|
3573
|
+
let thinking = null;
|
|
3574
|
+
let turnUsage;
|
|
3575
|
+
for await (const chunk of stream) {
|
|
3576
|
+
if (base.signal?.aborted)
|
|
3577
|
+
break;
|
|
3578
|
+
if (chunk.type === "thinking") {
|
|
3579
|
+
if (chunk.content)
|
|
3580
|
+
yield { content: chunk.content, type: "thinking" };
|
|
3581
|
+
thinking = thinking ?? { signature: "", text: "" };
|
|
3582
|
+
thinking.text += chunk.content;
|
|
3583
|
+
if (chunk.signature)
|
|
3584
|
+
thinking.signature = chunk.signature;
|
|
3585
|
+
} else if (chunk.type === "text") {
|
|
3586
|
+
thinking = flushThinking3(blocks, thinking);
|
|
3587
|
+
fullText += chunk.content;
|
|
3588
|
+
pushText(blocks, chunk.content);
|
|
3589
|
+
yield { content: chunk.content, type: "text" };
|
|
3590
|
+
} else if (chunk.type === "tool_use") {
|
|
3591
|
+
thinking = flushThinking3(blocks, thinking);
|
|
3592
|
+
pending.push({ id: chunk.id, input: chunk.input, name: chunk.name });
|
|
3593
|
+
blocks.push({
|
|
3594
|
+
id: chunk.id,
|
|
3595
|
+
input: chunk.input && typeof chunk.input === "object" ? chunk.input : {},
|
|
3596
|
+
name: chunk.name,
|
|
3597
|
+
type: "tool_use"
|
|
3598
|
+
});
|
|
3599
|
+
} else if (chunk.type === "done") {
|
|
3600
|
+
thinking = flushThinking3(blocks, thinking);
|
|
3601
|
+
turnUsage = chunk.usage;
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
thinking = flushThinking3(blocks, thinking);
|
|
3605
|
+
return { blocks, pending, usage: turnUsage };
|
|
3606
|
+
};
|
|
3607
|
+
while (turn < maxTurns) {
|
|
3608
|
+
turn += 1;
|
|
3609
|
+
const outcome = yield* streamOneTurn();
|
|
3610
|
+
usage = mergeUsage(usage, outcome.usage);
|
|
3611
|
+
yield { type: "turn", usage: outcome.usage };
|
|
3612
|
+
const { blocks, pending } = outcome;
|
|
3613
|
+
allToolCalls.push(...pending);
|
|
3614
|
+
const allRepeats = pending.length > 0 && pending.every((call) => executedKeys.has(toolCallKey(call)));
|
|
3615
|
+
const finished = pending.length === 0 || turn >= maxTurns || allRepeats || base.signal?.aborted === true;
|
|
3616
|
+
if (finished)
|
|
3617
|
+
break;
|
|
3618
|
+
messages.push({ content: blocks, role: "assistant" });
|
|
3619
|
+
const resultBlocks = [];
|
|
3620
|
+
for (const call of pending) {
|
|
3621
|
+
yield {
|
|
3622
|
+
id: call.id,
|
|
3623
|
+
input: call.input,
|
|
3624
|
+
name: call.name,
|
|
3625
|
+
type: "tool_start"
|
|
3626
|
+
};
|
|
3627
|
+
const startedAt = Date.now();
|
|
3628
|
+
const { ok, result } = await executeToolHandler(tools, call);
|
|
3629
|
+
executedKeys.add(toolCallKey(call));
|
|
3630
|
+
yield {
|
|
3631
|
+
id: call.id,
|
|
3632
|
+
input: call.input,
|
|
3633
|
+
ms: Date.now() - startedAt,
|
|
3634
|
+
name: call.name,
|
|
3635
|
+
ok,
|
|
3636
|
+
result,
|
|
3637
|
+
type: "tool_result"
|
|
3638
|
+
};
|
|
3639
|
+
resultBlocks.push({
|
|
3640
|
+
content: result,
|
|
3641
|
+
tool_use_id: call.id,
|
|
3642
|
+
type: "tool_result"
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
messages.push({ content: resultBlocks, role: "user" });
|
|
3646
|
+
}
|
|
3647
|
+
const summary = {
|
|
3648
|
+
text: fullText,
|
|
3649
|
+
toolCalls: allToolCalls,
|
|
3650
|
+
turns: turn,
|
|
3651
|
+
usage
|
|
3652
|
+
};
|
|
3653
|
+
yield { ...summary, type: "done" };
|
|
3654
|
+
return summary;
|
|
3655
|
+
};
|
|
3656
|
+
// src/ai/ui/uiCards.ts
|
|
3657
|
+
var createUiCards = (definitions) => {
|
|
3658
|
+
const byName = new Map(definitions.map((definition) => [definition.name, definition]));
|
|
3659
|
+
const tools = Object.fromEntries(definitions.map((definition) => [
|
|
3660
|
+
definition.name,
|
|
3661
|
+
{
|
|
3662
|
+
description: definition.description,
|
|
3663
|
+
handler: () => definition.ack,
|
|
3664
|
+
input: definition.inputSchema
|
|
3665
|
+
}
|
|
3666
|
+
]));
|
|
3667
|
+
const collect = (calls) => {
|
|
3668
|
+
const events = [];
|
|
3669
|
+
for (const call of calls) {
|
|
3670
|
+
const definition = byName.get(call.name);
|
|
3671
|
+
if (!definition)
|
|
3672
|
+
continue;
|
|
3673
|
+
const data = definition.parse(call.input);
|
|
3674
|
+
if (data !== null)
|
|
3675
|
+
events.push({ card: definition.name, data });
|
|
3676
|
+
}
|
|
3677
|
+
return events;
|
|
3678
|
+
};
|
|
3679
|
+
return { collect, has: (name) => byName.has(name), tools };
|
|
3680
|
+
};
|
|
3681
|
+
// src/ai/ui/catalog.ts
|
|
3682
|
+
var CHART_TYPES = ["bar", "line", "donut"];
|
|
3683
|
+
var CHART_MAX_SERIES = 8;
|
|
3684
|
+
var CHART_MAX_POINTS = 24;
|
|
3685
|
+
var TABLE_MAX_COLUMNS = 8;
|
|
3686
|
+
var TABLE_MAX_ROWS = 30;
|
|
3687
|
+
var STAT_TILES_MAX = 6;
|
|
3688
|
+
var LABEL_MAX_CHARS = 80;
|
|
3689
|
+
var TITLE_MAX_CHARS = 120;
|
|
3690
|
+
var CELL_MAX_CHARS = 160;
|
|
3691
|
+
var UNIT_MAX_CHARS = 8;
|
|
3692
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null;
|
|
3693
|
+
var cleanString = (value, maxChars) => typeof value === "string" && value.trim().length > 0 ? value.trim().slice(0, maxChars) : null;
|
|
3694
|
+
var cleanStringArray = (value, maxItems, maxChars) => {
|
|
3695
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
3696
|
+
return null;
|
|
3697
|
+
const cleaned = [];
|
|
3698
|
+
for (const entry of value.slice(0, maxItems)) {
|
|
3699
|
+
const text = cleanString(entry, maxChars);
|
|
3700
|
+
cleaned.push(text ?? "");
|
|
3701
|
+
}
|
|
3702
|
+
return cleaned;
|
|
3703
|
+
};
|
|
3704
|
+
var cleanNumberArray = (value, maxItems) => {
|
|
3705
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
3706
|
+
return null;
|
|
3707
|
+
const cleaned = [];
|
|
3708
|
+
for (const entry of value.slice(0, maxItems)) {
|
|
3709
|
+
if (typeof entry !== "number" || !Number.isFinite(entry))
|
|
3710
|
+
return null;
|
|
3711
|
+
cleaned.push(entry);
|
|
3712
|
+
}
|
|
3713
|
+
return cleaned;
|
|
3714
|
+
};
|
|
3715
|
+
var parseChartSpec = (input) => {
|
|
3716
|
+
if (!isRecord6(input))
|
|
3717
|
+
return null;
|
|
3718
|
+
const type = CHART_TYPES.find((entry) => entry === input.type);
|
|
3719
|
+
const title = cleanString(input.title, TITLE_MAX_CHARS);
|
|
3720
|
+
const labels = cleanStringArray(input.labels, CHART_MAX_POINTS, LABEL_MAX_CHARS);
|
|
3721
|
+
if (!type || !title || !labels)
|
|
3722
|
+
return null;
|
|
3723
|
+
if (!Array.isArray(input.series) || input.series.length === 0)
|
|
3724
|
+
return null;
|
|
3725
|
+
const series = [];
|
|
3726
|
+
for (const raw of input.series.slice(0, CHART_MAX_SERIES)) {
|
|
3727
|
+
if (!isRecord6(raw))
|
|
3728
|
+
return null;
|
|
3729
|
+
const name = cleanString(raw.name, LABEL_MAX_CHARS);
|
|
3730
|
+
const values = cleanNumberArray(raw.values, CHART_MAX_POINTS);
|
|
3731
|
+
if (!name || !values)
|
|
3732
|
+
return null;
|
|
3733
|
+
if (values.length !== labels.length)
|
|
3734
|
+
return null;
|
|
3735
|
+
series.push({ name, values });
|
|
3736
|
+
}
|
|
3737
|
+
if (type === "donut") {
|
|
3738
|
+
const [only] = series;
|
|
3739
|
+
if (series.length !== 1 || !only)
|
|
3740
|
+
return null;
|
|
3741
|
+
if (only.values.some((value) => value < 0))
|
|
3742
|
+
return null;
|
|
3743
|
+
}
|
|
3744
|
+
const spec = { labels, series, title, type };
|
|
3745
|
+
const unitPrefix = cleanString(input.unitPrefix, UNIT_MAX_CHARS);
|
|
3746
|
+
const unitSuffix = cleanString(input.unitSuffix, UNIT_MAX_CHARS);
|
|
3747
|
+
if (unitPrefix)
|
|
3748
|
+
spec.unitPrefix = unitPrefix;
|
|
3749
|
+
if (unitSuffix)
|
|
3750
|
+
spec.unitSuffix = unitSuffix;
|
|
3751
|
+
return spec;
|
|
3752
|
+
};
|
|
3753
|
+
var parseTableSpec = (input) => {
|
|
3754
|
+
if (!isRecord6(input))
|
|
3755
|
+
return null;
|
|
3756
|
+
const columns = cleanStringArray(input.columns, TABLE_MAX_COLUMNS, LABEL_MAX_CHARS);
|
|
3757
|
+
if (!columns)
|
|
3758
|
+
return null;
|
|
3759
|
+
if (!Array.isArray(input.rows) || input.rows.length === 0)
|
|
3760
|
+
return null;
|
|
3761
|
+
const rows = [];
|
|
3762
|
+
for (const raw of input.rows.slice(0, TABLE_MAX_ROWS)) {
|
|
3763
|
+
const cells = cleanStringArray(raw, TABLE_MAX_COLUMNS, CELL_MAX_CHARS);
|
|
3764
|
+
if (!cells)
|
|
3765
|
+
return null;
|
|
3766
|
+
while (cells.length < columns.length)
|
|
3767
|
+
cells.push("");
|
|
3768
|
+
rows.push(cells.slice(0, columns.length));
|
|
3769
|
+
}
|
|
3770
|
+
const spec = { columns, rows };
|
|
3771
|
+
const title = cleanString(input.title, TITLE_MAX_CHARS);
|
|
3772
|
+
if (title)
|
|
3773
|
+
spec.title = title;
|
|
3774
|
+
return spec;
|
|
3775
|
+
};
|
|
3776
|
+
var parseStatTilesSpec = (input) => {
|
|
3777
|
+
if (!isRecord6(input))
|
|
3778
|
+
return null;
|
|
3779
|
+
if (!Array.isArray(input.tiles) || input.tiles.length === 0)
|
|
3780
|
+
return null;
|
|
3781
|
+
const tiles = [];
|
|
3782
|
+
for (const raw of input.tiles.slice(0, STAT_TILES_MAX)) {
|
|
3783
|
+
if (!isRecord6(raw))
|
|
3784
|
+
return null;
|
|
3785
|
+
const label = cleanString(raw.label, LABEL_MAX_CHARS);
|
|
3786
|
+
const value = cleanString(raw.value, LABEL_MAX_CHARS);
|
|
3787
|
+
if (!label || !value)
|
|
3788
|
+
return null;
|
|
3789
|
+
const tile = { label, value };
|
|
3790
|
+
const delta = cleanString(raw.delta, LABEL_MAX_CHARS);
|
|
3791
|
+
if (delta)
|
|
3792
|
+
tile.delta = delta;
|
|
3793
|
+
if (raw.deltaDirection === "up" || raw.deltaDirection === "down" || raw.deltaDirection === "flat") {
|
|
3794
|
+
tile.deltaDirection = raw.deltaDirection;
|
|
3795
|
+
}
|
|
3796
|
+
tiles.push(tile);
|
|
3797
|
+
}
|
|
3798
|
+
return { tiles };
|
|
3799
|
+
};
|
|
3800
|
+
var SERIES_SCHEMA = {
|
|
3801
|
+
properties: {
|
|
3802
|
+
name: { description: "Series name (shown in the legend)", type: "string" },
|
|
3803
|
+
values: {
|
|
3804
|
+
description: "One number per label, same order as labels",
|
|
3805
|
+
items: { type: "number" },
|
|
3806
|
+
type: "array"
|
|
3807
|
+
}
|
|
3808
|
+
},
|
|
3809
|
+
required: ["name", "values"],
|
|
3810
|
+
type: "object"
|
|
3811
|
+
};
|
|
3812
|
+
var chartCard = {
|
|
3813
|
+
ack: "(chart rendered inline \u2014 do not repeat its numbers as text; add at most a 1-2 line takeaway)",
|
|
3814
|
+
description: "Render a real chart inline in the chat from data you have (tool results, the conversation). Use whenever numbers COMPARE or TREND: revenue by partner (bar), pipeline over time (line), share of a whole (donut). Rules: bar/line take up to 8 series aligned to the same labels; donut takes exactly ONE series of non-negative values (one slice per label). Prefer a chart over a wall of numbers, but never invent data for it.",
|
|
3815
|
+
inputSchema: {
|
|
3816
|
+
properties: {
|
|
3817
|
+
labels: {
|
|
3818
|
+
description: "Category labels \u2014 x-axis for bar/line, slice names for donut (max 24)",
|
|
3819
|
+
items: { type: "string" },
|
|
3820
|
+
type: "array"
|
|
3821
|
+
},
|
|
3822
|
+
series: {
|
|
3823
|
+
description: "Data series (max 8; donut exactly 1)",
|
|
3824
|
+
items: SERIES_SCHEMA,
|
|
3825
|
+
type: "array"
|
|
3826
|
+
},
|
|
3827
|
+
title: { description: "Short chart title", type: "string" },
|
|
3828
|
+
type: { enum: [...CHART_TYPES], type: "string" },
|
|
3829
|
+
unitPrefix: {
|
|
3830
|
+
description: 'Prepended to values, e.g. "$"',
|
|
3831
|
+
type: "string"
|
|
3832
|
+
},
|
|
3833
|
+
unitSuffix: {
|
|
3834
|
+
description: 'Appended to values, e.g. "%"',
|
|
3835
|
+
type: "string"
|
|
3836
|
+
}
|
|
3837
|
+
},
|
|
3838
|
+
required: ["type", "title", "labels", "series"],
|
|
3839
|
+
type: "object"
|
|
3840
|
+
},
|
|
3841
|
+
name: "render_chart",
|
|
3842
|
+
parse: parseChartSpec
|
|
3843
|
+
};
|
|
3844
|
+
var tableCard = {
|
|
3845
|
+
ack: "(table rendered inline \u2014 do not repeat its rows as text)",
|
|
3846
|
+
description: "Render a compact data table inline in the chat (max 8 columns \xD7 30 rows). Use for structured comparisons the member will scan \u2014 matches side by side, deal terms, task lists with dates. All cells are strings; format numbers yourself.",
|
|
3847
|
+
inputSchema: {
|
|
3848
|
+
properties: {
|
|
3849
|
+
columns: {
|
|
3850
|
+
description: "Column headers (max 8)",
|
|
3851
|
+
items: { type: "string" },
|
|
3852
|
+
type: "array"
|
|
3853
|
+
},
|
|
3854
|
+
rows: {
|
|
3855
|
+
description: "Rows of cells, each aligned to columns (max 30)",
|
|
3856
|
+
items: { items: { type: "string" }, type: "array" },
|
|
3857
|
+
type: "array"
|
|
3858
|
+
},
|
|
3859
|
+
title: { description: "Optional table title", type: "string" }
|
|
3860
|
+
},
|
|
3861
|
+
required: ["columns", "rows"],
|
|
3862
|
+
type: "object"
|
|
3863
|
+
},
|
|
3864
|
+
name: "render_table",
|
|
3865
|
+
parse: parseTableSpec
|
|
3866
|
+
};
|
|
3867
|
+
var statTilesCard = {
|
|
3868
|
+
ack: "(stat tiles rendered inline \u2014 do not repeat the numbers as text)",
|
|
3869
|
+
description: "Render a row of headline stat tiles inline in the chat (max 6): a label, a big value, and an optional delta with direction. Use for the 2-4 numbers that ARE the answer \u2014 total attributed revenue, pipeline value, credits remaining \u2014 instead of burying them in prose.",
|
|
3870
|
+
inputSchema: {
|
|
3871
|
+
properties: {
|
|
3872
|
+
tiles: {
|
|
3873
|
+
description: "The tiles (max 6)",
|
|
3874
|
+
items: {
|
|
3875
|
+
properties: {
|
|
3876
|
+
delta: {
|
|
3877
|
+
description: 'Optional change note, e.g. "+12% vs last month"',
|
|
3878
|
+
type: "string"
|
|
3879
|
+
},
|
|
3880
|
+
deltaDirection: { enum: ["up", "down", "flat"], type: "string" },
|
|
3881
|
+
label: { description: "What the number is", type: "string" },
|
|
3882
|
+
value: {
|
|
3883
|
+
description: 'The formatted headline value, e.g. "$42,300"',
|
|
3884
|
+
type: "string"
|
|
3885
|
+
}
|
|
3886
|
+
},
|
|
3887
|
+
required: ["label", "value"],
|
|
3888
|
+
type: "object"
|
|
3889
|
+
},
|
|
3890
|
+
type: "array"
|
|
3891
|
+
}
|
|
3892
|
+
},
|
|
3893
|
+
required: ["tiles"],
|
|
3894
|
+
type: "object"
|
|
3895
|
+
},
|
|
3896
|
+
name: "render_stat_tiles",
|
|
3897
|
+
parse: parseStatTilesSpec
|
|
3898
|
+
};
|
|
3899
|
+
var BUILTIN_UI_CARDS = [chartCard, tableCard, statTilesCard];
|
|
3900
|
+
// src/ai/ui/svg.ts
|
|
3901
|
+
var LIGHT_UI_THEME = {
|
|
3902
|
+
grid: "#e4e4e0",
|
|
3903
|
+
palette: [
|
|
3904
|
+
"#2a78d6",
|
|
3905
|
+
"#1baf7a",
|
|
3906
|
+
"#eda100",
|
|
3907
|
+
"#008300",
|
|
3908
|
+
"#4a3aa7",
|
|
3909
|
+
"#e34948",
|
|
3910
|
+
"#e87ba4",
|
|
3911
|
+
"#eb6834"
|
|
3912
|
+
],
|
|
3913
|
+
surface: "#fcfcfb",
|
|
3914
|
+
textPrimary: "#0b0b0b",
|
|
3915
|
+
textSecondary: "#52514e"
|
|
3916
|
+
};
|
|
3917
|
+
var DARK_UI_THEME = {
|
|
3918
|
+
grid: "#333331",
|
|
3919
|
+
palette: [
|
|
3920
|
+
"#3987e5",
|
|
3921
|
+
"#199e70",
|
|
3922
|
+
"#c98500",
|
|
3923
|
+
"#008300",
|
|
3924
|
+
"#9085e9",
|
|
3925
|
+
"#e66767",
|
|
3926
|
+
"#d55181",
|
|
3927
|
+
"#d95926"
|
|
3928
|
+
],
|
|
3929
|
+
surface: "#1a1a19",
|
|
3930
|
+
textPrimary: "#ffffff",
|
|
3931
|
+
textSecondary: "#c3c2b7"
|
|
3932
|
+
};
|
|
3933
|
+
var WIDTH = 640;
|
|
3934
|
+
var HEIGHT = 340;
|
|
3935
|
+
var MARGIN = { bottom: 42, left: 56, right: 16, top: 64 };
|
|
3936
|
+
var BAR_END_RADIUS = 4;
|
|
3937
|
+
var MARK_GAP = 2;
|
|
3938
|
+
var LINE_WIDTH = 2;
|
|
3939
|
+
var MAX_DIRECT_LABELED_SERIES = 4;
|
|
3940
|
+
var TICK_TARGET = 4;
|
|
3941
|
+
var FONT = "system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
3942
|
+
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3943
|
+
var formatValue = (value, spec) => {
|
|
3944
|
+
const sign = value < 0 ? "-" : "";
|
|
3945
|
+
const abs = Math.abs(value);
|
|
3946
|
+
const compact = abs >= 1e6 ? `${(abs / 1e6).toFixed(1).replace(/\.0$/, "")}M` : abs >= 1e4 ? `${(abs / 1000).toFixed(1).replace(/\.0$/, "")}k` : abs >= 1000 ? abs.toLocaleString("en-US") : `${Number.isInteger(abs) ? abs : abs.toFixed(1)}`;
|
|
3947
|
+
return `${sign}${spec.unitPrefix ?? ""}${compact}${spec.unitSuffix ?? ""}`;
|
|
3948
|
+
};
|
|
3949
|
+
var niceTicks = (min, max) => {
|
|
3950
|
+
const span = max - min || 1;
|
|
3951
|
+
const rough = span / TICK_TARGET;
|
|
3952
|
+
const power = Math.pow(10, Math.floor(Math.log10(rough)));
|
|
3953
|
+
const candidates = [1, 2, 5, 10].map((step2) => step2 * power);
|
|
3954
|
+
const step = candidates.find((candidate) => candidate >= rough) ?? candidates[candidates.length - 1] ?? rough;
|
|
3955
|
+
const start = Math.floor(min / step) * step;
|
|
3956
|
+
const ticks = [];
|
|
3957
|
+
for (let tick = start;tick <= max + step / 2; tick += step) {
|
|
3958
|
+
ticks.push(Math.abs(tick) < step / 1e6 ? 0 : tick);
|
|
3959
|
+
}
|
|
3960
|
+
return ticks;
|
|
3961
|
+
};
|
|
3962
|
+
var headerSvg = (spec, frame) => {
|
|
3963
|
+
const { theme } = frame;
|
|
3964
|
+
const parts = [
|
|
3965
|
+
`<text x="${frame.plotLeft}" y="24" fill="${theme.textPrimary}" font-size="15" font-weight="600">${escapeXml(spec.title)}</text>`
|
|
3966
|
+
];
|
|
3967
|
+
if (spec.series.length > 1) {
|
|
3968
|
+
let x = frame.plotLeft;
|
|
3969
|
+
const swatches = spec.series.map((series, index) => {
|
|
3970
|
+
const color = theme.palette[index % theme.palette.length];
|
|
3971
|
+
const label = escapeXml(series.name);
|
|
3972
|
+
const item = `<rect x="${x}" y="38" width="10" height="10" rx="2" fill="${color}"/><text x="${x + 14}" y="47" fill="${theme.textSecondary}" font-size="11">${label}</text>`;
|
|
3973
|
+
x += 14 + series.name.length * 6 + 18;
|
|
3974
|
+
return item;
|
|
3975
|
+
});
|
|
3976
|
+
parts.push(...swatches);
|
|
3977
|
+
}
|
|
3978
|
+
return parts.join("");
|
|
3979
|
+
};
|
|
3980
|
+
var gridSvg = (ticks, yFor, spec, frame) => ticks.map((tick) => {
|
|
3981
|
+
const y = yFor(tick);
|
|
3982
|
+
const isZero = tick === 0;
|
|
3983
|
+
return `<line x1="${frame.plotLeft}" y1="${y}" x2="${frame.plotRight}" y2="${y}" stroke="${isZero ? frame.theme.textSecondary : frame.theme.grid}" stroke-width="1"/><text x="${frame.plotLeft - 8}" y="${y + 3.5}" fill="${frame.theme.textSecondary}" font-size="10" text-anchor="end">${escapeXml(formatValue(tick, spec))}</text>`;
|
|
3984
|
+
}).join("");
|
|
3985
|
+
var xLabelsSvg = (spec, xCenter, frame) => {
|
|
3986
|
+
const every = Math.ceil(spec.labels.length / 12);
|
|
3987
|
+
return spec.labels.map((label, index) => {
|
|
3988
|
+
if (index % every !== 0)
|
|
3989
|
+
return "";
|
|
3990
|
+
const short = label.length > 12 ? `${label.slice(0, 11)}\u2026` : label;
|
|
3991
|
+
return `<text x="${xCenter(index)}" y="${frame.plotBottom + 18}" fill="${frame.theme.textSecondary}" font-size="10" text-anchor="middle">${escapeXml(short)}</text>`;
|
|
3992
|
+
}).join("");
|
|
3993
|
+
};
|
|
3994
|
+
var barPath = (x, yValue, yBase, width) => {
|
|
3995
|
+
const up = yValue <= yBase;
|
|
3996
|
+
const top = Math.min(yValue, yBase);
|
|
3997
|
+
const bottom = Math.max(yValue, yBase);
|
|
3998
|
+
const radius = Math.min(BAR_END_RADIUS, width / 2, bottom - top);
|
|
3999
|
+
if (radius <= 0)
|
|
4000
|
+
return "";
|
|
4001
|
+
if (up) {
|
|
4002
|
+
return `M${x},${bottom} L${x},${top + radius} Q${x},${top} ${x + radius},${top} L${x + width - radius},${top} Q${x + width},${top} ${x + width},${top + radius} L${x + width},${bottom} Z`;
|
|
4003
|
+
}
|
|
4004
|
+
return `M${x},${top} L${x},${bottom - radius} Q${x},${bottom} ${x + radius},${bottom} L${x + width - radius},${bottom} Q${x + width},${bottom} ${x + width},${bottom - radius} L${x + width},${top} Z`;
|
|
4005
|
+
};
|
|
4006
|
+
var valueDomain = (spec) => {
|
|
4007
|
+
const all = spec.series.flatMap((series) => series.values);
|
|
4008
|
+
const min = Math.min(0, ...all);
|
|
4009
|
+
const max = Math.max(0, ...all);
|
|
4010
|
+
return max === min ? { max: min + 1, min } : { max, min };
|
|
4011
|
+
};
|
|
4012
|
+
var cartesianSvg = (spec, frame) => {
|
|
4013
|
+
const { theme } = frame;
|
|
4014
|
+
const domain = valueDomain(spec);
|
|
4015
|
+
const ticks = niceTicks(domain.min, domain.max);
|
|
4016
|
+
const lo = Math.min(domain.min, ticks[0] ?? domain.min);
|
|
4017
|
+
const hi = Math.max(domain.max, ticks[ticks.length - 1] ?? domain.max);
|
|
4018
|
+
const yFor = (value) => frame.plotBottom - (value - lo) / (hi - lo) * (frame.plotBottom - frame.plotTop);
|
|
4019
|
+
const slot = (frame.plotRight - frame.plotLeft) / spec.labels.length;
|
|
4020
|
+
const xCenter = (index) => frame.plotLeft + slot * (index + 0.5);
|
|
4021
|
+
const parts = [gridSvg(ticks, yFor, spec, frame)];
|
|
4022
|
+
if (spec.type === "bar") {
|
|
4023
|
+
const group = Math.min(slot * 0.72, 64);
|
|
4024
|
+
const barWidth = Math.max(2, (group - MARK_GAP * (spec.series.length - 1)) / spec.series.length);
|
|
4025
|
+
const yBase = yFor(Math.max(lo, Math.min(hi, 0)));
|
|
4026
|
+
spec.series.forEach((series, seriesIndex) => {
|
|
4027
|
+
const color = theme.palette[seriesIndex % theme.palette.length];
|
|
4028
|
+
series.values.forEach((value, index) => {
|
|
4029
|
+
const x = xCenter(index) - group / 2 + seriesIndex * (barWidth + MARK_GAP);
|
|
4030
|
+
const tooltip = `${escapeXml(series.name)} \xB7 ${escapeXml(spec.labels[index] ?? "")}: ${escapeXml(formatValue(value, spec))}`;
|
|
4031
|
+
parts.push(`<path d="${barPath(x, yFor(value), yBase, barWidth)}" fill="${color}"><title>${tooltip}</title></path>`);
|
|
4032
|
+
});
|
|
4033
|
+
});
|
|
4034
|
+
const [only] = spec.series;
|
|
4035
|
+
if (spec.series.length === 1 && only && spec.labels.length <= 8) {
|
|
4036
|
+
only.values.forEach((value, index) => {
|
|
4037
|
+
const above = value >= 0;
|
|
4038
|
+
parts.push(`<text x="${xCenter(index)}" y="${yFor(value) + (above ? -6 : 14)}" fill="${theme.textSecondary}" font-size="10" text-anchor="middle">${escapeXml(formatValue(value, spec))}</text>`);
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
} else {
|
|
4042
|
+
spec.series.forEach((series, seriesIndex) => {
|
|
4043
|
+
const color = theme.palette[seriesIndex % theme.palette.length];
|
|
4044
|
+
const points = series.values.map((value, index) => `${xCenter(index)},${yFor(value)}`);
|
|
4045
|
+
parts.push(`<polyline points="${points.join(" ")}" fill="none" stroke="${color}" stroke-width="${LINE_WIDTH}" stroke-linejoin="round" stroke-linecap="round"/>`);
|
|
4046
|
+
series.values.forEach((value, index) => {
|
|
4047
|
+
const tooltip = `${escapeXml(series.name)} \xB7 ${escapeXml(spec.labels[index] ?? "")}: ${escapeXml(formatValue(value, spec))}`;
|
|
4048
|
+
parts.push(`<circle cx="${xCenter(index)}" cy="${yFor(value)}" r="4" fill="${color}" stroke="${theme.surface}" stroke-width="2"><title>${tooltip}</title></circle>`);
|
|
4049
|
+
});
|
|
4050
|
+
const last = series.values[series.values.length - 1];
|
|
4051
|
+
if (spec.series.length <= MAX_DIRECT_LABELED_SERIES && last !== undefined) {
|
|
4052
|
+
parts.push(`<text x="${frame.plotRight + 4}" y="${yFor(last) + 3.5}" fill="${theme.textSecondary}" font-size="10">${escapeXml(series.name)}</text>`);
|
|
4053
|
+
}
|
|
4054
|
+
});
|
|
4055
|
+
}
|
|
4056
|
+
parts.push(xLabelsSvg(spec, xCenter, frame));
|
|
4057
|
+
return parts.join("");
|
|
4058
|
+
};
|
|
4059
|
+
var donutSvg = (spec, frame) => {
|
|
4060
|
+
const { theme } = frame;
|
|
4061
|
+
const [series] = spec.series;
|
|
4062
|
+
if (!series)
|
|
4063
|
+
return "";
|
|
4064
|
+
const total = series.values.reduce((sum, value) => sum + value, 0);
|
|
4065
|
+
if (total <= 0)
|
|
4066
|
+
return "";
|
|
4067
|
+
const cx = (frame.plotLeft + frame.plotRight) / 2;
|
|
4068
|
+
const cy = (frame.plotTop + frame.plotBottom) / 2 + 4;
|
|
4069
|
+
const radius = Math.min((frame.plotBottom - frame.plotTop) / 2 - 4, (frame.plotRight - frame.plotLeft) / 4);
|
|
4070
|
+
const ring = Math.max(14, radius * 0.34);
|
|
4071
|
+
const mid = radius - ring / 2;
|
|
4072
|
+
const gapAngle = MARK_GAP / mid;
|
|
4073
|
+
const parts = [];
|
|
4074
|
+
let angle = -Math.PI / 2;
|
|
4075
|
+
series.values.forEach((value, index) => {
|
|
4076
|
+
const sweep = value / total * Math.PI * 2;
|
|
4077
|
+
const start = angle + gapAngle / 2;
|
|
4078
|
+
const end = angle + sweep - gapAngle / 2;
|
|
4079
|
+
angle += sweep;
|
|
4080
|
+
if (end <= start)
|
|
4081
|
+
return;
|
|
4082
|
+
const large = end - start > Math.PI ? 1 : 0;
|
|
4083
|
+
const x1 = cx + mid * Math.cos(start);
|
|
4084
|
+
const y1 = cy + mid * Math.sin(start);
|
|
4085
|
+
const x2 = cx + mid * Math.cos(end);
|
|
4086
|
+
const y2 = cy + mid * Math.sin(end);
|
|
4087
|
+
const color = theme.palette[index % theme.palette.length];
|
|
4088
|
+
const share = `${Math.round(value / total * 100)}%`;
|
|
4089
|
+
const tooltip = `${escapeXml(spec.labels[index] ?? "")}: ${escapeXml(formatValue(value, spec))} (${share})`;
|
|
4090
|
+
parts.push(`<path d="M${x1},${y1} A${mid},${mid} 0 ${large} 1 ${x2},${y2}" fill="none" stroke="${color}" stroke-width="${ring}"><title>${tooltip}</title></path>`);
|
|
4091
|
+
});
|
|
4092
|
+
parts.push(`<text x="${cx}" y="${cy + 5}" fill="${theme.textPrimary}" font-size="16" font-weight="600" text-anchor="middle">${escapeXml(formatValue(total, spec))}</text>`);
|
|
4093
|
+
let x = frame.plotLeft;
|
|
4094
|
+
spec.labels.forEach((label, index) => {
|
|
4095
|
+
const color = theme.palette[index % theme.palette.length];
|
|
4096
|
+
parts.push(`<rect x="${x}" y="38" width="10" height="10" rx="2" fill="${color}"/><text x="${x + 14}" y="47" fill="${theme.textSecondary}" font-size="11">${escapeXml(label)}</text>`);
|
|
4097
|
+
x += 14 + label.length * 6 + 18;
|
|
4098
|
+
});
|
|
4099
|
+
return parts.join("");
|
|
4100
|
+
};
|
|
4101
|
+
var renderChartSvg = (spec, options = {}) => {
|
|
4102
|
+
const base = options.mode === "dark" ? DARK_UI_THEME : LIGHT_UI_THEME;
|
|
4103
|
+
const theme = { ...base, ...options.theme };
|
|
4104
|
+
const width = options.width ?? WIDTH;
|
|
4105
|
+
const height = options.height ?? HEIGHT;
|
|
4106
|
+
const frame = {
|
|
4107
|
+
plotBottom: height - MARGIN.bottom,
|
|
4108
|
+
plotLeft: MARGIN.left,
|
|
4109
|
+
plotRight: width - MARGIN.right - (spec.type === "line" ? 64 : 0),
|
|
4110
|
+
plotTop: MARGIN.top,
|
|
4111
|
+
theme
|
|
4112
|
+
};
|
|
4113
|
+
const body = spec.type === "donut" ? donutSvg(spec, frame) : cartesianSvg(spec, frame);
|
|
4114
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(spec.title)}" font-family="${FONT}"><rect width="${width}" height="${height}" fill="${theme.surface}" rx="12"/>${spec.type === "donut" ? headerSvg({ ...spec, series: [] }, frame) : headerSvg(spec, frame)}${body}</svg>`;
|
|
4115
|
+
};
|
|
3504
4116
|
// src/ai/conversationManager.ts
|
|
3505
4117
|
var NOT_FOUND3 = -1;
|
|
3506
4118
|
var TITLE_MAX_LENGTH2 = 80;
|
|
@@ -4332,16 +4944,16 @@ var AVAILABLE_COMPONENT_STATUSES = new Set([
|
|
|
4332
4944
|
"degraded_performance"
|
|
4333
4945
|
]);
|
|
4334
4946
|
var AVAILABLE_PAGE_INDICATORS = new Set(["none", "minor"]);
|
|
4335
|
-
var
|
|
4947
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null;
|
|
4336
4948
|
var readString = (record, key) => typeof record[key] === "string" ? record[key] : "";
|
|
4337
4949
|
var findComponentStatus = (body, componentName) => {
|
|
4338
|
-
if (!
|
|
4950
|
+
if (!isRecord7(body) || !Array.isArray(body.components))
|
|
4339
4951
|
return null;
|
|
4340
|
-
const match = body.components.filter(
|
|
4952
|
+
const match = body.components.filter(isRecord7).find((component) => readString(component, "name") === componentName);
|
|
4341
4953
|
return match ? readString(match, "status") : null;
|
|
4342
4954
|
};
|
|
4343
4955
|
var readPageIndicator = (body) => {
|
|
4344
|
-
if (!
|
|
4956
|
+
if (!isRecord7(body) || !isRecord7(body.status))
|
|
4345
4957
|
return null;
|
|
4346
4958
|
const indicator = readString(body.status, "indicator");
|
|
4347
4959
|
return indicator === "" ? null : indicator;
|
|
@@ -4422,14 +5034,21 @@ var startProviderStatusMonitor = (options) => {
|
|
|
4422
5034
|
export {
|
|
4423
5035
|
xai,
|
|
4424
5036
|
withResilience,
|
|
5037
|
+
tableCard,
|
|
5038
|
+
streamAIWithTools,
|
|
4425
5039
|
streamAIToSSE,
|
|
4426
5040
|
streamAI,
|
|
5041
|
+
statTilesCard,
|
|
4427
5042
|
startProviderStatusMonitor,
|
|
4428
5043
|
setProviderAvailability,
|
|
4429
5044
|
serverMessageToAction,
|
|
4430
5045
|
serializeAIMessage,
|
|
4431
5046
|
resolveRenderers,
|
|
5047
|
+
renderChartSvg,
|
|
4432
5048
|
providerStatusPage,
|
|
5049
|
+
parseTableSpec,
|
|
5050
|
+
parseStatTilesSpec,
|
|
5051
|
+
parseChartSpec,
|
|
4433
5052
|
parseAIMessage,
|
|
4434
5053
|
openaiResponses,
|
|
4435
5054
|
openaiCompatible,
|
|
@@ -4447,6 +5066,7 @@ export {
|
|
|
4447
5066
|
gemini,
|
|
4448
5067
|
fetchProviderApiStatus,
|
|
4449
5068
|
deepseek,
|
|
5069
|
+
createUiCards,
|
|
4450
5070
|
createSyncConversationStore,
|
|
4451
5071
|
createOAuth2ClientCredentialsTokenSource,
|
|
4452
5072
|
createMemoryStore,
|
|
@@ -4454,12 +5074,22 @@ export {
|
|
|
4454
5074
|
createAIStream,
|
|
4455
5075
|
createAIConnection,
|
|
4456
5076
|
configureProviderResilience,
|
|
5077
|
+
chartCard,
|
|
4457
5078
|
anthropic,
|
|
4458
5079
|
alibaba,
|
|
4459
5080
|
aiChat,
|
|
5081
|
+
TABLE_MAX_ROWS,
|
|
5082
|
+
TABLE_MAX_COLUMNS,
|
|
5083
|
+
STAT_TILES_MAX,
|
|
4460
5084
|
ProviderError,
|
|
4461
|
-
PROVIDER_STATUS_PAGES
|
|
5085
|
+
PROVIDER_STATUS_PAGES,
|
|
5086
|
+
LIGHT_UI_THEME,
|
|
5087
|
+
DARK_UI_THEME,
|
|
5088
|
+
CHART_TYPES,
|
|
5089
|
+
CHART_MAX_SERIES,
|
|
5090
|
+
CHART_MAX_POINTS,
|
|
5091
|
+
BUILTIN_UI_CARDS
|
|
4462
5092
|
};
|
|
4463
5093
|
|
|
4464
|
-
//# debugId=
|
|
5094
|
+
//# debugId=D43CDA3E59D2F6FB64756E2164756E21
|
|
4465
5095
|
//# sourceMappingURL=index.js.map
|