@agent-native/core 0.76.9 → 0.76.11
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +39 -0
- package/corpus/core/docs/content/locales/ar-SA/actions.md +7 -7
- package/corpus/core/docs/content/locales/ar-SA/template-plan.md +6 -4
- package/corpus/core/docs/content/locales/de-DE/actions.md +9 -9
- package/corpus/core/docs/content/locales/de-DE/template-plan.md +6 -4
- package/corpus/core/docs/content/locales/es-ES/actions.md +9 -9
- package/corpus/core/docs/content/locales/es-ES/template-plan.md +6 -4
- package/corpus/core/docs/content/locales/fr-FR/actions.md +9 -9
- package/corpus/core/docs/content/locales/fr-FR/template-plan.md +6 -4
- package/corpus/core/docs/content/locales/hi-IN/actions.md +9 -9
- package/corpus/core/docs/content/locales/hi-IN/template-plan.md +4 -4
- package/corpus/core/docs/content/locales/ja-JP/actions.md +9 -9
- package/corpus/core/docs/content/locales/ja-JP/template-plan.md +4 -4
- package/corpus/core/docs/content/locales/ko-KR/actions.md +9 -9
- package/corpus/core/docs/content/locales/ko-KR/template-plan.md +4 -4
- package/corpus/core/docs/content/locales/pt-BR/actions.md +9 -9
- package/corpus/core/docs/content/locales/pt-BR/template-plan.md +6 -4
- package/corpus/core/docs/content/locales/zh-CN/actions.md +9 -9
- package/corpus/core/docs/content/locales/zh-CN/template-plan.md +4 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/scripts/generate-og-font-data.mjs +9 -5
- package/corpus/core/src/client/AgentPanel.tsx +70 -1
- package/corpus/core/src/client/AssistantChat.tsx +1 -3
- package/corpus/core/src/client/ErrorBoundary.tsx +151 -10
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +0 -62
- package/corpus/core/src/client/composer/TiptapComposer.tsx +6 -13
- package/corpus/core/src/server/og-fonts-data.ts +7 -4
- package/corpus/core/src/server/og-fonts.ts +10 -3
- package/corpus/core/src/server/social-og-image.ts +45 -6
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +199 -19
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +51 -31
- package/corpus/templates/analytics/app/components/layout/DashboardHeader.tsx +26 -16
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -2
- package/corpus/templates/analytics/app/components/layout/HeaderActions.tsx +8 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +2 -5
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +172 -107
- package/corpus/templates/analytics/app/i18n-data.ts +1076 -5
- package/corpus/templates/analytics/app/pages/Settings.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/AdhocRouter.tsx +1 -21
- package/corpus/templates/analytics/app/pages/adhoc/DashboardSkeleton.tsx +35 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +3 -26
- package/corpus/templates/analytics/changelog/2026-06-24-analytics-navigation-command-palette-and-settings-now-honor-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-daily-dashboard-charts-now-use-local-day-buckets-and-show-th.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-loading-placeholders-now-span-the-available-page-w.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-24-first-party-dashboard-dates-now-align-to-pacific-time-for-da.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +19 -7
- package/corpus/templates/assets/app/components/layout/Header.tsx +0 -2
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +0 -2
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +0 -2
- package/corpus/templates/chat/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/clips/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/clips/app/hooks/use-library.ts +2 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-06-24-archive-and-spaces-navigation-is-more-resilient-when-sidebar.md +6 -0
- package/corpus/templates/content/app/components/layout/Header.tsx +0 -2
- package/corpus/templates/design/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/forms/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/macros/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/mail/app/components/email/CodeBlockLangPicker.tsx +4 -2
- package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +18 -12
- package/corpus/templates/mail/app/components/email/ComposeEditor.tsx +9 -5
- package/corpus/templates/mail/app/components/email/ComposeModal.tsx +36 -19
- package/corpus/templates/mail/app/components/email/EmailList.tsx +59 -55
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +7 -3
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +25 -16
- package/corpus/templates/mail/app/components/email/InlineReplyComposer.tsx +25 -19
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +32 -17
- package/corpus/templates/mail/app/components/email/RecipientInput.tsx +26 -10
- package/corpus/templates/mail/app/components/email/SendLaterButton.tsx +5 -3
- package/corpus/templates/mail/app/components/email/SnoozeModal.tsx +4 -2
- package/corpus/templates/mail/app/components/email/SnoozePopover.tsx +9 -5
- package/corpus/templates/mail/app/components/email/extensions/ComposeImageBlock.tsx +4 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +168 -86
- package/corpus/templates/mail/app/components/layout/SearchBar.tsx +4 -2
- package/corpus/templates/mail/app/i18n/ar-SA.ts +212 -0
- package/corpus/templates/mail/app/i18n/de-DE.ts +212 -0
- package/corpus/templates/mail/app/i18n/en-US.ts +212 -0
- package/corpus/templates/mail/app/i18n/es-ES.ts +212 -0
- package/corpus/templates/mail/app/i18n/fr-FR.ts +212 -0
- package/corpus/templates/mail/app/i18n/hi-IN.ts +212 -0
- package/corpus/templates/mail/app/i18n/ja-JP.ts +212 -0
- package/corpus/templates/mail/app/i18n/ko-KR.ts +212 -0
- package/corpus/templates/mail/app/i18n/pt-BR.ts +212 -0
- package/corpus/templates/mail/app/i18n/zh-CN.ts +211 -0
- package/corpus/templates/mail/changelog/2026-06-24-interface-language-support-now-covers-more-mail-controls-and.md +6 -0
- package/corpus/templates/plan/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/slides/app/components/layout/Header.tsx +1 -6
- package/corpus/templates/videos/app/components/layout/Header.tsx +1 -6
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +23 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +1 -3
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ErrorBoundary.d.ts.map +1 -1
- package/dist/client/ErrorBoundary.js +128 -7
- package/dist/client/ErrorBoundary.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -17
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +4 -0
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +5 -9
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/server/og-fonts-data.d.ts +1 -0
- package/dist/server/og-fonts-data.d.ts.map +1 -1
- package/dist/server/og-fonts-data.js +5 -4
- package/dist/server/og-fonts-data.js.map +1 -1
- package/dist/server/og-fonts.d.ts +1 -0
- package/dist/server/og-fonts.d.ts.map +1 -1
- package/dist/server/og-fonts.js +10 -4
- package/dist/server/og-fonts.js.map +1 -1
- package/dist/server/social-og-image.d.ts.map +1 -1
- package/dist/server/social-og-image.js +33 -6
- package/dist/server/social-og-image.js.map +1 -1
- package/docs/content/locales/ar-SA/actions.md +7 -7
- package/docs/content/locales/ar-SA/template-plan.md +6 -4
- package/docs/content/locales/de-DE/actions.md +9 -9
- package/docs/content/locales/de-DE/template-plan.md +6 -4
- package/docs/content/locales/es-ES/actions.md +9 -9
- package/docs/content/locales/es-ES/template-plan.md +6 -4
- package/docs/content/locales/fr-FR/actions.md +9 -9
- package/docs/content/locales/fr-FR/template-plan.md +6 -4
- package/docs/content/locales/hi-IN/actions.md +9 -9
- package/docs/content/locales/hi-IN/template-plan.md +4 -4
- package/docs/content/locales/ja-JP/actions.md +9 -9
- package/docs/content/locales/ja-JP/template-plan.md +4 -4
- package/docs/content/locales/ko-KR/actions.md +9 -9
- package/docs/content/locales/ko-KR/template-plan.md +4 -4
- package/docs/content/locales/pt-BR/actions.md +9 -9
- package/docs/content/locales/pt-BR/template-plan.md +6 -4
- package/docs/content/locales/zh-CN/actions.md +9 -9
- package/docs/content/locales/zh-CN/template-plan.md +4 -4
- package/package.json +1 -1
|
@@ -5,14 +5,16 @@ import path from "node:path";
|
|
|
5
5
|
import {
|
|
6
6
|
LIBERATION_SANS_BOLD_BASE64,
|
|
7
7
|
LIBERATION_SANS_REGULAR_BASE64,
|
|
8
|
+
NOTO_NASKH_ARABIC_BASE64,
|
|
8
9
|
} from "./og-fonts-data.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Liberation Sans is the metric-compatible libre replacement for
|
|
12
|
-
* Arial/Helvetica that the OG image SVG asks for.
|
|
13
|
+
* Arial/Helvetica that the OG image SVG asks for. Noto Naskh Arabic fills the
|
|
14
|
+
* Arabic script gap for localized docs titles. They ship embedded as base64
|
|
13
15
|
* (see {@link ./og-fonts-data.ts}) so the renderer never depends on the host's
|
|
14
|
-
* system fonts — Linux serverless runtimes (Netlify/Lambda) have neither Arial
|
|
15
|
-
*
|
|
16
|
+
* system fonts — Linux serverless runtimes (Netlify/Lambda) have neither Arial,
|
|
17
|
+
* Inter, nor Arabic fallback fonts.
|
|
16
18
|
*/
|
|
17
19
|
const OG_FONT_FILES = [
|
|
18
20
|
{
|
|
@@ -20,9 +22,14 @@ const OG_FONT_FILES = [
|
|
|
20
22
|
base64: LIBERATION_SANS_REGULAR_BASE64,
|
|
21
23
|
},
|
|
22
24
|
{ filename: "LiberationSans-Bold.ttf", base64: LIBERATION_SANS_BOLD_BASE64 },
|
|
25
|
+
{
|
|
26
|
+
filename: "NotoNaskhArabic-Variable.ttf",
|
|
27
|
+
base64: NOTO_NASKH_ARABIC_BASE64,
|
|
28
|
+
},
|
|
23
29
|
] as const;
|
|
24
30
|
|
|
25
31
|
export const OG_FONT_FAMILY = "Liberation Sans";
|
|
32
|
+
export const OG_ARABIC_FONT_FAMILY = "Noto Naskh Arabic";
|
|
26
33
|
|
|
27
34
|
let cachedFontFiles: string[] | null | undefined;
|
|
28
35
|
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
resolveBuiltInAuthMarketingByName,
|
|
12
12
|
} from "./auth-marketing.js";
|
|
13
13
|
import { getAppName } from "./app-name.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
OG_ARABIC_FONT_FAMILY,
|
|
16
|
+
OG_FONT_FAMILY,
|
|
17
|
+
resolveOgFontFiles,
|
|
18
|
+
} from "./og-fonts.js";
|
|
15
19
|
|
|
16
20
|
export interface AgentNativeOgImageInput {
|
|
17
21
|
appName?: string | null;
|
|
@@ -32,7 +36,8 @@ const BRAND_BLUE = "#00B5FF";
|
|
|
32
36
|
const BRAND_MINT = "#48FFE4";
|
|
33
37
|
const BG = "#000000";
|
|
34
38
|
const FG = "#f5f5f5";
|
|
35
|
-
const
|
|
39
|
+
const DEFAULT_FONT_FAMILY = `${OG_FONT_FAMILY}, Arial, Helvetica, system-ui, sans-serif`;
|
|
40
|
+
const ARABIC_FONT_FAMILY = `${OG_ARABIC_FONT_FAMILY}, ${OG_FONT_FAMILY}, Arial, Helvetica, system-ui, sans-serif`;
|
|
36
41
|
const DEFAULT_ACCENT_TEXT = "100% free and open source";
|
|
37
42
|
|
|
38
43
|
const LOGO_MARK = `
|
|
@@ -101,6 +106,16 @@ function estimateTextWidth(value: string, fontSize: number): number {
|
|
|
101
106
|
return units * fontSize;
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
function containsArabicText(value: string): boolean {
|
|
110
|
+
return /[\u0600-\u06ff\u0750-\u077f\u0870-\u089f\ufb50-\ufdff\ufe70-\ufeff]/u.test(
|
|
111
|
+
value,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function fontFamilyForText(value: string): string {
|
|
116
|
+
return containsArabicText(value) ? ARABIC_FONT_FAMILY : DEFAULT_FONT_FAMILY;
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
function trimTextToWidth(
|
|
105
120
|
value: string,
|
|
106
121
|
fontSize: number,
|
|
@@ -205,6 +220,8 @@ function textBlock({
|
|
|
205
220
|
weight,
|
|
206
221
|
fill,
|
|
207
222
|
anchor = "start",
|
|
223
|
+
direction,
|
|
224
|
+
fontFamily = DEFAULT_FONT_FAMILY,
|
|
208
225
|
}: {
|
|
209
226
|
lines: string[];
|
|
210
227
|
x: number;
|
|
@@ -213,9 +230,14 @@ function textBlock({
|
|
|
213
230
|
lineHeight: number;
|
|
214
231
|
weight: number;
|
|
215
232
|
fill: string;
|
|
216
|
-
anchor?: "start" | "middle";
|
|
233
|
+
anchor?: "start" | "middle" | "end";
|
|
234
|
+
direction?: "ltr" | "rtl";
|
|
235
|
+
fontFamily?: string;
|
|
217
236
|
}): string {
|
|
218
|
-
|
|
237
|
+
const directionAttrs = direction
|
|
238
|
+
? ` direction="${direction}" unicode-bidi="plaintext"`
|
|
239
|
+
: "";
|
|
240
|
+
return `<text x="${x}" y="${y}" text-anchor="${anchor}"${directionAttrs} font-family="${fontFamily}" font-size="${fontSize}" font-weight="${weight}" fill="${fill}">${lines
|
|
219
241
|
.map(
|
|
220
242
|
(line, index) =>
|
|
221
243
|
`<tspan x="${x}" dy="${index === 0 ? 0 : lineHeight}">${escapeSvg(line)}</tspan>`,
|
|
@@ -289,6 +311,10 @@ export function renderAgentNativeOgImageSvg(
|
|
|
289
311
|
const title = cleanText(input.title) || titleFromAppName(appName);
|
|
290
312
|
const accentText = cleanText(input.accentText) || DEFAULT_ACCENT_TEXT;
|
|
291
313
|
const titleLayout = getTitleLayout(title);
|
|
314
|
+
const titleIsRtl = containsArabicText(title);
|
|
315
|
+
const textX = titleIsRtl ? WIDTH - 80 : 80;
|
|
316
|
+
const accentX = titleIsRtl ? WIDTH - 84 : 84;
|
|
317
|
+
const textAnchor = titleIsRtl ? "end" : "start";
|
|
292
318
|
const titleY = titleLayout.lines.length > 1 ? 288 : 330;
|
|
293
319
|
const accentY =
|
|
294
320
|
titleY + titleLayout.lineHeight * (titleLayout.lines.length - 1) + 70;
|
|
@@ -312,7 +338,7 @@ export function renderAgentNativeOgImageSvg(
|
|
|
312
338
|
<g>
|
|
313
339
|
${textBlock({
|
|
314
340
|
lines: titleLayout.lines,
|
|
315
|
-
x:
|
|
341
|
+
x: textX,
|
|
316
342
|
y: titleY,
|
|
317
343
|
fontSize: titleLayout.fontSize,
|
|
318
344
|
lineHeight: titleLayout.lineHeight,
|
|
@@ -321,8 +347,21 @@ export function renderAgentNativeOgImageSvg(
|
|
|
321
347
|
// bundle, which is the intended look for the display title.
|
|
322
348
|
weight: 800,
|
|
323
349
|
fill: FG,
|
|
350
|
+
anchor: textAnchor,
|
|
351
|
+
direction: titleIsRtl ? "rtl" : undefined,
|
|
352
|
+
fontFamily: fontFamilyForText(title),
|
|
353
|
+
})}
|
|
354
|
+
${textBlock({
|
|
355
|
+
lines: [accentText],
|
|
356
|
+
x: accentX,
|
|
357
|
+
y: accentY,
|
|
358
|
+
fontSize: 34,
|
|
359
|
+
lineHeight: 40,
|
|
360
|
+
weight: 800,
|
|
361
|
+
fill: BRAND_BLUE,
|
|
362
|
+
anchor: textAnchor,
|
|
363
|
+
fontFamily: fontFamilyForText(accentText),
|
|
324
364
|
})}
|
|
325
|
-
<text x="84" y="${accentY}" font-family="${FONT_FAMILY}" font-size="34" font-weight="800" fill="${BRAND_BLUE}">${escapeSvg(accentText)}</text>
|
|
326
365
|
</g>
|
|
327
366
|
</svg>`;
|
|
328
367
|
}
|
|
@@ -97,6 +97,10 @@ const CHART_LEGEND_PROPS = {
|
|
|
97
97
|
wrapperStyle: CHART_LEGEND_WRAPPER_STYLE,
|
|
98
98
|
} as const;
|
|
99
99
|
|
|
100
|
+
const PARTIAL_DAY_TIME_ZONE = "America/Los_Angeles";
|
|
101
|
+
const PARTIAL_DAY_DASH = "3 5";
|
|
102
|
+
const PARTIAL_DAY_KEY_PREFIX = "__sql_chart_partial_day";
|
|
103
|
+
|
|
100
104
|
function formatYValue(
|
|
101
105
|
value: number,
|
|
102
106
|
formatter?: "number" | "currency" | "percent",
|
|
@@ -167,6 +171,122 @@ function truncateLabel(value: string, max = 48): string {
|
|
|
167
171
|
return `${value.slice(0, max - 3)}...`;
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
function parseCalendarDate(value: string): Date | null {
|
|
175
|
+
const normalized = /^\d{8}$/.test(value)
|
|
176
|
+
? `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`
|
|
177
|
+
: value;
|
|
178
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(normalized);
|
|
179
|
+
if (dateOnly) {
|
|
180
|
+
return new Date(
|
|
181
|
+
Number(dateOnly[1]),
|
|
182
|
+
Number(dateOnly[2]) - 1,
|
|
183
|
+
Number(dateOnly[3]),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const parsed = new Date(normalized);
|
|
187
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function toSqlChartDateKey(value: unknown): string | null {
|
|
191
|
+
if (typeof value === "string") {
|
|
192
|
+
const trimmed = value.trim();
|
|
193
|
+
const ymd = /^(\d{4})-(\d{2})-(\d{2})$/.exec(trimmed);
|
|
194
|
+
if (ymd) return `${ymd[1]}-${ymd[2]}-${ymd[3]}`;
|
|
195
|
+
const compact = /^(\d{4})(\d{2})(\d{2})$/.exec(trimmed);
|
|
196
|
+
if (compact) return `${compact[1]}-${compact[2]}-${compact[3]}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const parsed = new Date(String(value ?? ""));
|
|
200
|
+
return Number.isNaN(parsed.getTime()) ? null : sqlChartLocalDateKey(parsed);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function sqlChartLocalDateKey(
|
|
204
|
+
date = new Date(),
|
|
205
|
+
timeZone = PARTIAL_DAY_TIME_ZONE,
|
|
206
|
+
): string {
|
|
207
|
+
try {
|
|
208
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
209
|
+
timeZone,
|
|
210
|
+
year: "numeric",
|
|
211
|
+
month: "2-digit",
|
|
212
|
+
day: "2-digit",
|
|
213
|
+
}).formatToParts(date);
|
|
214
|
+
const get = (type: string) =>
|
|
215
|
+
parts.find((part) => part.type === type)?.value ?? "";
|
|
216
|
+
const year = get("year");
|
|
217
|
+
const month = get("month");
|
|
218
|
+
const day = get("day");
|
|
219
|
+
if (year && month && day) return `${year}-${month}-${day}`;
|
|
220
|
+
} catch {}
|
|
221
|
+
return date.toISOString().slice(0, 10);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface SplitTimeSeries {
|
|
225
|
+
key: string;
|
|
226
|
+
solidKey: string;
|
|
227
|
+
partialKey: string | null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function splitCurrentDayTimeSeriesRows(
|
|
231
|
+
rows: Record<string, unknown>[],
|
|
232
|
+
xKey: string,
|
|
233
|
+
yKeys: string[],
|
|
234
|
+
todayKey = sqlChartLocalDateKey(),
|
|
235
|
+
): { rows: Record<string, unknown>[]; series: SplitTimeSeries[] } {
|
|
236
|
+
const todayIndexes = rows
|
|
237
|
+
.map((row, index) => ({ index, dateKey: toSqlChartDateKey(row[xKey]) }))
|
|
238
|
+
.filter((entry) => entry.dateKey === todayKey)
|
|
239
|
+
.map((entry) => entry.index);
|
|
240
|
+
const firstTodayIndex = todayIndexes[0] ?? -1;
|
|
241
|
+
|
|
242
|
+
if (firstTodayIndex <= 0 || yKeys.length === 0) {
|
|
243
|
+
return {
|
|
244
|
+
rows,
|
|
245
|
+
series: yKeys.map((key) => ({ key, solidKey: key, partialKey: null })),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const todaySet = new Set(todayIndexes);
|
|
250
|
+
const previousIndex = firstTodayIndex - 1;
|
|
251
|
+
const series = yKeys.map((key, index) => ({
|
|
252
|
+
key,
|
|
253
|
+
solidKey: `${PARTIAL_DAY_KEY_PREFIX}_${index}_solid`,
|
|
254
|
+
partialKey: `${PARTIAL_DAY_KEY_PREFIX}_${index}_partial`,
|
|
255
|
+
}));
|
|
256
|
+
const splitRows = rows.map((row, index) => {
|
|
257
|
+
const next = { ...row };
|
|
258
|
+
const isToday = todaySet.has(index);
|
|
259
|
+
const isPartialSegmentStart = index === previousIndex;
|
|
260
|
+
for (const item of series) {
|
|
261
|
+
next[item.solidKey] = isToday ? null : row[item.key];
|
|
262
|
+
next[item.partialKey] =
|
|
263
|
+
isToday || isPartialSegmentStart ? row[item.key] : null;
|
|
264
|
+
}
|
|
265
|
+
return next;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return { rows: splitRows, series };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function shouldSplitCurrentDayTimeSeries(
|
|
272
|
+
panel: Pick<SqlPanel, "source">,
|
|
273
|
+
xKey: string,
|
|
274
|
+
): boolean {
|
|
275
|
+
if (panel.source === "prometheus") return false;
|
|
276
|
+
|
|
277
|
+
const normalizedKey = xKey.trim().toLowerCase();
|
|
278
|
+
if (normalizedKey === "timestamp" || normalizedKey.endsWith("_timestamp")) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
normalizedKey === "date" ||
|
|
284
|
+
normalizedKey === "day" ||
|
|
285
|
+
normalizedKey.endsWith("_date") ||
|
|
286
|
+
normalizedKey.endsWith("_day")
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
170
290
|
function formatSeriesLabel(value: string): string {
|
|
171
291
|
const { metric, labels } = parsePrometheusSeriesLabel(value);
|
|
172
292
|
const target =
|
|
@@ -232,11 +352,8 @@ function formatSeriesLabelForPanel(panel: SqlPanel, value: string): string {
|
|
|
232
352
|
function formatXLabel(value: string, panel: SqlPanel): string {
|
|
233
353
|
try {
|
|
234
354
|
const s = String(value);
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
: s;
|
|
238
|
-
const d = new Date(normalized);
|
|
239
|
-
if (!isNaN(d.getTime()) && s.length >= 8) {
|
|
355
|
+
const d = parseCalendarDate(s);
|
|
356
|
+
if (d && s.length >= 8) {
|
|
240
357
|
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
241
358
|
}
|
|
242
359
|
} catch {}
|
|
@@ -252,12 +369,21 @@ function numericTooltipValue(value: unknown): number {
|
|
|
252
369
|
return Number.isFinite(numeric) ? numeric : Number.NEGATIVE_INFINITY;
|
|
253
370
|
}
|
|
254
371
|
|
|
372
|
+
function tooltipItemName(item: {
|
|
373
|
+
dataKey?: string | number;
|
|
374
|
+
name?: string | number;
|
|
375
|
+
}): string {
|
|
376
|
+
return String(item.name ?? item.dataKey ?? "");
|
|
377
|
+
}
|
|
378
|
+
|
|
255
379
|
export function sortTooltipPayloadItems<
|
|
256
380
|
T extends {
|
|
381
|
+
dataKey?: string | number;
|
|
382
|
+
name?: string | number;
|
|
257
383
|
value?: unknown;
|
|
258
384
|
},
|
|
259
385
|
>(items: T[]): T[] {
|
|
260
|
-
|
|
386
|
+
const sorted = items
|
|
261
387
|
.map((item, index) => ({ item, index }))
|
|
262
388
|
.sort((a, b) => {
|
|
263
389
|
const diff =
|
|
@@ -265,6 +391,13 @@ export function sortTooltipPayloadItems<
|
|
|
265
391
|
return diff !== 0 ? diff : a.index - b.index;
|
|
266
392
|
})
|
|
267
393
|
.map(({ item }) => item);
|
|
394
|
+
const seen = new Set<string>();
|
|
395
|
+
return sorted.filter((item) => {
|
|
396
|
+
const name = tooltipItemName(item);
|
|
397
|
+
if (seen.has(name)) return false;
|
|
398
|
+
seen.add(name);
|
|
399
|
+
return true;
|
|
400
|
+
});
|
|
268
401
|
}
|
|
269
402
|
|
|
270
403
|
function useSeriesVisibility(keys: string[]): {
|
|
@@ -1390,6 +1523,21 @@ function TimeSeriesRenderer({
|
|
|
1390
1523
|
const seriesNameFormatter = (name: string) =>
|
|
1391
1524
|
formatSeriesLabelForPanel(panel, name);
|
|
1392
1525
|
const { hiddenKeys, visibleKeys, toggleSeries } = useSeriesVisibility(yKeys);
|
|
1526
|
+
const splitPartialDay = shouldSplitCurrentDayTimeSeries(panel, xKey);
|
|
1527
|
+
const { rows: chartRows, series } = useMemo(
|
|
1528
|
+
() =>
|
|
1529
|
+
splitPartialDay
|
|
1530
|
+
? splitCurrentDayTimeSeriesRows(rows, xKey, yKeys)
|
|
1531
|
+
: {
|
|
1532
|
+
rows,
|
|
1533
|
+
series: yKeys.map((key) => ({
|
|
1534
|
+
key,
|
|
1535
|
+
solidKey: key,
|
|
1536
|
+
partialKey: null,
|
|
1537
|
+
})),
|
|
1538
|
+
},
|
|
1539
|
+
[rows, xKey, yKeys, splitPartialDay],
|
|
1540
|
+
);
|
|
1393
1541
|
|
|
1394
1542
|
if (chartType === "line") {
|
|
1395
1543
|
return (
|
|
@@ -1402,7 +1550,7 @@ function TimeSeriesRenderer({
|
|
|
1402
1550
|
showCustomLegend
|
|
1403
1551
|
>
|
|
1404
1552
|
<ResponsiveContainer width="100%" height="100%">
|
|
1405
|
-
<LineChart data={
|
|
1553
|
+
<LineChart data={chartRows}>
|
|
1406
1554
|
<XAxis
|
|
1407
1555
|
dataKey={xKey}
|
|
1408
1556
|
stroke="hsl(var(--muted-foreground))"
|
|
@@ -1435,18 +1583,33 @@ function TimeSeriesRenderer({
|
|
|
1435
1583
|
}
|
|
1436
1584
|
itemSorter={(item) => -(Number(item.value) || 0)}
|
|
1437
1585
|
/>
|
|
1438
|
-
{
|
|
1586
|
+
{series.map((item, i) => (
|
|
1439
1587
|
<Line
|
|
1440
|
-
key={
|
|
1588
|
+
key={item.solidKey}
|
|
1441
1589
|
type="monotone"
|
|
1442
|
-
dataKey={
|
|
1443
|
-
name={seriesNameFormatter(key)}
|
|
1590
|
+
dataKey={item.solidKey}
|
|
1591
|
+
name={seriesNameFormatter(item.key)}
|
|
1444
1592
|
stroke={colors[i % colors.length]}
|
|
1445
1593
|
strokeWidth={2}
|
|
1446
1594
|
dot={false}
|
|
1447
|
-
hide={hiddenKeys.has(key)}
|
|
1595
|
+
hide={hiddenKeys.has(item.key)}
|
|
1448
1596
|
/>
|
|
1449
1597
|
))}
|
|
1598
|
+
{series.map((item, i) =>
|
|
1599
|
+
item.partialKey ? (
|
|
1600
|
+
<Line
|
|
1601
|
+
key={item.partialKey}
|
|
1602
|
+
type="monotone"
|
|
1603
|
+
dataKey={item.partialKey}
|
|
1604
|
+
name={seriesNameFormatter(item.key)}
|
|
1605
|
+
stroke={colors[i % colors.length]}
|
|
1606
|
+
strokeWidth={2}
|
|
1607
|
+
strokeDasharray={PARTIAL_DAY_DASH}
|
|
1608
|
+
dot={false}
|
|
1609
|
+
hide={hiddenKeys.has(item.key)}
|
|
1610
|
+
/>
|
|
1611
|
+
) : null,
|
|
1612
|
+
)}
|
|
1450
1613
|
</LineChart>
|
|
1451
1614
|
</ResponsiveContainer>
|
|
1452
1615
|
</ChartFrame>
|
|
@@ -1468,7 +1631,7 @@ function TimeSeriesRenderer({
|
|
|
1468
1631
|
showCustomLegend
|
|
1469
1632
|
>
|
|
1470
1633
|
<ResponsiveContainer width="100%" height="100%">
|
|
1471
|
-
<AreaChart data={
|
|
1634
|
+
<AreaChart data={chartRows}>
|
|
1472
1635
|
{showFill && (
|
|
1473
1636
|
<defs>
|
|
1474
1637
|
{yKeys.map((key, i) => (
|
|
@@ -1526,20 +1689,37 @@ function TimeSeriesRenderer({
|
|
|
1526
1689
|
}
|
|
1527
1690
|
itemSorter={(item) => -(Number(item.value) || 0)}
|
|
1528
1691
|
/>
|
|
1529
|
-
{
|
|
1692
|
+
{series.map((item, i) => (
|
|
1530
1693
|
<Area
|
|
1531
|
-
key={
|
|
1694
|
+
key={item.solidKey}
|
|
1532
1695
|
type="monotone"
|
|
1533
|
-
dataKey={
|
|
1534
|
-
name={seriesNameFormatter(key)}
|
|
1696
|
+
dataKey={item.solidKey}
|
|
1697
|
+
name={seriesNameFormatter(item.key)}
|
|
1535
1698
|
stroke={colors[i % colors.length]}
|
|
1536
1699
|
strokeWidth={2}
|
|
1537
1700
|
fillOpacity={showFill ? 1 : 0}
|
|
1538
|
-
fill={showFill ? `url(#sql-gradient-${key})` : "none"}
|
|
1701
|
+
fill={showFill ? `url(#sql-gradient-${item.key})` : "none"}
|
|
1539
1702
|
stackId={stacked ? "stack" : undefined}
|
|
1540
|
-
hide={hiddenKeys.has(key)}
|
|
1703
|
+
hide={hiddenKeys.has(item.key)}
|
|
1541
1704
|
/>
|
|
1542
1705
|
))}
|
|
1706
|
+
{series.map((item, i) =>
|
|
1707
|
+
item.partialKey ? (
|
|
1708
|
+
<Area
|
|
1709
|
+
key={item.partialKey}
|
|
1710
|
+
type="monotone"
|
|
1711
|
+
dataKey={item.partialKey}
|
|
1712
|
+
name={seriesNameFormatter(item.key)}
|
|
1713
|
+
stroke={colors[i % colors.length]}
|
|
1714
|
+
strokeWidth={2}
|
|
1715
|
+
strokeDasharray={PARTIAL_DAY_DASH}
|
|
1716
|
+
fill="none"
|
|
1717
|
+
fillOpacity={0}
|
|
1718
|
+
stackId={stacked ? "partial-stack" : undefined}
|
|
1719
|
+
hide={hiddenKeys.has(item.key)}
|
|
1720
|
+
/>
|
|
1721
|
+
) : null,
|
|
1722
|
+
)}
|
|
1543
1723
|
</AreaChart>
|
|
1544
1724
|
</ResponsiveContainer>
|
|
1545
1725
|
</ChartFrame>
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
callAction,
|
|
28
28
|
useChangeVersions,
|
|
29
29
|
ChangelogDialog,
|
|
30
|
+
useT,
|
|
30
31
|
} from "@agent-native/core/client";
|
|
31
32
|
import { extensionPath } from "@agent-native/core/client/extensions";
|
|
32
33
|
import changelog from "../../../CHANGELOG.md?raw";
|
|
@@ -50,10 +51,14 @@ interface ExtensionSearchItem {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const defaultTools = [
|
|
53
|
-
{
|
|
54
|
+
{
|
|
55
|
+
id: "explorer",
|
|
56
|
+
nameKey: "commandPalette.toolExplorer",
|
|
57
|
+
href: "/dashboards/explorer",
|
|
58
|
+
},
|
|
54
59
|
{
|
|
55
60
|
id: "customer-health",
|
|
56
|
-
|
|
61
|
+
nameKey: "commandPalette.toolCustomerHealth",
|
|
57
62
|
href: "/dashboards/customer-health",
|
|
58
63
|
},
|
|
59
64
|
];
|
|
@@ -126,9 +131,9 @@ async function fetchExplorerDashboards(): Promise<ExplorerDashboard[]> {
|
|
|
126
131
|
}
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
async function fetchSqlDashboards(
|
|
130
|
-
|
|
131
|
-
> {
|
|
134
|
+
async function fetchSqlDashboards(
|
|
135
|
+
t: (key: string) => string,
|
|
136
|
+
): Promise<{ id: string; name: string; hiddenAt: string | null }[]> {
|
|
132
137
|
try {
|
|
133
138
|
const rows = await callAction(
|
|
134
139
|
"list-sql-dashboards",
|
|
@@ -142,7 +147,7 @@ async function fetchSqlDashboards(): Promise<
|
|
|
142
147
|
name:
|
|
143
148
|
typeof d.name === "string" && d.name.trim().length > 0
|
|
144
149
|
? d.name
|
|
145
|
-
: "
|
|
150
|
+
: t("commandPalette.untitledDashboard"),
|
|
146
151
|
hiddenAt: typeof d.hiddenAt === "string" ? d.hiddenAt : null,
|
|
147
152
|
}));
|
|
148
153
|
} catch {
|
|
@@ -183,6 +188,7 @@ function persistThemePreference(theme: "light" | "dark") {
|
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
export function CommandPalette() {
|
|
191
|
+
const t = useT();
|
|
186
192
|
const [open, setOpen] = useState(false);
|
|
187
193
|
const [changelogOpen, setChangelogOpen] = useState(false);
|
|
188
194
|
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -213,7 +219,7 @@ export function CommandPalette() {
|
|
|
213
219
|
const { data: sqlDashboards = [], isFetching: sqlDashboardsFetching } =
|
|
214
220
|
useQuery({
|
|
215
221
|
queryKey: ["sql-dashboards-palette", dashboardsSync],
|
|
216
|
-
queryFn: fetchSqlDashboards,
|
|
222
|
+
queryFn: () => fetchSqlDashboards(t),
|
|
217
223
|
staleTime: 30_000,
|
|
218
224
|
enabled: open,
|
|
219
225
|
placeholderData: (prev) => prev,
|
|
@@ -276,21 +282,24 @@ export function CommandPalette() {
|
|
|
276
282
|
}}
|
|
277
283
|
>
|
|
278
284
|
<CommandInput
|
|
279
|
-
placeholder="
|
|
285
|
+
placeholder={t("commandPalette.searchPlaceholder")}
|
|
280
286
|
value={searchQuery}
|
|
281
287
|
onValueChange={setSearchQuery}
|
|
282
288
|
/>
|
|
283
289
|
<CommandList>
|
|
284
290
|
{!asyncGroupsLoading && (
|
|
285
|
-
<CommandEmpty>
|
|
291
|
+
<CommandEmpty>{t("commandPalette.noResults")}</CommandEmpty>
|
|
286
292
|
)}
|
|
287
293
|
|
|
288
294
|
{explorerDashboardsFetching && explorerDashboards.length === 0 && (
|
|
289
|
-
<CommandLoadingGroup
|
|
295
|
+
<CommandLoadingGroup
|
|
296
|
+
heading={t("commandPalette.groupExplorerDashboards")}
|
|
297
|
+
rows={2}
|
|
298
|
+
/>
|
|
290
299
|
)}
|
|
291
300
|
|
|
292
301
|
{visibleExplorerDashboards.length > 0 && (
|
|
293
|
-
<CommandGroup heading="
|
|
302
|
+
<CommandGroup heading={t("commandPalette.groupExplorerDashboards")}>
|
|
294
303
|
{visibleExplorerDashboards.map((d) => (
|
|
295
304
|
<CommandItem
|
|
296
305
|
key={`ed-${d.id}`}
|
|
@@ -307,7 +316,7 @@ export function CommandPalette() {
|
|
|
307
316
|
<span className="truncate">{d.name}</span>
|
|
308
317
|
{d.hiddenAt ? (
|
|
309
318
|
<span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
310
|
-
|
|
319
|
+
{t("commandPalette.hidden")}
|
|
311
320
|
</span>
|
|
312
321
|
) : null}
|
|
313
322
|
</CommandItem>
|
|
@@ -316,11 +325,14 @@ export function CommandPalette() {
|
|
|
316
325
|
)}
|
|
317
326
|
|
|
318
327
|
{sqlDashboardsFetching && sqlDashboards.length === 0 && (
|
|
319
|
-
<CommandLoadingGroup
|
|
328
|
+
<CommandLoadingGroup
|
|
329
|
+
heading={t("commandPalette.groupSqlDashboards")}
|
|
330
|
+
rows={3}
|
|
331
|
+
/>
|
|
320
332
|
)}
|
|
321
333
|
|
|
322
334
|
{visibleSqlDashboards.length > 0 && (
|
|
323
|
-
<CommandGroup heading="
|
|
335
|
+
<CommandGroup heading={t("commandPalette.groupSqlDashboards")}>
|
|
324
336
|
{visibleSqlDashboards.map((d) => (
|
|
325
337
|
<CommandItem
|
|
326
338
|
key={`sql-${d.id}`}
|
|
@@ -335,7 +347,7 @@ export function CommandPalette() {
|
|
|
335
347
|
<span className="truncate">{d.name}</span>
|
|
336
348
|
{d.hiddenAt ? (
|
|
337
349
|
<span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
338
|
-
|
|
350
|
+
{t("commandPalette.hidden")}
|
|
339
351
|
</span>
|
|
340
352
|
) : null}
|
|
341
353
|
</CommandItem>
|
|
@@ -344,11 +356,14 @@ export function CommandPalette() {
|
|
|
344
356
|
)}
|
|
345
357
|
|
|
346
358
|
{extensionsFetching && extensions.length === 0 && (
|
|
347
|
-
<CommandLoadingGroup
|
|
359
|
+
<CommandLoadingGroup
|
|
360
|
+
heading={t("commandPalette.groupExtensions")}
|
|
361
|
+
rows={3}
|
|
362
|
+
/>
|
|
348
363
|
)}
|
|
349
364
|
|
|
350
365
|
{extensions.length > 0 && (
|
|
351
|
-
<CommandGroup heading="
|
|
366
|
+
<CommandGroup heading={t("commandPalette.groupExtensions")}>
|
|
352
367
|
{extensions.map((extension) => (
|
|
353
368
|
<CommandItem
|
|
354
369
|
key={`extension-${extension.id}`}
|
|
@@ -369,7 +384,7 @@ export function CommandPalette() {
|
|
|
369
384
|
</CommandGroup>
|
|
370
385
|
)}
|
|
371
386
|
|
|
372
|
-
<CommandGroup heading="
|
|
387
|
+
<CommandGroup heading={t("commandPalette.groupDashboards")}>
|
|
373
388
|
{dashboards.map((d) => (
|
|
374
389
|
<CommandItem
|
|
375
390
|
key={`dash-${d.id}`}
|
|
@@ -382,20 +397,20 @@ export function CommandPalette() {
|
|
|
382
397
|
))}
|
|
383
398
|
</CommandGroup>
|
|
384
399
|
|
|
385
|
-
<CommandGroup heading="
|
|
386
|
-
{defaultTools.map((
|
|
400
|
+
<CommandGroup heading={t("commandPalette.groupTools")}>
|
|
401
|
+
{defaultTools.map((tool) => (
|
|
387
402
|
<CommandItem
|
|
388
|
-
key={`tool-${
|
|
389
|
-
onSelect={() => go(
|
|
390
|
-
keywords={commandPaletteKeywords(t.
|
|
403
|
+
key={`tool-${tool.id}`}
|
|
404
|
+
onSelect={() => go(tool.href)}
|
|
405
|
+
keywords={commandPaletteKeywords(t(tool.nameKey), "tool")}
|
|
391
406
|
>
|
|
392
407
|
<IconTool className="me-2 h-4 w-4 text-muted-foreground" />
|
|
393
|
-
{t.
|
|
408
|
+
{t(tool.nameKey)}
|
|
394
409
|
</CommandItem>
|
|
395
410
|
))}
|
|
396
411
|
</CommandGroup>
|
|
397
412
|
|
|
398
|
-
<CommandGroup heading="
|
|
413
|
+
<CommandGroup heading={t("commandPalette.groupAppearance")}>
|
|
399
414
|
<CommandItem
|
|
400
415
|
onSelect={() => {
|
|
401
416
|
const nextTheme = isDark ? "light" : "dark";
|
|
@@ -409,18 +424,20 @@ export function CommandPalette() {
|
|
|
409
424
|
) : (
|
|
410
425
|
<IconMoon className="me-2 h-4 w-4 text-muted-foreground" />
|
|
411
426
|
)}
|
|
412
|
-
|
|
427
|
+
{isDark
|
|
428
|
+
? t("commandPalette.toggleLightMode")
|
|
429
|
+
: t("commandPalette.toggleDarkMode")}
|
|
413
430
|
</CommandItem>
|
|
414
431
|
</CommandGroup>
|
|
415
432
|
|
|
416
|
-
<CommandGroup heading="
|
|
433
|
+
<CommandGroup heading={t("commandPalette.groupHelp")}>
|
|
417
434
|
<CommandItem
|
|
418
435
|
onSelect={() => {
|
|
419
436
|
setOpen(false);
|
|
420
437
|
setChangelogOpen(true);
|
|
421
438
|
}}
|
|
422
439
|
keywords={commandPaletteKeywords(
|
|
423
|
-
"
|
|
440
|
+
t("commandPalette.whatsNew"),
|
|
424
441
|
"changelog",
|
|
425
442
|
"updates",
|
|
426
443
|
"release notes",
|
|
@@ -428,16 +445,19 @@ export function CommandPalette() {
|
|
|
428
445
|
)}
|
|
429
446
|
>
|
|
430
447
|
<IconHistory className="me-2 h-4 w-4 text-muted-foreground" />
|
|
431
|
-
|
|
448
|
+
{t("commandPalette.whatsNew")}
|
|
432
449
|
</CommandItem>
|
|
433
450
|
</CommandGroup>
|
|
434
451
|
|
|
435
452
|
{savedChartsFetching && savedCharts.length === 0 && (
|
|
436
|
-
<CommandLoadingGroup
|
|
453
|
+
<CommandLoadingGroup
|
|
454
|
+
heading={t("commandPalette.groupSavedCharts")}
|
|
455
|
+
rows={2}
|
|
456
|
+
/>
|
|
437
457
|
)}
|
|
438
458
|
|
|
439
459
|
{savedCharts.length > 0 && (
|
|
440
|
-
<CommandGroup heading="
|
|
460
|
+
<CommandGroup heading={t("commandPalette.groupSavedCharts")}>
|
|
441
461
|
{savedCharts.map((c) => (
|
|
442
462
|
<CommandItem
|
|
443
463
|
key={`chart-${c.id}`}
|