@pitcher/js-api 1.28.0 → 1.30.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.
Files changed (90) hide show
  1. package/js-api.esm.js +477 -44
  2. package/js-api.esm.js.map +1 -1
  3. package/js-api.umd.min.js +32 -11
  4. package/js-api.umd.min.js.map +1 -1
  5. package/lib/ai-tasks/__tests__/weekly-focus.spec.d.ts +1 -0
  6. package/lib/ai-tasks/weekly-focus.d.ts +241 -0
  7. package/lib/apps/browser/appTypes.d.ts +28 -0
  8. package/lib/apps/browser/stores/api.d.ts +6 -1
  9. package/lib/apps/browser/stores/app.d.ts +5 -0
  10. package/lib/apps/browser/stores/app.selectionPruneScope.test.d.ts +1 -0
  11. package/lib/apps/browser/stores/upload.d.ts +26 -1
  12. package/lib/apps/browser/stores/upload.types.d.ts +9 -0
  13. package/lib/apps/browser/util/lifecycle.d.ts +57 -0
  14. package/lib/apps/browser/util/lifecycle.test.d.ts +1 -0
  15. package/lib/apps/canvas-builder/components/ui/DynamicContent/useDynamicContent.d.ts +2 -0
  16. package/lib/apps/canvas-builder/components/ui/Multimedia/Multimedia.ldAsset.spec.d.ts +1 -0
  17. package/lib/apps/canvas-builder/composables/useCanvas.d.ts +203 -1
  18. package/lib/apps/canvas-builder/composables/useCanvas.saveEcho.spec.d.ts +1 -0
  19. package/lib/apps/canvas-builder/composables/useCanvasBlocks.d.ts +36 -0
  20. package/lib/apps/canvas-builder/composables/useCanvasExit.d.ts +1 -0
  21. package/lib/apps/canvas-builder/composables/useCanvasHistory.d.ts +36 -0
  22. package/lib/apps/canvas-builder/composables/useCanvasTheme.d.ts +20 -18
  23. package/lib/apps/canvas-builder/composables/useContentSelector.d.ts +8 -1
  24. package/lib/apps/canvas-builder/composables/useInstalledEmbeddables.d.ts +1 -1
  25. package/lib/apps/canvas-builder/composables/useNavigatorDrawer.d.ts +2 -0
  26. package/lib/apps/canvas-builder/composables/usePopupApps.d.ts +2 -0
  27. package/lib/apps/canvas-builder/types/canvas.d.ts +53 -3
  28. package/lib/apps/canvas-builder/util/canvas.util.d.ts +8 -0
  29. package/lib/apps/canvas-builder/util/completionWizard.util.d.ts +107 -0
  30. package/lib/apps/canvas-builder/util/completionWizard.util.test.d.ts +1 -0
  31. package/lib/apps/canvas-builder/util/fullscreenMode.util.d.ts +7 -0
  32. package/lib/apps/canvas-builder/util/fullscreenMode.util.test.d.ts +1 -0
  33. package/lib/apps/canvas-builder/util/lazySections.util.d.ts +35 -0
  34. package/lib/apps/canvas-builder/util/lazySections.util.test.d.ts +1 -0
  35. package/lib/apps/collection-player/composables/collection-navigation.util.d.ts +27 -0
  36. package/lib/apps/collection-player/composables/collection-navigation.util.spec.d.ts +1 -0
  37. package/lib/apps/collection-player/stores/app.store.d.ts +3 -0
  38. package/lib/apps/content-selector/components/Assets.uploaderRefresh.spec.d.ts +1 -0
  39. package/lib/apps/content-selector/stores/app.d.ts +1 -1
  40. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.annotations.use.d.ts +5 -1
  41. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.formFields.use.d.ts +8 -0
  42. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.link.use.d.ts +1 -1
  43. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.overlayItems.use.d.ts +1 -1
  44. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.use.d.ts +4 -9
  45. package/lib/components/CFileViewer/pdf/CFileViewer.pdf.util.d.ts +1 -1
  46. package/lib/components/CFileViewer/pdf/CFileViewerFullscreen.pdf.use.d.ts +1 -1
  47. package/lib/components/CFileViewer/pdf/__tests__/CFileViewer.pdf.annotations.text.test.d.ts +1 -0
  48. package/lib/components/savedCanvases/CSavedCanvasesManagement.use.d.ts +1 -0
  49. package/lib/composables/useCourseOnlyApps.d.ts +1 -1
  50. package/lib/composables/usePdfInterpolation.d.ts +2 -2
  51. package/lib/composables/useWindowEvents.d.ts +1 -1
  52. package/lib/constants/cdp.const.d.ts +1 -0
  53. package/lib/constants/translationLanguages.const.d.ts +72 -0
  54. package/lib/main.lib.d.ts +2 -0
  55. package/lib/sdk/api/HighLevelApi.d.ts +26 -9
  56. package/lib/sdk/api/modules/admin/types.admin.d.ts +1 -0
  57. package/lib/sdk/api/modules/ai.d.ts +43 -1
  58. package/lib/sdk/api/modules/ai.spec.d.ts +1 -0
  59. package/lib/sdk/api/modules/canvas.d.ts +8 -0
  60. package/lib/sdk/api/modules/index.d.ts +2 -1
  61. package/lib/sdk/api/modules/stt.d.ts +65 -0
  62. package/lib/sdk/api/modules/stt.spec.d.ts +1 -0
  63. package/lib/sdk/api/modules/tts.d.ts +41 -0
  64. package/lib/sdk/api/modules/tts.spec.d.ts +1 -0
  65. package/lib/sdk/api/modules/ui/types.ui.d.ts +7 -0
  66. package/lib/sdk/interfaces.d.ts +26 -1
  67. package/lib/sdk/main.d.ts +35 -6
  68. package/lib/sdk/payload.types.d.ts +284 -0
  69. package/lib/types/ai-tasks.const.d.ts +39 -1
  70. package/lib/types/call.d.ts +14 -0
  71. package/lib/types/canvases.d.ts +0 -3
  72. package/lib/types/launchDarkly.types.d.ts +1 -3
  73. package/lib/types/organizationSettings.types.d.ts +10 -0
  74. package/lib/types/sfdc.d.ts +15 -0
  75. package/lib/util/soql.util.d.ts +36 -0
  76. package/lib/util/user.d.ts +18 -0
  77. package/lib/util/user.test.d.ts +1 -0
  78. package/package.json +1 -1
  79. package/types/openapi/index.d.ts +0 -1
  80. package/types/openapi/models/DeletedEnvironment.d.ts +0 -1
  81. package/types/openapi/models/Environment.d.ts +0 -1
  82. package/types/openapi/models/EnvironmentRequest.d.ts +0 -1
  83. package/types/openapi/models/File.d.ts +2 -0
  84. package/types/openapi/models/FileRetrieve.d.ts +2 -0
  85. package/types/openapi/models/PatchedEnvironmentRequest.d.ts +0 -1
  86. package/types/openapi/models/PatchedUserRequest.d.ts +1 -2
  87. package/types/openapi/models/User.d.ts +1 -2
  88. package/types/openapi/models/UserRequest.d.ts +1 -2
  89. package/lib/sdk/api/modules/languages.d.ts +0 -8
  90. package/types/openapi/models/LanguageEnum.d.ts +0 -12
package/js-api.esm.js CHANGED
@@ -2385,6 +2385,261 @@ function resolvePiaSearchMode(config) {
2385
2385
  return "off";
2386
2386
  }
2387
2387
 
2388
+ const WINDOW_DAYS = 6;
2389
+ const MAX_RANK_MEETINGS = 12;
2390
+ const HERO_MAX_CHARS = 400;
2391
+ const REASON_MAX_CHARS = 140;
2392
+ const DEFAULT_MAX_PICKS = 5;
2393
+ const TITLE_MAX_CHARS = 70;
2394
+ const ACCOUNT_NAME_MAX_CHARS = 60;
2395
+ const SECTION_LABEL_MAX_CHARS = 40;
2396
+ const FIELD_LABEL_MAX_CHARS = 30;
2397
+ const FIELD_VALUE_MAX_CHARS = 60;
2398
+ const WEEKLY_FOCUS_RANKING_RULES = `You rank a sales rep's upcoming meetings by how much they need preparation.
2399
+
2400
+ Below are {{meeting_count}} numbered meetings, each with its account's CRM context (deals, tasks, recent calls).
2401
+
2402
+ Pick the {{max_picks}} most important. Pick fewer only if the list has fewer meetings.
2403
+
2404
+ Rules:
2405
+ - Highest priority first; ties go to the earlier meeting.
2406
+ - Strongest signals: an overdue or due-soon high-priority task, a poorly rated recent interaction, a deal closing within days. Routine meetings on healthy accounts rank last.
2407
+ - {{account_rule}}
2408
+ - Each "reason": ONE sentence, max {{reason_max_chars}} characters, only facts from the data — name the deal, task, rating, or date that makes it matter.
2409
+ - "hero": 1-2 sentences, max {{hero_max_chars}} characters, summarizing the week in plain business language.
2410
+ - Never invent facts. Never repeat field names, codes, or IDs.`;
2411
+ const ONE_PER_ACCOUNT_RULE = "Pick at most ONE meeting per account: when an account has several meetings, pick only its most important one.";
2412
+ const MULTI_PER_ACCOUNT_RULE = "Prefer spreading picks across different accounts, but you may pick two meetings on the same account when each is genuinely high priority on its own.";
2413
+ function effectiveMaxPicks(maxPicks) {
2414
+ if (typeof maxPicks !== "number" || !Number.isFinite(maxPicks)) return DEFAULT_MAX_PICKS;
2415
+ return Math.min(8, Math.max(1, Math.floor(maxPicks)));
2416
+ }
2417
+ function clamp(value, max) {
2418
+ const trimmed = value.trim();
2419
+ return trimmed.length > max ? trimmed.slice(0, max - 1).trimEnd() + "…" : trimmed;
2420
+ }
2421
+ function fill(template2, tokens) {
2422
+ return template2.replace(
2423
+ /\{\{(\w+)\}\}/g,
2424
+ (match, key) => Object.prototype.hasOwnProperty.call(tokens, key) ? tokens[key] : match
2425
+ );
2426
+ }
2427
+ const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
2428
+ function startOfDayUtcMs(date, timeZone) {
2429
+ if (!timeZone) return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
2430
+ const parts = new Intl.DateTimeFormat("en-US", {
2431
+ timeZone,
2432
+ year: "numeric",
2433
+ month: "2-digit",
2434
+ day: "2-digit"
2435
+ }).formatToParts(date);
2436
+ const get = (type) => Number(parts.find((p) => p.type === type)?.value);
2437
+ return Date.UTC(get("year"), get("month") - 1, get("day"));
2438
+ }
2439
+ function daysAgo(iso, options) {
2440
+ const now = options?.now ?? /* @__PURE__ */ new Date();
2441
+ const todayMs = startOfDayUtcMs(now, options?.timeZone);
2442
+ const dateOnly = DATE_ONLY.exec(iso);
2443
+ if (dateOnly) {
2444
+ const [y, m, d] = [+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]];
2445
+ const thenMs = Date.UTC(y, m, d);
2446
+ const back = new Date(thenMs);
2447
+ if (back.getUTCFullYear() !== y || back.getUTCMonth() !== m || back.getUTCDate() !== d) return null;
2448
+ return Math.round((todayMs - thenMs) / 864e5);
2449
+ }
2450
+ const then = new Date(iso);
2451
+ if (Number.isNaN(then.getTime())) return null;
2452
+ return Math.round((todayMs - startOfDayUtcMs(then, options?.timeZone)) / 864e5);
2453
+ }
2454
+ function weeklyWindowMeetings(meetings, options) {
2455
+ const now = options?.now ?? /* @__PURE__ */ new Date();
2456
+ const nowMs = now.getTime();
2457
+ return meetings.filter((meeting) => {
2458
+ const days = daysAgo(meeting.meeting_time, { now, timeZone: options?.timeZone });
2459
+ if (days === null) return false;
2460
+ if (!DATE_ONLY.test(meeting.meeting_time)) {
2461
+ const t = new Date(meeting.meeting_time).getTime();
2462
+ if (Number.isNaN(t) || t <= nowMs) return false;
2463
+ }
2464
+ const offset = -days;
2465
+ return offset >= 0 && offset <= WINDOW_DAYS;
2466
+ }).sort((a, b) => sortableTime(a.meeting_time) - sortableTime(b.meeting_time));
2467
+ }
2468
+ function sortableTime(iso) {
2469
+ const t = Date.parse(iso);
2470
+ return Number.isNaN(t) ? Number.POSITIVE_INFINITY : t;
2471
+ }
2472
+ function relativeDays(days) {
2473
+ if (days === 0) return "today";
2474
+ if (days === 1) return "yesterday";
2475
+ if (days < 0) return days === -1 ? "tomorrow" : `in ${-days} days`;
2476
+ return `${days} days ago`;
2477
+ }
2478
+ function formatWhen(iso, timeZone) {
2479
+ if (DATE_ONLY.test(iso)) return iso;
2480
+ const date = new Date(iso);
2481
+ if (Number.isNaN(date.getTime())) return iso;
2482
+ const day = date.toLocaleDateString("en-US", {
2483
+ weekday: "short",
2484
+ month: "short",
2485
+ day: "numeric",
2486
+ ...timeZone && { timeZone }
2487
+ });
2488
+ const time = date.toLocaleTimeString("en-US", {
2489
+ hour: "numeric",
2490
+ minute: "2-digit",
2491
+ ...timeZone && { timeZone }
2492
+ });
2493
+ return `${day} ${time}`.trim();
2494
+ }
2495
+ function meetingBlock(index, meeting, context, options) {
2496
+ const when = formatWhen(meeting.meeting_time, options.timeZone);
2497
+ const offset = -(daysAgo(meeting.meeting_time, options) ?? 0);
2498
+ const day = offset === 0 ? "today" : offset === 1 ? "tomorrow" : `in ${offset} days`;
2499
+ const attendees = meeting.attendee_count ?? 0;
2500
+ const head = `${index}. "${clamp(meeting.meeting_subject, TITLE_MAX_CHARS)}" — ${clamp(meeting.account_name, ACCOUNT_NAME_MAX_CHARS)} — ${when} (${day})` + (attendees > 0 ? ` — ${attendees} attendee${attendees === 1 ? "" : "s"}` : "");
2501
+ const lines = [head];
2502
+ for (const section of context?.sections ?? []) {
2503
+ if (section.items.length === 0) continue;
2504
+ const first = section.items[0];
2505
+ const fieldList = first.fields ?? [];
2506
+ const fields = section.fields_curated && fieldList.length > 0 ? " · " + fieldList.slice(0, 3).map((f) => `${clamp(f.label, FIELD_LABEL_MAX_CHARS)}: ${clamp(f.value, FIELD_VALUE_MAX_CHARS)}`).join(" · ") : "";
2507
+ const age = first.days_ago != null ? ` (${relativeDays(first.days_ago)})` : "";
2508
+ const count = section.items.length > 1 ? `${section.items.length} records; key: ` : "";
2509
+ lines.push(
2510
+ ` ${clamp(section.label, SECTION_LABEL_MAX_CHARS)}: ${count}${clamp(first.title, TITLE_MAX_CHARS)}${fields}${age}`
2511
+ );
2512
+ }
2513
+ return lines.join("\n");
2514
+ }
2515
+ function template(variables) {
2516
+ const meetings = variables.meetings;
2517
+ const context = variables.context ?? {};
2518
+ const maxPicks = effectiveMaxPicks(variables.max_picks);
2519
+ const onePerAccount = variables.one_per_account !== false;
2520
+ const options = { now: variables.now, timeZone: variables.time_zone };
2521
+ const blocks = meetings.map((m, i) => meetingBlock(i + 1, m, context[m.account_id], options)).join("\n");
2522
+ const rules = fill(variables.ranking_rules || WEEKLY_FOCUS_RANKING_RULES, {
2523
+ meeting_count: String(meetings.length),
2524
+ max_picks: String(maxPicks),
2525
+ reason_max_chars: String(REASON_MAX_CHARS),
2526
+ hero_max_chars: String(HERO_MAX_CHARS),
2527
+ account_rule: onePerAccount ? ONE_PER_ACCOUNT_RULE : MULTI_PER_ACCOUNT_RULE
2528
+ });
2529
+ return `${rules}
2530
+
2531
+ Answer with JSON ONLY — no other text. The object has exactly two keys:
2532
+ "hero": the summary as one string.
2533
+ "top": an array with one entry per picked meeting, highest priority first. Each entry is an object with "m" (the picked meeting's number from the list below, as an integer) and "reason" (one sentence as a string).
2534
+
2535
+ MEETINGS:
2536
+ ${blocks}`;
2537
+ }
2538
+ function tryParse(slice) {
2539
+ try {
2540
+ return JSON.parse(slice);
2541
+ } catch {
2542
+ const repaired = slice.replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/,\s*([}\]])/g, "$1");
2543
+ try {
2544
+ return JSON.parse(repaired);
2545
+ } catch {
2546
+ return null;
2547
+ }
2548
+ }
2549
+ }
2550
+ function looksLikeResponse(o) {
2551
+ return Array.isArray(o.top);
2552
+ }
2553
+ function extractJson(raw) {
2554
+ let fallback = null;
2555
+ for (let start = raw.indexOf("{"); start !== -1; start = raw.indexOf("{", start + 1)) {
2556
+ let depth = 0;
2557
+ let inString = false;
2558
+ let escaped = false;
2559
+ for (let i = start; i < raw.length; i++) {
2560
+ const ch = raw[i];
2561
+ if (inString) {
2562
+ if (escaped) escaped = false;
2563
+ else if (ch === "\\") escaped = true;
2564
+ else if (ch === '"') inString = false;
2565
+ } else if (ch === '"') {
2566
+ inString = true;
2567
+ } else if (ch === "{") {
2568
+ depth += 1;
2569
+ } else if (ch === "}") {
2570
+ depth -= 1;
2571
+ if (depth === 0) {
2572
+ const parsed = tryParse(raw.slice(start, i + 1));
2573
+ if (parsed) {
2574
+ if (looksLikeResponse(parsed)) return parsed;
2575
+ if (!fallback) fallback = parsed;
2576
+ }
2577
+ break;
2578
+ }
2579
+ }
2580
+ }
2581
+ }
2582
+ return fallback;
2583
+ }
2584
+ function normalizeWeeklyFocus(parsed) {
2585
+ const raw = parsed ?? {};
2586
+ const top = [];
2587
+ if (Array.isArray(raw.top)) {
2588
+ for (const pick of raw.top) {
2589
+ const m = typeof pick?.m === "number" ? pick.m : Number(pick?.m);
2590
+ const reason = typeof pick?.reason === "string" ? pick.reason.trim() : "";
2591
+ if (!Number.isInteger(m) || !reason) continue;
2592
+ top.push({ m, reason: clamp(reason, REASON_MAX_CHARS) });
2593
+ }
2594
+ }
2595
+ const hero = typeof raw.hero === "string" ? clamp(raw.hero, HERO_MAX_CHARS) : "";
2596
+ return { hero, top };
2597
+ }
2598
+ function extractWeeklyFocus(rawText) {
2599
+ const parsed = extractJson(rawText ?? "");
2600
+ if (!parsed) return { hero: "", top: [] };
2601
+ return normalizeWeeklyFocus(parsed);
2602
+ }
2603
+ function resolveWeeklyFocusPicks(output, rankedMeetings, options = {}) {
2604
+ const maxPicks = effectiveMaxPicks(options.maxPicks);
2605
+ const onePerAccount = options.onePerAccount !== false;
2606
+ const windowCount = options.windowCount ?? rankedMeetings.length;
2607
+ const seen = /* @__PURE__ */ new Set();
2608
+ const pickedAccounts = /* @__PURE__ */ new Set();
2609
+ const top = [];
2610
+ let dropped = 0;
2611
+ for (const pick of output.top ?? []) {
2612
+ const meeting = Number.isInteger(pick.m) ? rankedMeetings[pick.m - 1] : void 0;
2613
+ const reason = typeof pick.reason === "string" ? pick.reason.trim() : "";
2614
+ if (!meeting || !reason || seen.has(meeting.event_id)) {
2615
+ dropped += 1;
2616
+ continue;
2617
+ }
2618
+ if (onePerAccount && pickedAccounts.has(meeting.account_id)) {
2619
+ dropped += 1;
2620
+ continue;
2621
+ }
2622
+ seen.add(meeting.event_id);
2623
+ pickedAccounts.add(meeting.account_id);
2624
+ top.push({ event_id: meeting.event_id, rank: top.length + 1, reason: clamp(reason, REASON_MAX_CHARS) });
2625
+ if (top.length >= maxPicks) break;
2626
+ }
2627
+ return { top, collapsed_count: Math.max(0, windowCount - top.length), dropped };
2628
+ }
2629
+ function weeklyFocusTarget(rankedMeetings, options = {}) {
2630
+ const onePerAccount = options.onePerAccount !== false;
2631
+ const ceiling = onePerAccount ? new Set(rankedMeetings.map((m) => m.account_id)).size : rankedMeetings.length;
2632
+ return Math.min(effectiveMaxPicks(options.maxPicks), ceiling);
2633
+ }
2634
+ function prepareWeeklyFocusMeetings(meetings, options) {
2635
+ const windowMeetings = weeklyWindowMeetings(meetings, options);
2636
+ return {
2637
+ windowMeetings,
2638
+ rankedMeetings: windowMeetings.slice(0, MAX_RANK_MEETINGS),
2639
+ windowCount: windowMeetings.length
2640
+ };
2641
+ }
2642
+
2388
2643
  const PIA_SEARCH_ROUTE = "/core/api/protected/pia-prepare/pia-search";
2389
2644
  const CAPABILITIES_TIMEOUT_MS = 3e3;
2390
2645
  const ON_DEVICE_MAX_TOKENS = 512;
@@ -2419,6 +2674,32 @@ function apiOriginFromEnv(env) {
2419
2674
  if (typeof orgDomain === "string" && orgDomain.length > 0) return normalizeOrigin(orgDomain);
2420
2675
  throw new Error("piaSearchAnswer: no customer domain found in env (token_claims / organization metadata)");
2421
2676
  }
2677
+ async function postJsonWithTimeout(url, body, accessToken, timeoutMs, label) {
2678
+ const controller = new AbortController();
2679
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2680
+ try {
2681
+ const response = await fetch(url, {
2682
+ method: "POST",
2683
+ signal: controller.signal,
2684
+ headers: {
2685
+ "Content-Type": "application/json",
2686
+ Authorization: `Bearer ${accessToken}`
2687
+ },
2688
+ body: JSON.stringify(body)
2689
+ });
2690
+ if (!response.ok) {
2691
+ throw new Error(`${label} ${response.status}`);
2692
+ }
2693
+ return await response.json();
2694
+ } catch (error) {
2695
+ if (error?.name === "AbortError") {
2696
+ throw new Error(`${label} online request timed out after ${timeoutMs}ms`);
2697
+ }
2698
+ throw error;
2699
+ } finally {
2700
+ clearTimeout(timer);
2701
+ }
2702
+ }
2422
2703
  function buildOnDevicePrompt(query, contextText) {
2423
2704
  const context = typeof contextText === "string" && contextText.trim().length > 0 ? contextText.trim() : "No content context was provided.";
2424
2705
  return `You are PIA, a sales content assistant. Answer the rep's question using ONLY the CONTEXT below — do not invent content that is not in it.
@@ -2428,7 +2709,7 @@ ${context}
2428
2709
 
2429
2710
  QUESTION: ${query}
2430
2711
 
2431
- Answer concisely in plain text. If the context does not contain the answer, say you could not find it in the available content.`;
2712
+ Answer concisely in plain text. If the context does not contain the answer, say you could not find it in the available content. Refer to content by its name only — do not include any file ids or codes in your answer.`;
2432
2713
  }
2433
2714
  async function aiGetCapabilities() {
2434
2715
  const env = await highLevelApi.API.request("get_env");
@@ -2474,41 +2755,126 @@ async function piaSearchAnswer(payload) {
2474
2755
  if (!instanceId) {
2475
2756
  throw new Error("piaSearchAnswer: instance_id could not be resolved from the payload or env");
2476
2757
  }
2477
- const controller = new AbortController();
2478
- const timer = setTimeout(() => controller.abort(), ONLINE_TIMEOUT_MS);
2479
- try {
2480
- const response = await fetch(`${apiOriginFromEnv(env)}${PIA_SEARCH_ROUTE}`, {
2481
- method: "POST",
2482
- signal: controller.signal,
2483
- headers: {
2484
- "Content-Type": "application/json",
2485
- Authorization: `Bearer ${env.pitcher.access_token}`
2486
- },
2487
- body: JSON.stringify({
2488
- query: payload.query,
2489
- instance_id: instanceId,
2490
- ...Array.isArray(payload.file_ids) && payload.file_ids.length > 0 && { file_ids: payload.file_ids }
2491
- })
2492
- });
2493
- if (!response.ok) {
2494
- throw new Error(`pia_search ${response.status}`);
2495
- }
2496
- const data = await response.json();
2497
- const output = data?.pia_search ?? {};
2498
- return {
2499
- answer: typeof output.answer === "string" ? output.answer : "",
2500
- cited_file_ids: Array.isArray(output.cited_file_ids) ? output.cited_file_ids : [],
2501
- ...output.confidence && { confidence: output.confidence },
2502
- source: "online"
2503
- };
2504
- } catch (error) {
2505
- if (error?.name === "AbortError") {
2506
- throw new Error(`pia_search online request timed out after ${ONLINE_TIMEOUT_MS}ms`);
2758
+ const data = await postJsonWithTimeout(
2759
+ `${apiOriginFromEnv(env)}${PIA_SEARCH_ROUTE}`,
2760
+ {
2761
+ query: payload.query,
2762
+ instance_id: instanceId,
2763
+ ...Array.isArray(payload.file_ids) && payload.file_ids.length > 0 && { file_ids: payload.file_ids },
2764
+ ...payload.include_document_text === true && { include_document_text: true },
2765
+ ...Array.isArray(payload.expansion_terms) && payload.expansion_terms.length > 0 && { expansion_terms: payload.expansion_terms }
2766
+ },
2767
+ env.pitcher.access_token,
2768
+ ONLINE_TIMEOUT_MS,
2769
+ "pia_search"
2770
+ );
2771
+ const output = data?.pia_search ?? {};
2772
+ return {
2773
+ answer: typeof output.answer === "string" ? output.answer : "",
2774
+ cited_file_ids: Array.isArray(output.cited_file_ids) ? output.cited_file_ids : [],
2775
+ ...output.confidence && { confidence: output.confidence },
2776
+ source: "online"
2777
+ };
2778
+ }
2779
+ const WEEKLY_FOCUS_ROUTE = "/core/api/protected/pia-prepare/weekly-focus";
2780
+ const weeklyFocusOutputReserve = (maxPicks) => 220 + 60 * maxPicks;
2781
+ const ON_DEVICE_COMPLETE_TIMEOUT_MS = 12e4;
2782
+ async function weeklyFocusRank(payload) {
2783
+ if (!payload || !Array.isArray(payload.meetings)) {
2784
+ return Promise.reject(new Error("meetings is required and must be an array"));
2785
+ }
2786
+ const env = await highLevelApi.API.request("get_env");
2787
+ const timeZone = payload.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
2788
+ const { windowMeetings, rankedMeetings, windowCount } = prepareWeeklyFocusMeetings(payload.meetings, { timeZone });
2789
+ if (rankedMeetings.length === 0) {
2790
+ return { hero: "", top: [], collapsed_count: 0, source: env?.mode === "IOS" ? "on_device" : "online" };
2791
+ }
2792
+ const maxPicks = effectiveMaxPicks(payload.max_picks);
2793
+ const resolveOptions = {
2794
+ maxPicks,
2795
+ onePerAccount: payload.one_per_account,
2796
+ windowCount
2797
+ };
2798
+ if (env?.mode === "IOS") {
2799
+ try {
2800
+ const capabilities = await withTimeout(
2801
+ highLevelApi.API.request("ai.get_capabilities"),
2802
+ CAPABILITIES_TIMEOUT_MS,
2803
+ "ai.get_capabilities"
2804
+ );
2805
+ if (capabilities?.available) {
2806
+ const prompt = template({
2807
+ meetings: rankedMeetings,
2808
+ context: payload.context,
2809
+ max_picks: maxPicks,
2810
+ one_per_account: payload.one_per_account,
2811
+ time_zone: timeZone
2812
+ });
2813
+ const runOnce = async () => {
2814
+ const completion = await withTimeout(
2815
+ aiComplete({ prompt, max_tokens: weeklyFocusOutputReserve(maxPicks) }),
2816
+ ON_DEVICE_COMPLETE_TIMEOUT_MS,
2817
+ "ai.complete"
2818
+ );
2819
+ const parsed2 = extractWeeklyFocus(completion.text);
2820
+ return { parsed: parsed2, resolved: resolveWeeklyFocusPicks(parsed2, rankedMeetings, resolveOptions) };
2821
+ };
2822
+ let attempt = await runOnce();
2823
+ if (attempt.resolved.top.length < weeklyFocusTarget(rankedMeetings, resolveOptions)) {
2824
+ try {
2825
+ const second = await runOnce();
2826
+ if (second.resolved.top.length > attempt.resolved.top.length) attempt = second;
2827
+ } catch (error) {
2828
+ console.warn("[weeklyFocusRank] on-device retry failed, keeping the first attempt:", error);
2829
+ }
2830
+ }
2831
+ const { parsed, resolved } = attempt;
2832
+ if (resolved.top.length > 0 || parsed.hero) {
2833
+ return {
2834
+ hero: parsed.hero,
2835
+ top: resolved.top,
2836
+ collapsed_count: resolved.collapsed_count,
2837
+ source: "on_device"
2838
+ };
2839
+ }
2840
+ }
2841
+ } catch (error) {
2842
+ console.warn("[weeklyFocusRank] on-device path unavailable, falling back to online:", error);
2507
2843
  }
2508
- throw error;
2509
- } finally {
2510
- clearTimeout(timer);
2511
2844
  }
2845
+ const instanceId = payload.instance_id || env?.pitcher?.instance?.id;
2846
+ if (!instanceId) {
2847
+ throw new Error("weeklyFocusRank: instance_id could not be resolved from the payload or env");
2848
+ }
2849
+ const data = await postJsonWithTimeout(
2850
+ `${apiOriginFromEnv(env)}${WEEKLY_FOCUS_ROUTE}`,
2851
+ {
2852
+ instance_id: instanceId,
2853
+ // Send the WINDOW, not the caller's whole calendar. The route re-windows
2854
+ // anyway (idempotent — same rule, same zone), so this only drops meetings
2855
+ // it would have dropped, while keeping `collapsed_count` correct: the route
2856
+ // derives the window size from what it receives, so sending the
2857
+ // already-capped 12 would understate it. It also keeps a caller with a long
2858
+ // history from tripping the route's 500-meeting body cap over meetings
2859
+ // outside the week entirely.
2860
+ meetings: windowMeetings,
2861
+ time_zone: timeZone,
2862
+ ...payload.context && { context: payload.context },
2863
+ // Clamped, not raw: the route 400s on out-of-range values.
2864
+ ...payload.max_picks !== void 0 && { max_picks: maxPicks },
2865
+ ...payload.one_per_account !== void 0 && { one_per_account: payload.one_per_account }
2866
+ },
2867
+ env.pitcher.access_token,
2868
+ ONLINE_TIMEOUT_MS,
2869
+ "weekly_focus"
2870
+ );
2871
+ const output = data?.weekly_focus ?? {};
2872
+ return {
2873
+ hero: typeof output.hero === "string" ? output.hero : "",
2874
+ top: Array.isArray(output.top) ? output.top : [],
2875
+ collapsed_count: typeof output.collapsed_count === "number" ? output.collapsed_count : 0,
2876
+ source: "online"
2877
+ };
2512
2878
  }
2513
2879
 
2514
2880
  const APPS_DB_BASE_PATH = "/core/api/protected/appsdb";
@@ -2773,10 +3139,6 @@ function isOffline() {
2773
3139
  return this.API.request("is_offline");
2774
3140
  }
2775
3141
 
2776
- function getLanguages() {
2777
- return this.API.request("get_languages");
2778
- }
2779
-
2780
3142
  function getCanvases$1(payload) {
2781
3143
  return this.API.request("get_canvases", payload);
2782
3144
  }
@@ -3760,6 +4122,65 @@ function assignCanvasTheme(payload) {
3760
4122
  return this.API.request("assign_canvas_theme", payload);
3761
4123
  }
3762
4124
 
4125
+ const STT_ERROR_CODES = [
4126
+ "STT_DISABLED",
4127
+ "STT_DEVICE_UNSUPPORTED",
4128
+ "STT_MODEL_NOT_READY",
4129
+ "STT_BUSY",
4130
+ "STT_MIC_PERMISSION_DENIED",
4131
+ "STT_NOT_RECORDING",
4132
+ "STT_CAPTURE_FAILED",
4133
+ "STT_ERROR"
4134
+ ];
4135
+ function asSttUnsupportedError(error) {
4136
+ const marker = "requestTypeDoesNotExists";
4137
+ const e = error;
4138
+ const isUnsupported = e?.error_code === marker || e?.errorCode === marker || e?.code === marker || typeof e?.reason === "string" && e.reason.includes(marker) || typeof e?.message === "string" && e.message.includes(marker);
4139
+ return isUnsupported ? new Error("STT_DEVICE_UNSUPPORTED: dictation is not supported by this app version") : error;
4140
+ }
4141
+ function sttStart(payload) {
4142
+ return this.API.request("stt.start", payload).catch((error) => {
4143
+ throw asSttUnsupportedError(error);
4144
+ });
4145
+ }
4146
+ function sttAvailability() {
4147
+ return this.API.request("stt.availability");
4148
+ }
4149
+ function sttWarmup() {
4150
+ return this.API.request("stt.warmup");
4151
+ }
4152
+ function sttStop(payload) {
4153
+ return this.API.request("stt.stop", payload).catch((error) => {
4154
+ throw asSttUnsupportedError(error);
4155
+ });
4156
+ }
4157
+ function sttErrorCode(error) {
4158
+ const e = error;
4159
+ const direct = e?.code ?? e?.error_code ?? e?.errorCode;
4160
+ if (typeof direct === "string" && STT_ERROR_CODES.includes(direct)) {
4161
+ return direct;
4162
+ }
4163
+ const text = typeof e?.reason === "string" ? e.reason : typeof e?.message === "string" ? e.message : "";
4164
+ return STT_ERROR_CODES.find((code) => text.includes(code));
4165
+ }
4166
+
4167
+ const TTS_ERROR_CODES = ["TTS_EMPTY_TEXT", "TTS_VOICE_UNAVAILABLE", "TTS_ERROR"];
4168
+ function ttsSpeak(payload) {
4169
+ return this.API.request("tts.speak", payload);
4170
+ }
4171
+ function ttsStop() {
4172
+ return this.API.request("tts.stop");
4173
+ }
4174
+ function ttsErrorCode(error) {
4175
+ const e = error;
4176
+ const direct = e?.code ?? e?.error_code ?? e?.errorCode;
4177
+ if (typeof direct === "string" && TTS_ERROR_CODES.includes(direct)) {
4178
+ return direct;
4179
+ }
4180
+ const text = typeof e?.reason === "string" ? e.reason : typeof e?.message === "string" ? e.message : "";
4181
+ return TTS_ERROR_CODES.find((code) => text.includes(code));
4182
+ }
4183
+
3763
4184
  const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3764
4185
  __proto__: null,
3765
4186
  aiComplete,
@@ -3805,7 +4226,6 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3805
4226
  getFolder,
3806
4227
  getFolders,
3807
4228
  getInstanceMetadataTemplates: getInstanceMetadataTemplates$1,
3808
- getLanguages,
3809
4229
  getSectionsByIds,
3810
4230
  getThemes,
3811
4231
  getUsers: getUsers$1,
@@ -3817,6 +4237,7 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3817
4237
  openWebViewAlwaysOnTop,
3818
4238
  patchCoreFolder,
3819
4239
  piaSearchAnswer,
4240
+ postJsonWithTimeout,
3820
4241
  query,
3821
4242
  refreshAccessToken,
3822
4243
  refreshServiceToken,
@@ -3827,16 +4248,25 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
3827
4248
  shareCanvas,
3828
4249
  showPeerSession,
3829
4250
  showSyncbox,
4251
+ sttAvailability,
4252
+ sttErrorCode,
4253
+ sttStart,
4254
+ sttStop,
4255
+ sttWarmup,
3830
4256
  submitUserFeedback,
3831
4257
  toast: toast$2,
3832
4258
  track,
3833
4259
  triggerNonFilesSync,
4260
+ ttsErrorCode,
4261
+ ttsSpeak,
4262
+ ttsStop,
3834
4263
  unassignCanvasTheme,
3835
4264
  updateCanvas: updateCanvas$2,
3836
4265
  updateCanvasIndicators,
3837
4266
  updateFile,
3838
4267
  updateFolder,
3839
- updateMyUser
4268
+ updateMyUser,
4269
+ weeklyFocusRank
3840
4270
  }, Symbol.toStringTag, { value: 'Module' }));
3841
4271
 
3842
4272
  class EventEmitter {
@@ -3947,6 +4377,8 @@ var PitcherEventName = /* @__PURE__ */ ((PitcherEventName2) => {
3947
4377
  PitcherEventName2["SYNC_BADGE_VALUE_REPORTED"] = "sync_badge_value_reported";
3948
4378
  PitcherEventName2["SYNC_BOX_RESOLVE_ERROR_TAPPED"] = "sync_box_resolve_error_tapped";
3949
4379
  PitcherEventName2["PEER_CONNECTIVITY_EVENT"] = "peer_connectivity_event";
4380
+ PitcherEventName2["STT_PARTIAL"] = "stt.partial";
4381
+ PitcherEventName2["STT_INTERRUPTED"] = "stt.interrupted";
3950
4382
  return PitcherEventName2;
3951
4383
  })(PitcherEventName || {});
3952
4384
  var PitcherBroadcastedEventName = /* @__PURE__ */ ((PitcherBroadcastedEventName2) => {
@@ -4316,16 +4748,17 @@ const onlineRestApiHandlers = {
4316
4748
  },
4317
4749
  update_canvas: async (params) => {
4318
4750
  check();
4319
- const { id, fields, ...canvasData } = params;
4751
+ const { id, fields, lazy_sections, ...canvasData } = params;
4752
+ const lazyQs = lazy_sections === true ? "lazy_sections=true" : "";
4320
4753
  const useCoreEndpoint = env?.launch_darkly?.enable_canvas_core_patch === true;
4321
4754
  if (useCoreEndpoint) {
4322
4755
  const origin = env.pitcher.token_claims["https://pitcher.com/claims/urls"].custom_domain;
4323
- const fieldsQs = fields ? `?fields=${encodeURIComponent(fields)}` : "";
4324
- const url = `${origin}/core/api/protected/v1/canvases/${id}${fieldsQs}`;
4756
+ const qs = [fields ? `fields=${encodeURIComponent(fields)}` : "", lazyQs].filter(Boolean).join("&");
4757
+ const url = `${origin}/core/api/protected/v1/canvases/${id}${qs ? `?${qs}` : ""}`;
4325
4758
  const res2 = await http.patchUrl(url, canvasData);
4326
4759
  return res2;
4327
4760
  }
4328
- const res = await http.patch(`/canvases/${id}`, { fields, ...canvasData });
4761
+ const res = await http.patch(`/canvases/${id}${lazyQs ? `?${lazyQs}` : ""}`, { fields, ...canvasData });
4329
4762
  return res;
4330
4763
  },
4331
4764
  delete_canvas: async (params) => {