@automattic/agenttic-client 0.1.0

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,688 @@
1
+ // src/markdown-extensions/charts/BarChart.tsx
2
+ import { BarChart as AutomatticBarChart } from "@automattic/charts";
3
+
4
+ // src/markdown-extensions/charts/BaseChart.tsx
5
+ import {
6
+ defaultTheme,
7
+ ThemeProvider
8
+ } from "@automattic/charts";
9
+ import { useMemo } from "react";
10
+
11
+ // src/utils/theme.ts
12
+ var getCSSVariable = (variableName, fallback = "") => {
13
+ try {
14
+ const value = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim();
15
+ return value || fallback;
16
+ } catch (error) {
17
+ console.warn(`Failed to get CSS variable ${variableName}:`, error);
18
+ return fallback;
19
+ }
20
+ };
21
+
22
+ // src/markdown-extensions/charts/ChartError.tsx
23
+ import { jsx, jsxs } from "react/jsx-runtime";
24
+ var ChartError = ({ message, details }) => /* @__PURE__ */ jsxs("div", { className: "chart-error", children: [
25
+ /* @__PURE__ */ jsx("strong", { children: "Chart Error:" }),
26
+ " ",
27
+ message,
28
+ details && /* @__PURE__ */ jsxs("details", { children: [
29
+ /* @__PURE__ */ jsx("summary", { children: "Show Details" }),
30
+ /* @__PURE__ */ jsx("pre", { children: details })
31
+ ] })
32
+ ] });
33
+
34
+ // src/markdown-extensions/charts/BaseChart.tsx
35
+ import { jsx as jsx2 } from "react/jsx-runtime";
36
+ var BaseChart = ({
37
+ children,
38
+ error
39
+ }) => {
40
+ const customTheme = useMemo(() => {
41
+ const primaryColor = getCSSVariable("--color-primary", "#4F46E5");
42
+ const colors = [primaryColor, "#f0b849", "#00a32a", "#8c5e94"];
43
+ return {
44
+ ...defaultTheme,
45
+ colors,
46
+ legendLabelStyles: {
47
+ fontSize: "11px",
48
+ fontWeight: "400",
49
+ lineHeight: "1.2",
50
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif'
51
+ },
52
+ legendItemStyles: {
53
+ padding: "2px 0",
54
+ display: "flex",
55
+ alignItems: "center"
56
+ },
57
+ legendGlyphSize: 12
58
+ };
59
+ }, []);
60
+ if (error) {
61
+ return /* @__PURE__ */ jsx2(ChartError, { message: error.message, details: error.details });
62
+ }
63
+ return /* @__PURE__ */ jsx2("div", { className: "chart-block", children: /* @__PURE__ */ jsx2("div", { className: "chart-container", children: /* @__PURE__ */ jsx2(ThemeProvider, { theme: customTheme, children }) }) });
64
+ };
65
+
66
+ // src/markdown-extensions/charts/utils/chartUtils.ts
67
+ var calculateBottomMargin = (minMargin = 80, charWidth = 6, padding = 25, labelLength) => {
68
+ const calculatedLength = labelLength ?? 5;
69
+ return Math.max(minMargin, calculatedLength * charWidth + padding);
70
+ };
71
+ var parseDate = (dateValue) => {
72
+ if (dateValue instanceof Date) {
73
+ return dateValue;
74
+ }
75
+ const dateStr = String(dateValue);
76
+ if (dateStr.includes("T") || dateStr.includes(" ")) {
77
+ return new Date(dateStr);
78
+ }
79
+ const [year, month, day] = dateStr.split("-").map(Number);
80
+ return new Date(year, month - 1, day);
81
+ };
82
+ var getTimeAxisConfig = (data) => {
83
+ const allDataPoints = [];
84
+ data?.forEach((series) => {
85
+ if (series?.data) {
86
+ allDataPoints.push(...series.data);
87
+ }
88
+ });
89
+ if (allDataPoints.length === 0) {
90
+ return {};
91
+ }
92
+ const allDates = allDataPoints.map((point) => {
93
+ if (point && "date" in point && point.date) {
94
+ return parseDate(point.date);
95
+ }
96
+ if (point && "dateString" in point && point.dateString) {
97
+ return parseDate(point.dateString);
98
+ }
99
+ return null;
100
+ }).filter(
101
+ (date) => date !== null && !isNaN(date.getTime())
102
+ ).sort((a, b) => a.getTime() - b.getTime());
103
+ const uniqueDates = Array.from(
104
+ new Set(allDates.map((d) => d.getTime()))
105
+ ).map((timestamp) => new Date(timestamp));
106
+ if (uniqueDates.length === 0) {
107
+ return {};
108
+ }
109
+ const indices = [
110
+ 0,
111
+ Math.floor(uniqueDates.length / 3),
112
+ Math.floor(2 * uniqueDates.length / 3),
113
+ uniqueDates.length - 1
114
+ ];
115
+ const tickValues = indices.map((i) => uniqueDates[i]).filter(
116
+ (date) => date !== null && !isNaN(date.getTime())
117
+ );
118
+ return {
119
+ orientation: "bottom",
120
+ tickValues,
121
+ tickFormat: (value) => {
122
+ let date = null;
123
+ if (value instanceof Date) {
124
+ date = value;
125
+ } else if (typeof value === "string") {
126
+ const parsed = new Date(value);
127
+ if (!isNaN(parsed.getTime())) {
128
+ date = parsed;
129
+ }
130
+ }
131
+ if (date) {
132
+ return `${(date.getMonth() + 1).toString().padStart(2, "0")}/${date.getDate().toString().padStart(2, "0")}`;
133
+ }
134
+ return String(value || "");
135
+ },
136
+ tickLabelProps: () => ({
137
+ fontSize: 11,
138
+ textAnchor: "end",
139
+ angle: -90,
140
+ dx: 0,
141
+ dy: -5
142
+ })
143
+ };
144
+ };
145
+ var getDefaultChartMargins = (customMargin) => {
146
+ const defaultMargin = {
147
+ top: 15,
148
+ right: 20,
149
+ bottom: 100,
150
+ left: 50
151
+ };
152
+ return {
153
+ ...defaultMargin,
154
+ ...customMargin,
155
+ bottom: calculateBottomMargin(120)
156
+ };
157
+ };
158
+
159
+ // src/markdown-extensions/charts/BarChart.tsx
160
+ import { jsx as jsx3 } from "react/jsx-runtime";
161
+ var BarChart = (props) => {
162
+ const {
163
+ data,
164
+ currency,
165
+ showLegend = true,
166
+ withTooltips = true,
167
+ renderTooltip,
168
+ margin: defaultMargin,
169
+ mode = "time-comparison",
170
+ truncateLabels = true,
171
+ maxLabelLength = 15,
172
+ error,
173
+ ...restProps
174
+ } = props;
175
+ const truncateText = (text, maxLength) => {
176
+ if (!truncateLabels || text.length <= maxLength) {
177
+ return text;
178
+ }
179
+ return text.substring(0, maxLength - 3) + "...";
180
+ };
181
+ const calculateBarChartBottomMargin = () => {
182
+ if (mode === "item-comparison" && data && data.length > 0) {
183
+ let longestLabel = "";
184
+ data.forEach((series) => {
185
+ if (series?.data) {
186
+ series.data.forEach((item) => {
187
+ const label = item.label || "";
188
+ const truncated = truncateText(label, maxLabelLength);
189
+ if (truncated.length > longestLabel.length) {
190
+ longestLabel = truncated;
191
+ }
192
+ });
193
+ }
194
+ });
195
+ return calculateBottomMargin(80, 6, 25, longestLabel.length);
196
+ }
197
+ return calculateBottomMargin();
198
+ };
199
+ const margin = {
200
+ ...getDefaultChartMargins(defaultMargin),
201
+ bottom: calculateBarChartBottomMargin()
202
+ };
203
+ const getAxisConfig = () => {
204
+ if (mode === "item-comparison") {
205
+ return {
206
+ xScale: {
207
+ type: "band",
208
+ padding: 0.2
209
+ },
210
+ axis: {
211
+ x: {
212
+ orientation: "bottom",
213
+ tickFormat: (value) => truncateText(value, maxLabelLength),
214
+ tickLabelProps: {
215
+ fontSize: 11,
216
+ textAnchor: "end",
217
+ angle: -90,
218
+ dx: 0,
219
+ dy: -5
220
+ }
221
+ }
222
+ }
223
+ };
224
+ }
225
+ return {
226
+ xScale: { type: "time" },
227
+ axis: {
228
+ x: {
229
+ ...getTimeAxisConfig(data),
230
+ dy: -15
231
+ }
232
+ }
233
+ };
234
+ };
235
+ const axisConfig = getAxisConfig();
236
+ const chartProps = {
237
+ data,
238
+ withTooltips,
239
+ renderTooltip,
240
+ showLegend,
241
+ legendOrientation: "horizontal",
242
+ legendAlignmentHorizontal: "center",
243
+ legendAlignmentVertical: "bottom",
244
+ margin,
245
+ // TODO: Fix this inconsistency that seems to be in @automattic/charts
246
+ // Item-comparison mode requires axis config wrapped in 'options' prop
247
+ // Time-comparison mode requires axis config spread directly
248
+ ...mode === "item-comparison" ? { options: axisConfig } : axisConfig,
249
+ ...currency && { currency },
250
+ ...restProps
251
+ };
252
+ return /* @__PURE__ */ jsx3(BaseChart, { error, children: /* @__PURE__ */ jsx3(AutomatticBarChart, { ...chartProps }) });
253
+ };
254
+
255
+ // src/markdown-extensions/charts/LineChart.tsx
256
+ import { LineChart as AutomatticLineChart } from "@automattic/charts";
257
+ import { jsx as jsx4 } from "react/jsx-runtime";
258
+ var LineChart = (props) => {
259
+ const {
260
+ data,
261
+ currency,
262
+ showLegend = true,
263
+ withTooltips = true,
264
+ renderTooltip,
265
+ margin: defaultMargin,
266
+ withGradientFill = true,
267
+ error,
268
+ ...restProps
269
+ } = props;
270
+ const margin = {
271
+ ...getDefaultChartMargins(),
272
+ bottom: 80
273
+ };
274
+ const chartProps = {
275
+ data,
276
+ withTooltips,
277
+ renderTooltip,
278
+ showLegend,
279
+ withGradientFill,
280
+ withLegendGlyph: false,
281
+ legendOrientation: "horizontal",
282
+ legendAlignmentHorizontal: "center",
283
+ legendAlignmentVertical: "bottom",
284
+ margin,
285
+ options: {
286
+ xScale: {
287
+ type: "time"
288
+ },
289
+ axis: {
290
+ x: {
291
+ ...getTimeAxisConfig(data)
292
+ }
293
+ }
294
+ },
295
+ ...currency && { currency },
296
+ ...restProps
297
+ };
298
+ return /* @__PURE__ */ jsx4(BaseChart, { error, children: /* @__PURE__ */ jsx4(AutomatticLineChart, { ...chartProps }) });
299
+ };
300
+
301
+ // src/markdown-extensions/charts/ChartBlock.tsx
302
+ import { useCallback, useEffect, useRef, useState } from "react";
303
+ import { __, sprintf } from "@wordpress/i18n";
304
+
305
+ // src/markdown-extensions/charts/ChartErrorBoundary.tsx
306
+ import { Component } from "react";
307
+ import { jsx as jsx5 } from "react/jsx-runtime";
308
+ var ChartErrorBoundary = class extends Component {
309
+ constructor(props) {
310
+ super(props);
311
+ this.state = {
312
+ hasError: false,
313
+ error: null,
314
+ errorInfo: null
315
+ };
316
+ }
317
+ static getDerivedStateFromError(error) {
318
+ return {
319
+ hasError: true,
320
+ error,
321
+ errorInfo: null
322
+ };
323
+ }
324
+ componentDidCatch(error, errorInfo) {
325
+ console.error("Chart rendering error:", error);
326
+ console.error("Error info:", errorInfo);
327
+ if (this.props.chartData) {
328
+ console.error(
329
+ "Chart data that caused the error:",
330
+ this.props.chartData
331
+ );
332
+ try {
333
+ const parsed = JSON.parse(this.props.chartData);
334
+ console.error("Parsed chart data:", parsed);
335
+ } catch (parseError) {
336
+ console.error("Could not parse chart data as JSON");
337
+ }
338
+ }
339
+ this.setState({
340
+ errorInfo
341
+ });
342
+ }
343
+ render() {
344
+ if (this.state.hasError) {
345
+ const errorMessage = this.state.error?.message || "An error occurred while rendering the chart";
346
+ const errorDetails = [
347
+ "The chart could not be rendered due to an error.",
348
+ "The error has been logged to the console."
349
+ ];
350
+ return /* @__PURE__ */ jsx5(
351
+ ChartError,
352
+ {
353
+ message: errorMessage,
354
+ details: errorDetails.join("\n")
355
+ }
356
+ );
357
+ }
358
+ return this.props.children;
359
+ }
360
+ };
361
+
362
+ // src/markdown-extensions/charts/ChartBlock.tsx
363
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
364
+ var ChartBlock = ({
365
+ data,
366
+ className = "",
367
+ config
368
+ }) => {
369
+ const [error, setError] = useState(null);
370
+ const [chartData, setChartData] = useState(
371
+ null
372
+ );
373
+ const [containerWidth, setContainerWidth] = useState(300);
374
+ const resizeObserverRef = useRef(null);
375
+ const customRenderTooltip = useCallback(
376
+ (params) => {
377
+ const { tooltipData } = params;
378
+ const nearestDatum = tooltipData?.nearestDatum?.datum;
379
+ if (!nearestDatum) {
380
+ return null;
381
+ }
382
+ const formatValue = (value) => {
383
+ if (chartData?.currency) {
384
+ const { symbol, symbolPosition } = chartData.currency;
385
+ let formattedValue;
386
+ if (value >= 1e6) {
387
+ formattedValue = `${(value / 1e6).toFixed(
388
+ 1
389
+ )}M`;
390
+ } else if (value >= 1e3) {
391
+ formattedValue = `${(value / 1e3).toFixed(1)}K`;
392
+ } else {
393
+ formattedValue = value.toLocaleString();
394
+ }
395
+ return symbolPosition === "right" ? `${formattedValue}${symbol}` : `${symbol}${formattedValue}`;
396
+ }
397
+ if (value >= 1e6) {
398
+ return `${(value / 1e6).toFixed(1)}M`;
399
+ } else if (value >= 1e3) {
400
+ return `${(value / 1e3).toFixed(1)}K`;
401
+ }
402
+ return value.toLocaleString();
403
+ };
404
+ const formatDate = (date) => {
405
+ const now = /* @__PURE__ */ new Date();
406
+ const diffInDays = Math.floor(
407
+ (now.getTime() - date.getTime()) / (1e3 * 60 * 60 * 24)
408
+ );
409
+ if (diffInDays === 0) {
410
+ return __("Today", "a8c-agenttic");
411
+ } else if (diffInDays === 1) {
412
+ return __("Yesterday", "a8c-agenttic");
413
+ } else if (diffInDays < 7) {
414
+ return sprintf(
415
+ /* translators: %d: number of days */
416
+ __("%d days ago", "a8c-agenttic"),
417
+ diffInDays
418
+ );
419
+ }
420
+ return date.toLocaleDateString("en-US", {
421
+ year: "numeric",
422
+ month: "short",
423
+ day: "numeric"
424
+ });
425
+ };
426
+ const productName = nearestDatum.label;
427
+ const tooltipPoints = Object.entries(
428
+ tooltipData?.datumByKey || {}
429
+ ).map(([key, datumInfo]) => {
430
+ const datum = datumInfo.datum;
431
+ return {
432
+ key,
433
+ value: datum.value
434
+ };
435
+ }).sort((a, b) => b.value - a.value);
436
+ return /* @__PURE__ */ jsxs2("div", { children: [
437
+ productName && /* @__PURE__ */ jsx6(
438
+ "div",
439
+ {
440
+ style: {
441
+ fontSize: "12px",
442
+ fontWeight: "bold",
443
+ marginBottom: "4px",
444
+ color: "#1e1e1e",
445
+ borderBottom: "1px solid #eee",
446
+ paddingBottom: "2px"
447
+ },
448
+ children: productName
449
+ }
450
+ ),
451
+ nearestDatum.date && /* @__PURE__ */ jsx6(
452
+ "div",
453
+ {
454
+ style: {
455
+ fontSize: "10px",
456
+ opacity: 0.8,
457
+ marginBottom: "4px"
458
+ },
459
+ children: formatDate(nearestDatum.date)
460
+ }
461
+ ),
462
+ tooltipPoints.map((point) => /* @__PURE__ */ jsxs2(
463
+ "div",
464
+ {
465
+ style: { marginBottom: "2px" },
466
+ children: [
467
+ /* @__PURE__ */ jsxs2("strong", { children: [
468
+ point.key,
469
+ ":"
470
+ ] }),
471
+ " ",
472
+ formatValue(point.value)
473
+ ]
474
+ },
475
+ point.key
476
+ ))
477
+ ] });
478
+ },
479
+ [chartData?.currency]
480
+ );
481
+ useEffect(() => {
482
+ setError(null);
483
+ setChartData(null);
484
+ if (!data || typeof data !== "string") {
485
+ setError({
486
+ message: __("Invalid chart data provided", "a8c-agenttic"),
487
+ details: `Input data: ${data}`
488
+ });
489
+ return;
490
+ }
491
+ try {
492
+ const rawData = JSON.parse(data.trim());
493
+ if (!rawData.chartType) {
494
+ setError({
495
+ message: __(
496
+ "Chart data must include chartType",
497
+ "a8c-agenttic"
498
+ ),
499
+ details: __("Available types: line, bar", "a8c-agenttic")
500
+ });
501
+ return;
502
+ }
503
+ if (!rawData.data || !Array.isArray(rawData.data)) {
504
+ setError({
505
+ message: __(
506
+ "Chart data must include a data array",
507
+ "a8c-agenttic"
508
+ ),
509
+ details: `Input data: ${data}`
510
+ });
511
+ return;
512
+ }
513
+ if (rawData.data.length === 0) {
514
+ setError({
515
+ message: __(
516
+ "No data points found for chart",
517
+ "a8c-agenttic"
518
+ ),
519
+ details: `Input data: ${data}`
520
+ });
521
+ return;
522
+ }
523
+ const processedDataSeries = rawData.data.map((series) => ({
524
+ ...series,
525
+ data: series.data.map((point) => {
526
+ if (point.date) {
527
+ const parsedDate = new Date(point.date);
528
+ if (isNaN(parsedDate.getTime())) {
529
+ console.warn(
530
+ `Invalid date string: "${point.date}" in series "${series.label}"`
531
+ );
532
+ return {
533
+ label: point.label,
534
+ value: point.value,
535
+ date: void 0
536
+ };
537
+ }
538
+ return {
539
+ label: point.label,
540
+ value: point.value,
541
+ date: parsedDate
542
+ };
543
+ }
544
+ return {
545
+ label: point.label,
546
+ value: point.value,
547
+ date: void 0
548
+ };
549
+ })
550
+ }));
551
+ const processedData = {
552
+ chartType: rawData.chartType,
553
+ title: rawData.title,
554
+ data: processedDataSeries,
555
+ currency: rawData.currency,
556
+ mode: rawData.mode || "time-comparison"
557
+ };
558
+ setChartData(processedData);
559
+ } catch (parseError) {
560
+ setError({
561
+ message: __(
562
+ "Failed to parse chart data as JSON",
563
+ "a8c-agenttic"
564
+ ),
565
+ details: `Input data: ${data}`
566
+ });
567
+ }
568
+ }, [data]);
569
+ const setContainerRef = useCallback((node) => {
570
+ if (resizeObserverRef.current) {
571
+ resizeObserverRef.current.disconnect();
572
+ }
573
+ if (node) {
574
+ const { width } = node.getBoundingClientRect();
575
+ const contentWidth = Math.max(280, width - 4);
576
+ setContainerWidth(contentWidth);
577
+ resizeObserverRef.current = new ResizeObserver((entries) => {
578
+ for (const entry of entries) {
579
+ const resizedWidth = entry.contentRect.width;
580
+ const resizedContentWidth = Math.max(
581
+ 280,
582
+ resizedWidth - 4
583
+ );
584
+ setContainerWidth(resizedContentWidth);
585
+ }
586
+ });
587
+ resizeObserverRef.current.observe(node);
588
+ }
589
+ }, []);
590
+ useEffect(() => {
591
+ return () => {
592
+ if (resizeObserverRef.current) {
593
+ resizeObserverRef.current.disconnect();
594
+ resizeObserverRef.current = null;
595
+ }
596
+ };
597
+ }, []);
598
+ if (error) {
599
+ return /* @__PURE__ */ jsx6(ChartError, { message: error.message, details: error.details });
600
+ }
601
+ if (!chartData) {
602
+ return /* @__PURE__ */ jsx6(
603
+ ChartError,
604
+ {
605
+ message: __("No chart data available", "a8c-agenttic")
606
+ }
607
+ );
608
+ }
609
+ const hasMultipleSeries = chartData.data.length > 1;
610
+ const shouldShowLegend = hasMultipleSeries;
611
+ const chartWidth = containerWidth;
612
+ const commonProps = {
613
+ data: chartData.data,
614
+ currency: chartData.currency,
615
+ showLegend: shouldShowLegend,
616
+ withTooltips: true,
617
+ renderTooltip: customRenderTooltip,
618
+ error: null,
619
+ maxWidth: chartWidth,
620
+ aspectRatio: 1.2,
621
+ resizeDebounceTime: 300
622
+ };
623
+ const renderChart = () => {
624
+ switch (chartData.chartType) {
625
+ case "line":
626
+ return /* @__PURE__ */ jsx6(LineChart, { ...commonProps });
627
+ case "bar":
628
+ return /* @__PURE__ */ jsx6(
629
+ BarChart,
630
+ {
631
+ ...commonProps,
632
+ mode: chartData.mode
633
+ }
634
+ );
635
+ default:
636
+ return /* @__PURE__ */ jsx6(
637
+ ChartError,
638
+ {
639
+ message: sprintf(
640
+ /* translators: %s: chart type name */
641
+ __("Unsupported chart type: %s", "a8c-agenttic"),
642
+ chartData.chartType
643
+ )
644
+ }
645
+ );
646
+ }
647
+ };
648
+ return /* @__PURE__ */ jsx6(ChartErrorBoundary, { chartData: data, children: /* @__PURE__ */ jsxs2(
649
+ "div",
650
+ {
651
+ ref: setContainerRef,
652
+ className: `chart-block ${className}`,
653
+ children: [
654
+ chartData.title && /* @__PURE__ */ jsx6("h3", { className: "chart-block-title", children: chartData.title }),
655
+ /* @__PURE__ */ jsx6("div", { className: "chart-container", children: renderChart() })
656
+ ]
657
+ }
658
+ ) });
659
+ };
660
+
661
+ // src/markdown-extensions/charts/index.ts
662
+ import React2 from "react";
663
+ function createChartBlock(config) {
664
+ return function ChartCodeBlock(props) {
665
+ const { children, className, ...rest } = props;
666
+ const isChartBlock = className?.includes("language-chart");
667
+ if (!isChartBlock) {
668
+ return React2.createElement(
669
+ "code",
670
+ { className, ...rest },
671
+ children
672
+ );
673
+ }
674
+ const chartData = typeof children === "string" ? children : "";
675
+ return React2.createElement(ChartBlock, {
676
+ data: chartData,
677
+ className: "markdown-chart",
678
+ config
679
+ });
680
+ };
681
+ }
682
+
683
+ export {
684
+ BarChart,
685
+ LineChart,
686
+ ChartBlock,
687
+ createChartBlock
688
+ };