@devicai/ui 0.40.2 → 0.41.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 (74) hide show
  1. package/README.md +74 -1
  2. package/dist/cjs/api/client.js +125 -6
  3. package/dist/cjs/api/client.js.map +1 -1
  4. package/dist/cjs/components/AICommandBar/AICommandBar.js +4 -2
  5. package/dist/cjs/components/AICommandBar/AICommandBar.js.map +1 -1
  6. package/dist/cjs/components/AICommandBar/useAICommandBar.js +4 -2
  7. package/dist/cjs/components/AICommandBar/useAICommandBar.js.map +1 -1
  8. package/dist/cjs/components/AIElementWrapper/useAIElementWrapper.js +4 -2
  9. package/dist/cjs/components/AIElementWrapper/useAIElementWrapper.js.map +1 -1
  10. package/dist/cjs/components/AIGenerationButton/useAIGenerationButton.js +4 -2
  11. package/dist/cjs/components/AIGenerationButton/useAIGenerationButton.js.map +1 -1
  12. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +18 -9
  13. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  14. package/dist/cjs/components/ChatDrawer/ChatInput.js +9 -3
  15. package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
  16. package/dist/cjs/components/ChatDrawer/ChatMessages.js +9 -3
  17. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  18. package/dist/cjs/components/ChatDrawer/ConversationSelector.js +9 -7
  19. package/dist/cjs/components/ChatDrawer/ConversationSelector.js.map +1 -1
  20. package/dist/cjs/components/ChatDrawer/HandoffSubagentWidget.js +10 -3
  21. package/dist/cjs/components/ChatDrawer/HandoffSubagentWidget.js.map +1 -1
  22. package/dist/cjs/components/ChatDrawer/UsageBar.js +8 -2
  23. package/dist/cjs/components/ChatDrawer/UsageBar.js.map +1 -1
  24. package/dist/cjs/components/CoreMemoryModal/CoreMemoryModal.js +7 -3
  25. package/dist/cjs/components/CoreMemoryModal/CoreMemoryModal.js.map +1 -1
  26. package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js +36 -21
  27. package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
  28. package/dist/cjs/components/IntegrationsModal/useIntegrations.js +12 -5
  29. package/dist/cjs/components/IntegrationsModal/useIntegrations.js.map +1 -1
  30. package/dist/cjs/components/ThreadStateTag/ThreadStateTag.js +10 -3
  31. package/dist/cjs/components/ThreadStateTag/ThreadStateTag.js.map +1 -1
  32. package/dist/cjs/hooks/useDevicChat.js +4 -2
  33. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  34. package/dist/cjs/provider/DevicProvider.js +27 -3
  35. package/dist/cjs/provider/DevicProvider.js.map +1 -1
  36. package/dist/esm/api/client.d.ts +63 -3
  37. package/dist/esm/api/client.js +125 -6
  38. package/dist/esm/api/client.js.map +1 -1
  39. package/dist/esm/components/AICommandBar/AICommandBar.js +4 -2
  40. package/dist/esm/components/AICommandBar/AICommandBar.js.map +1 -1
  41. package/dist/esm/components/AICommandBar/useAICommandBar.js +4 -2
  42. package/dist/esm/components/AICommandBar/useAICommandBar.js.map +1 -1
  43. package/dist/esm/components/AIElementWrapper/useAIElementWrapper.js +4 -2
  44. package/dist/esm/components/AIElementWrapper/useAIElementWrapper.js.map +1 -1
  45. package/dist/esm/components/AIGenerationButton/useAIGenerationButton.js +4 -2
  46. package/dist/esm/components/AIGenerationButton/useAIGenerationButton.js.map +1 -1
  47. package/dist/esm/components/ChatDrawer/ChatDrawer.js +18 -9
  48. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  49. package/dist/esm/components/ChatDrawer/ChatInput.js +9 -3
  50. package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
  51. package/dist/esm/components/ChatDrawer/ChatMessages.js +9 -3
  52. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  53. package/dist/esm/components/ChatDrawer/ConversationSelector.js +9 -7
  54. package/dist/esm/components/ChatDrawer/ConversationSelector.js.map +1 -1
  55. package/dist/esm/components/ChatDrawer/HandoffSubagentWidget.js +10 -3
  56. package/dist/esm/components/ChatDrawer/HandoffSubagentWidget.js.map +1 -1
  57. package/dist/esm/components/ChatDrawer/UsageBar.js +8 -2
  58. package/dist/esm/components/ChatDrawer/UsageBar.js.map +1 -1
  59. package/dist/esm/components/CoreMemoryModal/CoreMemoryModal.js +7 -3
  60. package/dist/esm/components/CoreMemoryModal/CoreMemoryModal.js.map +1 -1
  61. package/dist/esm/components/IntegrationsModal/IntegrationsHint.js +36 -21
  62. package/dist/esm/components/IntegrationsModal/IntegrationsHint.js.map +1 -1
  63. package/dist/esm/components/IntegrationsModal/useIntegrations.js +12 -5
  64. package/dist/esm/components/IntegrationsModal/useIntegrations.js.map +1 -1
  65. package/dist/esm/components/ThreadStateTag/ThreadStateTag.js +10 -3
  66. package/dist/esm/components/ThreadStateTag/ThreadStateTag.js.map +1 -1
  67. package/dist/esm/hooks/useDevicChat.js +4 -2
  68. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  69. package/dist/esm/index.d.ts +1 -1
  70. package/dist/esm/provider/DevicProvider.d.ts +1 -1
  71. package/dist/esm/provider/DevicProvider.js +27 -3
  72. package/dist/esm/provider/DevicProvider.js.map +1 -1
  73. package/dist/esm/provider/types.d.ts +62 -5
  74. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"UsageBar.js","sources":["../../../../src/components/ChatDrawer/UsageBar.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport { DevicApiClient } from '../../api/client';\nimport type { TenantUsage, TenantUsageRule } from '../../api/types';\n\n/**\n * Controls what each usage row renders. All flags compose, so a developer can,\n * for example, show absolute values together with the percentage and the tier.\n */\nexport interface UsageBarDisplay {\n /**\n * Show the absolute amount of the active window: `current / limit metric`\n * (e.g. `1,200 / 5,000 tokens`).\n * @default false\n */\n showValues?: boolean;\n /**\n * Show the utilization percentage (e.g. `24%`).\n * @default true\n */\n showPercent?: boolean;\n /**\n * Show the temporal dimension of the window (e.g. `per 3h`, `every 3 hours`).\n * @default true\n */\n showWindow?: boolean;\n /**\n * Show the tier (plan) the limits come from, as a chip above the bar(s).\n * @default false\n */\n showTier?: boolean;\n /**\n * When more than one limit rule applies, render every rule (`true`) or only\n * the single most restrictive one (`false`, highest utilization). The\n * per-row presentation still follows the flags above.\n * @default true\n */\n showAllRules?: boolean;\n}\n\nconst DEFAULT_DISPLAY: Required<UsageBarDisplay> = {\n showValues: false,\n showPercent: true,\n showWindow: true,\n showTier: false,\n showAllRules: true,\n};\n\n/**\n * Usage data handed to a `customUsageBar` renderer so a developer can build\n * their own component with the live consumption / limits.\n */\nexport interface UsageBarData {\n /** Tenant the usage belongs to. */\n tenantId?: string;\n /** Subtenant the usage is scoped to, if any. */\n subtenantId?: string;\n /** Tier (plan) the limits come from, if any. */\n tierId?: string;\n /**\n * Applicable usage rules (filtered by `metric` when set), sorted by\n * utilization descending — the most restrictive first.\n */\n rules: TenantUsageRule[];\n /** `true` until the first fetch resolves. */\n loading: boolean;\n}\n\n/**\n * Props for the UsageBar component.\n */\nexport interface UsageBarProps {\n /** API key (resolved from provider by the ChatDrawer). */\n apiKey?: string;\n /** Base URL (resolved from provider by the ChatDrawer). */\n baseUrl?: string;\n /** Tenant whose usage is displayed. Required — without it the bar renders nothing. */\n tenantId?: string;\n /** Optional subtenant to scope the usage to a specific end user. */\n subtenantId?: string;\n /**\n * Display mode:\n * - 'always': the bar is always visible.\n * - 'onDemand': a small toggle button is shown; the bar appears on click.\n */\n mode?: 'always' | 'onDemand';\n /** Restrict the bar to a single metric. When omitted, all applicable rules are considered. */\n metric?: 'tokens' | 'cost';\n /**\n * What to render per rule (values / percentage / window / tier) and whether\n * to render all applicable rules or only the most restrictive one.\n */\n display?: UsageBarDisplay;\n /**\n * Render your own component instead of the built-in bar(s). Receives the live\n * usage data (rules + tier + loading) and renders in the same slot, replacing\n * the default UI entirely. When set, fetching/polling is still handled here;\n * `mode`/`display` only affect the default UI and are ignored.\n */\n customUsageBar?: (data: UsageBarData) => React.ReactNode;\n /** Primary color for the bar fill (below the warning threshold). */\n color?: string;\n /**\n * Changes to this value trigger a refetch (e.g. the message count, so the bar\n * refreshes after each assistant turn).\n */\n refreshKey?: number | string;\n /** Enable debug logging of fetch errors to the console. */\n debug?: boolean;\n}\n\nconst WARN_THRESHOLD = 90;\n\nfunction fillColor(percent: number, base: string): string {\n if (percent >= 100) return '#ff4d4f';\n if (percent >= WARN_THRESHOLD) return '#faad14';\n return base;\n}\n\nfunction formatAmount(value: number, metric: 'tokens' | 'cost'): string {\n if (metric === 'cost') {\n return `$${value.toFixed(value < 1 ? 4 : 2)}`;\n }\n return Math.round(value).toLocaleString();\n}\n\nfunction windowLabel(unit: string, every: number): string {\n const u = every > 1 ? `${unit}s` : unit;\n return every > 1 ? `every ${every} ${u}` : `per ${unit}`;\n}\n\nfunction resetLabel(resetsAt?: number): string {\n if (!resetsAt) return '';\n const ms = resetsAt - Date.now();\n if (ms <= 0) return 'resets now';\n const mins = Math.round(ms / 60000);\n if (mins < 60) return `resets in ${mins}m`;\n const hours = Math.round(mins / 60);\n if (hours < 48) return `resets in ${hours}h`;\n const days = Math.round(hours / 24);\n return `resets in ${days}d`;\n}\n\n/**\n * A thin usage bar showing the current tenant/subtenant usage window(s).\n * Rendered above the chat input. Antd-free — styled via `.devic-usage-*` classes.\n * Renders nothing when there are no usage limits configured for the tenant.\n */\nexport function UsageBar({\n apiKey,\n baseUrl,\n tenantId,\n subtenantId,\n mode = 'always',\n metric,\n display,\n customUsageBar,\n color = '#1890ff',\n refreshKey,\n debug = false,\n}: UsageBarProps): JSX.Element | null {\n const cfg = useMemo(() => ({ ...DEFAULT_DISPLAY, ...display }), [display]);\n\n const client = useMemo(() => {\n if (!apiKey) return null;\n return new DevicApiClient({\n apiKey,\n baseUrl: baseUrl || 'https://api.devic.ai',\n });\n }, [apiKey, baseUrl]);\n\n const [usage, setUsage] = useState<TenantUsage | null>(null);\n const [loaded, setLoaded] = useState(false);\n // onDemand starts hidden; 'always' is effectively visible from the start.\n const [visible, setVisible] = useState(mode !== 'onDemand');\n\n const fetchUsage = useCallback(async () => {\n if (!client || !tenantId) return;\n try {\n const data = await client.getTenantUsage(tenantId, subtenantId);\n setUsage(data || null);\n } catch (err) {\n if (debug) console.warn('[UsageBar] getTenantUsage failed:', err);\n setUsage(null);\n } finally {\n setLoaded(true);\n }\n }, [client, tenantId, subtenantId, debug]);\n\n // A custom renderer always wants the data, regardless of the onDemand toggle.\n const active = !!customUsageBar || visible;\n\n useEffect(() => {\n if (!active) return;\n void fetchUsage();\n }, [active, fetchUsage, refreshKey]);\n\n // Applicable rules: filter by metric, sort most-restrictive first.\n const filteredSorted = useMemo(() => {\n const pool = (usage?.usage || []).filter(\n (r) => !metric || r.metric === metric,\n );\n return [...pool].sort((a, b) => b.percent - a.percent);\n }, [usage, metric]);\n\n // Default UI honours showAllRules; custom renderers always get the full set.\n const rules = useMemo(\n () => (cfg.showAllRules ? filteredSorted : filteredSorted.slice(0, 1)),\n [filteredSorted, cfg.showAllRules],\n );\n\n const tierId = usage?.tierId || filteredSorted[0]?.tierId;\n\n if (!tenantId || !client) return null;\n\n // Custom renderer fully replaces the default UI; hand it the live data.\n if (customUsageBar) {\n return (\n <>\n {customUsageBar({\n tenantId,\n subtenantId,\n tierId,\n rules: filteredSorted,\n loading: !loaded,\n })}\n </>\n );\n }\n\n // onDemand, collapsed: show a small toggle button.\n if (mode === 'onDemand' && !visible) {\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode=\"onDemand\">\n <button\n type=\"button\"\n className=\"devic-usage-toggle\"\n onClick={() => setVisible(true)}\n title=\"Show usage\"\n >\n <GaugeIcon />\n <span>Usage</span>\n </button>\n </div>\n );\n }\n\n // No configured limits (or not loaded yet with nothing to show): render nothing\n // in 'always' mode; in expanded onDemand, show a subtle note so the toggle isn't\n // confusing.\n if (rules.length === 0) {\n if (mode === 'onDemand' && loaded) {\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode=\"onDemand\">\n <div className=\"devic-usage-bar\" data-empty=\"true\">\n <span className=\"devic-usage-bar-label\">No usage limits</span>\n <button\n type=\"button\"\n className=\"devic-usage-collapse\"\n onClick={() => setVisible(false)}\n title=\"Hide usage\"\n aria-label=\"Hide usage\"\n >\n &times;\n </button>\n </div>\n </div>\n );\n }\n return null;\n }\n\n const showTierChip = cfg.showTier && !!tierId;\n const multi = rules.length > 1;\n // A header carries the tier chip and/or the onDemand collapse control, so the\n // collapse \"×\" has a stable home regardless of how many rows render below.\n const header =\n showTierChip || mode === 'onDemand' ? (\n <div className=\"devic-usage-bar-head\">\n {showTierChip ? (\n <span className=\"devic-usage-tier\" title={`Tier: ${tierId}`}>\n {tierId}\n </span>\n ) : (\n <span />\n )}\n {mode === 'onDemand' && (\n <button\n type=\"button\"\n className=\"devic-usage-collapse\"\n onClick={() => setVisible(false)}\n title=\"Hide usage\"\n aria-label=\"Hide usage\"\n >\n &times;\n </button>\n )}\n </div>\n ) : null;\n\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode={mode}>\n {header}\n {/* All rows share a single container by default. */}\n <div className=\"devic-usage-bar-group\">\n {rules.map((rule, i) => {\n const pct = Math.min(100, Math.round(rule.percent));\n const fill = fillColor(pct, color);\n const valuesText = `${formatAmount(rule.current, rule.metric)} / ${formatAmount(\n rule.limit,\n rule.metric,\n )} ${rule.metric}`;\n // Distinguish rows by metric when several render; otherwise use the\n // scope-aware label so single-rule mode reads \"Usage 24%\".\n const prefix = multi\n ? rule.metric === 'cost'\n ? 'Cost'\n : 'Tokens'\n : rule.scope === 'subtenant'\n ? 'Your usage'\n : 'Usage';\n const main: string[] = [];\n if (cfg.showPercent) main.push(`${pct}%`);\n if (cfg.showValues) main.push(valuesText);\n if (main.length === 0) main.push(`${pct}%`);\n const label = `${prefix} ${main.join(' · ')}`.trim();\n const reset = resetLabel(rule.resetsAt);\n const win = windowLabel(rule.windowUnit, rule.windowEvery);\n const title = `${valuesText} · ${win}${reset ? ` · ${reset}` : ''}`;\n\n return (\n <div\n key={`${rule.metric}-${rule.windowUnit}-${rule.windowEvery}-${rule.scope}-${i}`}\n className=\"devic-usage-bar\"\n title={title}\n data-state={pct >= 100 ? 'exceeded' : 'ok'}\n >\n <span className=\"devic-usage-bar-label\">{label}</span>\n <div className=\"devic-usage-bar-track\">\n <div\n className=\"devic-usage-bar-fill\"\n style={{ width: `${pct}%`, backgroundColor: fill }}\n />\n </div>\n {cfg.showWindow && (\n <span className=\"devic-usage-bar-window\">{win}</span>\n )}\n {reset && <span className=\"devic-usage-bar-reset\">{reset}</span>}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** Small gauge icon for the on-demand toggle. */\nfunction GaugeIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 14a4 4 0 1 0-4-4\" />\n <path d=\"M3 18a9 9 0 0 1 18 0\" />\n <line x1=\"12\" y1=\"14\" x2=\"15\" y2=\"11\" />\n </svg>\n );\n}\n"],"names":["_jsx","_Fragment","_jsxs"],"mappings":"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"}
1
+ {"version":3,"file":"UsageBar.js","sources":["../../../../src/components/ChatDrawer/UsageBar.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useOptionalDevicContext } from '../../provider';\nimport { DevicApiClient } from '../../api/client';\nimport type { TenantUsage, TenantUsageRule } from '../../api/types';\n\n/**\n * Controls what each usage row renders. All flags compose, so a developer can,\n * for example, show absolute values together with the percentage and the tier.\n */\nexport interface UsageBarDisplay {\n /**\n * Show the absolute amount of the active window: `current / limit metric`\n * (e.g. `1,200 / 5,000 tokens`).\n * @default false\n */\n showValues?: boolean;\n /**\n * Show the utilization percentage (e.g. `24%`).\n * @default true\n */\n showPercent?: boolean;\n /**\n * Show the temporal dimension of the window (e.g. `per 3h`, `every 3 hours`).\n * @default true\n */\n showWindow?: boolean;\n /**\n * Show the tier (plan) the limits come from, as a chip above the bar(s).\n * @default false\n */\n showTier?: boolean;\n /**\n * When more than one limit rule applies, render every rule (`true`) or only\n * the single most restrictive one (`false`, highest utilization). The\n * per-row presentation still follows the flags above.\n * @default true\n */\n showAllRules?: boolean;\n}\n\nconst DEFAULT_DISPLAY: Required<UsageBarDisplay> = {\n showValues: false,\n showPercent: true,\n showWindow: true,\n showTier: false,\n showAllRules: true,\n};\n\n/**\n * Usage data handed to a `customUsageBar` renderer so a developer can build\n * their own component with the live consumption / limits.\n */\nexport interface UsageBarData {\n /** Tenant the usage belongs to. */\n tenantId?: string;\n /** Subtenant the usage is scoped to, if any. */\n subtenantId?: string;\n /** Tier (plan) the limits come from, if any. */\n tierId?: string;\n /**\n * Applicable usage rules (filtered by `metric` when set), sorted by\n * utilization descending — the most restrictive first.\n */\n rules: TenantUsageRule[];\n /** `true` until the first fetch resolves. */\n loading: boolean;\n}\n\n/**\n * Props for the UsageBar component.\n */\nexport interface UsageBarProps {\n /** API key (resolved from provider by the ChatDrawer). */\n apiKey?: string;\n /** Base URL (resolved from provider by the ChatDrawer). */\n baseUrl?: string;\n /** Tenant whose usage is displayed. Required — without it the bar renders nothing. */\n tenantId?: string;\n /** Optional subtenant to scope the usage to a specific end user. */\n subtenantId?: string;\n /**\n * Display mode:\n * - 'always': the bar is always visible.\n * - 'onDemand': a small toggle button is shown; the bar appears on click.\n */\n mode?: 'always' | 'onDemand';\n /** Restrict the bar to a single metric. When omitted, all applicable rules are considered. */\n metric?: 'tokens' | 'cost';\n /**\n * What to render per rule (values / percentage / window / tier) and whether\n * to render all applicable rules or only the most restrictive one.\n */\n display?: UsageBarDisplay;\n /**\n * Render your own component instead of the built-in bar(s). Receives the live\n * usage data (rules + tier + loading) and renders in the same slot, replacing\n * the default UI entirely. When set, fetching/polling is still handled here;\n * `mode`/`display` only affect the default UI and are ignored.\n */\n customUsageBar?: (data: UsageBarData) => React.ReactNode;\n /** Primary color for the bar fill (below the warning threshold). */\n color?: string;\n /**\n * Changes to this value trigger a refetch (e.g. the message count, so the bar\n * refreshes after each assistant turn).\n */\n refreshKey?: number | string;\n /** Enable debug logging of fetch errors to the console. */\n debug?: boolean;\n}\n\nconst WARN_THRESHOLD = 90;\n\nfunction fillColor(percent: number, base: string): string {\n if (percent >= 100) return '#ff4d4f';\n if (percent >= WARN_THRESHOLD) return '#faad14';\n return base;\n}\n\nfunction formatAmount(value: number, metric: 'tokens' | 'cost'): string {\n if (metric === 'cost') {\n return `$${value.toFixed(value < 1 ? 4 : 2)}`;\n }\n return Math.round(value).toLocaleString();\n}\n\nfunction windowLabel(unit: string, every: number): string {\n const u = every > 1 ? `${unit}s` : unit;\n return every > 1 ? `every ${every} ${u}` : `per ${unit}`;\n}\n\nfunction resetLabel(resetsAt?: number): string {\n if (!resetsAt) return '';\n const ms = resetsAt - Date.now();\n if (ms <= 0) return 'resets now';\n const mins = Math.round(ms / 60000);\n if (mins < 60) return `resets in ${mins}m`;\n const hours = Math.round(mins / 60);\n if (hours < 48) return `resets in ${hours}h`;\n const days = Math.round(hours / 24);\n return `resets in ${days}d`;\n}\n\n/**\n * A thin usage bar showing the current tenant/subtenant usage window(s).\n * Rendered above the chat input. Antd-free — styled via `.devic-usage-*` classes.\n * Renders nothing when there are no usage limits configured for the tenant.\n */\nexport function UsageBar({\n apiKey,\n baseUrl,\n tenantId,\n subtenantId,\n mode = 'always',\n metric,\n display,\n customUsageBar,\n color = '#1890ff',\n refreshKey,\n debug = false,\n}: UsageBarProps): JSX.Element | null {\n const cfg = useMemo(() => ({ ...DEFAULT_DISPLAY, ...display }), [display]);\n\n const devicContext = useOptionalDevicContext();\n const getTenantSession = devicContext?.getTenantSession;\n const onSessionExpired = devicContext?.onSessionExpired;\n const client = useMemo(() => {\n if (!apiKey && !getTenantSession) return null;\n return new DevicApiClient({\n apiKey,\n baseUrl: baseUrl || 'https://api.devic.ai',\n getTenantSession,\n onSessionExpired,\n });\n }, [apiKey, getTenantSession, baseUrl]);\n\n const [usage, setUsage] = useState<TenantUsage | null>(null);\n const [loaded, setLoaded] = useState(false);\n // onDemand starts hidden; 'always' is effectively visible from the start.\n const [visible, setVisible] = useState(mode !== 'onDemand');\n\n const fetchUsage = useCallback(async () => {\n if (!client || !tenantId) return;\n try {\n const data = await client.getTenantUsage(tenantId, subtenantId);\n setUsage(data || null);\n } catch (err) {\n if (debug) console.warn('[UsageBar] getTenantUsage failed:', err);\n setUsage(null);\n } finally {\n setLoaded(true);\n }\n }, [client, tenantId, subtenantId, debug]);\n\n // A custom renderer always wants the data, regardless of the onDemand toggle.\n const active = !!customUsageBar || visible;\n\n useEffect(() => {\n if (!active) return;\n void fetchUsage();\n }, [active, fetchUsage, refreshKey]);\n\n // Applicable rules: filter by metric, sort most-restrictive first.\n const filteredSorted = useMemo(() => {\n const pool = (usage?.usage || []).filter(\n (r) => !metric || r.metric === metric,\n );\n return [...pool].sort((a, b) => b.percent - a.percent);\n }, [usage, metric]);\n\n // Default UI honours showAllRules; custom renderers always get the full set.\n const rules = useMemo(\n () => (cfg.showAllRules ? filteredSorted : filteredSorted.slice(0, 1)),\n [filteredSorted, cfg.showAllRules],\n );\n\n const tierId = usage?.tierId || filteredSorted[0]?.tierId;\n\n if (!tenantId || !client) return null;\n\n // Custom renderer fully replaces the default UI; hand it the live data.\n if (customUsageBar) {\n return (\n <>\n {customUsageBar({\n tenantId,\n subtenantId,\n tierId,\n rules: filteredSorted,\n loading: !loaded,\n })}\n </>\n );\n }\n\n // onDemand, collapsed: show a small toggle button.\n if (mode === 'onDemand' && !visible) {\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode=\"onDemand\">\n <button\n type=\"button\"\n className=\"devic-usage-toggle\"\n onClick={() => setVisible(true)}\n title=\"Show usage\"\n >\n <GaugeIcon />\n <span>Usage</span>\n </button>\n </div>\n );\n }\n\n // No configured limits (or not loaded yet with nothing to show): render nothing\n // in 'always' mode; in expanded onDemand, show a subtle note so the toggle isn't\n // confusing.\n if (rules.length === 0) {\n if (mode === 'onDemand' && loaded) {\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode=\"onDemand\">\n <div className=\"devic-usage-bar\" data-empty=\"true\">\n <span className=\"devic-usage-bar-label\">No usage limits</span>\n <button\n type=\"button\"\n className=\"devic-usage-collapse\"\n onClick={() => setVisible(false)}\n title=\"Hide usage\"\n aria-label=\"Hide usage\"\n >\n &times;\n </button>\n </div>\n </div>\n );\n }\n return null;\n }\n\n const showTierChip = cfg.showTier && !!tierId;\n const multi = rules.length > 1;\n // A header carries the tier chip and/or the onDemand collapse control, so the\n // collapse \"×\" has a stable home regardless of how many rows render below.\n const header =\n showTierChip || mode === 'onDemand' ? (\n <div className=\"devic-usage-bar-head\">\n {showTierChip ? (\n <span className=\"devic-usage-tier\" title={`Tier: ${tierId}`}>\n {tierId}\n </span>\n ) : (\n <span />\n )}\n {mode === 'onDemand' && (\n <button\n type=\"button\"\n className=\"devic-usage-collapse\"\n onClick={() => setVisible(false)}\n title=\"Hide usage\"\n aria-label=\"Hide usage\"\n >\n &times;\n </button>\n )}\n </div>\n ) : null;\n\n return (\n <div className=\"devic-usage-bar-wrap\" data-mode={mode}>\n {header}\n {/* All rows share a single container by default. */}\n <div className=\"devic-usage-bar-group\">\n {rules.map((rule, i) => {\n const pct = Math.min(100, Math.round(rule.percent));\n const fill = fillColor(pct, color);\n const valuesText = `${formatAmount(rule.current, rule.metric)} / ${formatAmount(\n rule.limit,\n rule.metric,\n )} ${rule.metric}`;\n // Distinguish rows by metric when several render; otherwise use the\n // scope-aware label so single-rule mode reads \"Usage 24%\".\n const prefix = multi\n ? rule.metric === 'cost'\n ? 'Cost'\n : 'Tokens'\n : rule.scope === 'subtenant'\n ? 'Your usage'\n : 'Usage';\n const main: string[] = [];\n if (cfg.showPercent) main.push(`${pct}%`);\n if (cfg.showValues) main.push(valuesText);\n if (main.length === 0) main.push(`${pct}%`);\n const label = `${prefix} ${main.join(' · ')}`.trim();\n const reset = resetLabel(rule.resetsAt);\n const win = windowLabel(rule.windowUnit, rule.windowEvery);\n const title = `${valuesText} · ${win}${reset ? ` · ${reset}` : ''}`;\n\n return (\n <div\n key={`${rule.metric}-${rule.windowUnit}-${rule.windowEvery}-${rule.scope}-${i}`}\n className=\"devic-usage-bar\"\n title={title}\n data-state={pct >= 100 ? 'exceeded' : 'ok'}\n >\n <span className=\"devic-usage-bar-label\">{label}</span>\n <div className=\"devic-usage-bar-track\">\n <div\n className=\"devic-usage-bar-fill\"\n style={{ width: `${pct}%`, backgroundColor: fill }}\n />\n </div>\n {cfg.showWindow && (\n <span className=\"devic-usage-bar-window\">{win}</span>\n )}\n {reset && <span className=\"devic-usage-bar-reset\">{reset}</span>}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** Small gauge icon for the on-demand toggle. */\nfunction GaugeIcon(): JSX.Element {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 14a4 4 0 1 0-4-4\" />\n <path d=\"M3 18a9 9 0 0 1 18 0\" />\n <line x1=\"12\" y1=\"14\" x2=\"15\" y2=\"11\" />\n </svg>\n );\n}\n"],"names":["_jsx","_Fragment","_jsxs"],"mappings":";;;;;AAwCA,MAAM,eAAe,GAA8B;AACjD,IAAA,UAAU,EAAE,KAAK;AACjB,IAAA,WAAW,EAAE,IAAI;AACjB,IAAA,UAAU,EAAE,IAAI;AAChB,IAAA,QAAQ,EAAE,KAAK;AACf,IAAA,YAAY,EAAE,IAAI;CACnB;AAiED,MAAM,cAAc,GAAG,EAAE;AAEzB,SAAS,SAAS,CAAC,OAAe,EAAE,IAAY,EAAA;IAC9C,IAAI,OAAO,IAAI,GAAG;AAAE,QAAA,OAAO,SAAS;IACpC,IAAI,OAAO,IAAI,cAAc;AAAE,QAAA,OAAO,SAAS;AAC/C,IAAA,OAAO,IAAI;AACb;AAEA,SAAS,YAAY,CAAC,KAAa,EAAE,MAAyB,EAAA;AAC5D,IAAA,IAAI,MAAM,KAAK,MAAM,EAAE;AACrB,QAAA,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE;IAC/C;IACA,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,cAAc,EAAE;AAC3C;AAEA,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa,EAAA;AAC9C,IAAA,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAA,EAAG,IAAI,CAAA,CAAA,CAAG,GAAG,IAAI;AACvC,IAAA,OAAO,KAAK,GAAG,CAAC,GAAG,CAAA,MAAA,EAAS,KAAK,CAAA,CAAA,EAAI,CAAC,EAAE,GAAG,CAAA,IAAA,EAAO,IAAI,EAAE;AAC1D;AAEA,SAAS,UAAU,CAAC,QAAiB,EAAA;AACnC,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,EAAE;IACxB,MAAM,EAAE,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE;IAChC,IAAI,EAAE,IAAI,CAAC;AAAE,QAAA,OAAO,YAAY;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC;IACnC,IAAI,IAAI,GAAG,EAAE;QAAE,OAAO,CAAA,UAAA,EAAa,IAAI,CAAA,CAAA,CAAG;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC;IACnC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,CAAA,UAAA,EAAa,KAAK,CAAA,CAAA,CAAG;IAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;IACnC,OAAO,CAAA,UAAA,EAAa,IAAI,CAAA,CAAA,CAAG;AAC7B;AAEA;;;;AAIG;AACG,SAAU,QAAQ,CAAC,EACvB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,WAAW,EACX,IAAI,GAAG,QAAQ,EACf,MAAM,EACN,OAAO,EACP,cAAc,EACd,KAAK,GAAG,SAAS,EACjB,UAAU,EACV,KAAK,GAAG,KAAK,GACC,EAAA;IACd,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;AAE1E,IAAA,MAAM,YAAY,GAAG,uBAAuB,EAAE;AAC9C,IAAA,MAAM,gBAAgB,GAAG,YAAY,EAAE,gBAAgB;AACvD,IAAA,MAAM,gBAAgB,GAAG,YAAY,EAAE,gBAAgB;AACvD,IAAA,MAAM,MAAM,GAAG,OAAO,CAAC,MAAK;AAC1B,QAAA,IAAI,CAAC,MAAM,IAAI,CAAC,gBAAgB;AAAE,YAAA,OAAO,IAAI;QAC7C,OAAO,IAAI,cAAc,CAAC;YACxB,MAAM;YACN,OAAO,EAAE,OAAO,IAAI,sBAAsB;YAC1C,gBAAgB;YAChB,gBAAgB;AACjB,SAAA,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,CAAC;IAEvC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC;IAC5D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;;AAE3C,IAAA,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,IAAI,KAAK,UAAU,CAAC;AAE3D,IAAA,MAAM,UAAU,GAAG,WAAW,CAAC,YAAW;AACxC,QAAA,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;YAAE;AAC1B,QAAA,IAAI;YACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,QAAQ,EAAE,WAAW,CAAC;AAC/D,YAAA,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC;QACxB;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,IAAI,KAAK;AAAE,gBAAA,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,GAAG,CAAC;YACjE,QAAQ,CAAC,IAAI,CAAC;QAChB;gBAAU;YACR,SAAS,CAAC,IAAI,CAAC;QACjB;IACF,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;;AAG1C,IAAA,MAAM,MAAM,GAAG,CAAC,CAAC,cAAc,IAAI,OAAO;IAE1C,SAAS,CAAC,MAAK;AACb,QAAA,IAAI,CAAC,MAAM;YAAE;QACb,KAAK,UAAU,EAAE;IACnB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;;AAGpC,IAAA,MAAM,cAAc,GAAG,OAAO,CAAC,MAAK;QAClC,MAAM,IAAI,GAAG,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,MAAM,CACtC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CACtC;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;AACxD,IAAA,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;;AAGnB,IAAA,MAAM,KAAK,GAAG,OAAO,CACnB,OAAO,GAAG,CAAC,YAAY,GAAG,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EACtE,CAAC,cAAc,EAAE,GAAG,CAAC,YAAY,CAAC,CACnC;AAED,IAAA,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE,MAAM;AAEzD,IAAA,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,IAAI;;IAGrC,IAAI,cAAc,EAAE;QAClB,QACEA,GAAA,CAAAC,QAAA,EAAA,EAAA,QAAA,EACG,cAAc,CAAC;gBACd,QAAQ;gBACR,WAAW;gBACX,MAAM;AACN,gBAAA,KAAK,EAAE,cAAc;gBACrB,OAAO,EAAE,CAAC,MAAM;aACjB,CAAC,EAAA,CACD;IAEP;;AAGA,IAAA,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,EAAE;AACnC,QAAA,QACED,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,sBAAsB,eAAW,UAAU,EAAA,QAAA,EACxDE,IAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,oBAAoB,EAC9B,OAAO,EAAE,MAAM,UAAU,CAAC,IAAI,CAAC,EAC/B,KAAK,EAAC,YAAY,EAAA,QAAA,EAAA,CAElBF,GAAA,CAAC,SAAS,EAAA,EAAA,CAAG,EACbA,kCAAkB,CAAA,EAAA,CACX,EAAA,CACL;IAEV;;;;AAKA,IAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;AACtB,QAAA,IAAI,IAAI,KAAK,UAAU,IAAI,MAAM,EAAE;YACjC,QACEA,aAAK,SAAS,EAAC,sBAAsB,EAAA,WAAA,EAAW,UAAU,EAAA,QAAA,EACxDE,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,iBAAiB,EAAA,YAAA,EAAY,MAAM,aAChDF,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,uBAAuB,EAAA,QAAA,EAAA,iBAAA,EAAA,CAAuB,EAC9DA,GAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,sBAAsB,EAChC,OAAO,EAAE,MAAM,UAAU,CAAC,KAAK,CAAC,EAChC,KAAK,EAAC,YAAY,EAAA,YAAA,EACP,YAAY,EAAA,QAAA,EAAA,QAAA,EAAA,CAGhB,CAAA,EAAA,CACL,EAAA,CACF;QAEV;AACA,QAAA,OAAO,IAAI;IACb;IAEA,MAAM,YAAY,GAAG,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM;AAC7C,IAAA,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;;;AAG9B,IAAA,MAAM,MAAM,GACV,YAAY,IAAI,IAAI,KAAK,UAAU,IACjCE,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,sBAAsB,aAClC,YAAY,IACXF,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,kBAAkB,EAAC,KAAK,EAAE,CAAA,MAAA,EAAS,MAAM,EAAE,EAAA,QAAA,EACxD,MAAM,GACF,KAEPA,GAAA,CAAA,MAAA,EAAA,EAAA,CAAQ,CACT,EACA,IAAI,KAAK,UAAU,KAClBA,GAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,sBAAsB,EAChC,OAAO,EAAE,MAAM,UAAU,CAAC,KAAK,CAAC,EAChC,KAAK,EAAC,YAAY,EAAA,YAAA,EACP,YAAY,EAAA,QAAA,EAAA,QAAA,EAAA,CAGhB,CACV,CAAA,EAAA,CACG,IACJ,IAAI;IAEV,QACEE,IAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,sBAAsB,eAAY,IAAI,EAAA,QAAA,EAAA,CAClD,MAAM,EAEPF,GAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,uBAAuB,EAAA,QAAA,EACnC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAI;AACrB,oBAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACnD,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC;AAClC,oBAAA,MAAM,UAAU,GAAG,CAAA,EAAG,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA,GAAA,EAAM,YAAY,CAC7E,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,MAAM,CACZ,CAAA,CAAA,EAAI,IAAI,CAAC,MAAM,EAAE;;;oBAGlB,MAAM,MAAM,GAAG;AACb,0BAAE,IAAI,CAAC,MAAM,KAAK;AAChB,8BAAE;AACF,8BAAE;AACJ,0BAAE,IAAI,CAAC,KAAK,KAAK;AACf,8BAAE;8BACA,OAAO;oBACb,MAAM,IAAI,GAAa,EAAE;oBACzB,IAAI,GAAG,CAAC,WAAW;AAAE,wBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA,CAAA,CAAG,CAAC;oBACzC,IAAI,GAAG,CAAC,UAAU;AAAE,wBAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;AACzC,oBAAA,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;AAAE,wBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAA,CAAA,CAAG,CAAC;AAC3C,oBAAA,MAAM,KAAK,GAAG,CAAA,EAAG,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA,CAAE,CAAC,IAAI,EAAE;oBACpD,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;AACvC,oBAAA,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC;AAC1D,oBAAA,MAAM,KAAK,GAAG,CAAA,EAAG,UAAU,CAAA,GAAA,EAAM,GAAG,GAAG,KAAK,GAAG,CAAA,GAAA,EAAM,KAAK,CAAA,CAAE,GAAG,EAAE,EAAE;oBAEnE,QACEE,cAEE,SAAS,EAAC,iBAAiB,EAC3B,KAAK,EAAE,KAAK,EAAA,YAAA,EACA,GAAG,IAAI,GAAG,GAAG,UAAU,GAAG,IAAI,EAAA,QAAA,EAAA,CAE1CF,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,uBAAuB,EAAA,QAAA,EAAE,KAAK,EAAA,CAAQ,EACtDA,aAAK,SAAS,EAAC,uBAAuB,EAAA,QAAA,EACpCA,GAAA,CAAA,KAAA,EAAA,EACE,SAAS,EAAC,sBAAsB,EAChC,KAAK,EAAE,EAAE,KAAK,EAAE,CAAA,EAAG,GAAG,CAAA,CAAA,CAAG,EAAE,eAAe,EAAE,IAAI,EAAE,GAClD,EAAA,CACE,EACL,GAAG,CAAC,UAAU,KACbA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,wBAAwB,EAAA,QAAA,EAAE,GAAG,EAAA,CAAQ,CACtD,EACA,KAAK,IAAIA,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,uBAAuB,YAAE,KAAK,EAAA,CAAQ,KAf3D,CAAA,EAAG,IAAI,CAAC,MAAM,CAAA,CAAA,EAAI,IAAI,CAAC,UAAU,CAAA,CAAA,EAAI,IAAI,CAAC,WAAW,CAAA,CAAA,EAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA,CAAE,CAgB3E;AAEV,gBAAA,CAAC,CAAC,EAAA,CACE,CAAA,EAAA,CACF;AAEV;AAEA;AACA,SAAS,SAAS,GAAA;AAChB,IAAA,QACEE,IAAA,CAAA,KAAA,EAAA,EACE,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EAAA,QAAA,EAAA,CAEtBF,GAAA,CAAA,MAAA,EAAA,EAAM,CAAC,EAAC,sBAAsB,EAAA,CAAG,EACjCA,GAAA,CAAA,MAAA,EAAA,EAAM,CAAC,EAAC,sBAAsB,EAAA,CAAG,EACjCA,cAAM,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAA,CAAG,CAAA,EAAA,CACpC;AAEV;;;;"}
@@ -25,16 +25,20 @@ function PinIcon() {
25
25
  */
26
26
  function CoreMemoryModal({ isOpen, onClose, assistantId, tenantId, subtenantId, apiKey, baseUrl, editable = true, title = "Assistant memory", theme, }) {
27
27
  const context = useOptionalDevicContext();
28
- const resolvedApiKey = apiKey || context?.apiKey;
29
28
  const resolvedBaseUrl = baseUrl || context?.baseUrl || "https://api.devic.ai";
30
29
  const resolvedTenantId = tenantId || context?.tenantId;
31
30
  const resolvedSubtenantId = subtenantId || context?.subtenantId;
32
- const client = useMemo(() => resolvedApiKey
31
+ const resolvedApiKey = apiKey || context?.apiKey;
32
+ const resolvedTenantSession = context?.getTenantSession;
33
+ const onSessionExpired = context?.onSessionExpired;
34
+ const client = useMemo(() => resolvedApiKey || resolvedTenantSession
33
35
  ? new DevicApiClient({
34
36
  apiKey: resolvedApiKey,
35
37
  baseUrl: resolvedBaseUrl,
38
+ getTenantSession: resolvedTenantSession,
39
+ onSessionExpired,
36
40
  })
37
- : null, [resolvedApiKey, resolvedBaseUrl]);
41
+ : null, [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]);
38
42
  const [loading, setLoading] = useState(false);
39
43
  const [enabled, setEnabled] = useState(true);
40
44
  const [entries, setEntries] = useState([]);
@@ -1 +1 @@
1
- {"version":3,"file":"CoreMemoryModal.js","sources":["../../../../src/components/CoreMemoryModal/CoreMemoryModal.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { DevicApiClient } from \"../../api/client\";\nimport type { CoreMemoryEntry, CoreMemoryLimits } from \"../../api/types\";\nimport { useOptionalDevicContext } from \"../../provider\";\nimport { themeVars, type DevicTheme } from \"../theme\";\nimport \"./CoreMemoryModal.css\";\n\n/** Canonical sections of the core tier, in display order. */\nconst SECTIONS = [\"persona\", \"instructions\", \"decisions\", \"profile\"] as const;\n\nexport interface CoreMemoryModalProps {\n /** Whether the modal is visible. */\n isOpen: boolean;\n onClose: () => void;\n /** Assistant whose memory bucket is shown. */\n assistantId: string;\n /** Tenant of the end user (falls back to the provider's tenantId). */\n tenantId?: string;\n /** Subtenant of the end user (falls back to the provider's subtenantId). */\n subtenantId?: string;\n /** API key override (falls back to the provider's). */\n apiKey?: string;\n /** Base URL override (falls back to the provider's). */\n baseUrl?: string;\n /**\n * Let the user add, edit and remove entries. Set false for a read-only\n * view of what the assistant remembers.\n * @default true\n */\n editable?: boolean;\n /** Modal title. @default \"Assistant memory\" */\n title?: string;\n /**\n * Colours and font. Same names as the drawer's style options, and the drawer\n * passes its own down — a dialog opening in the default light palette over a\n * themed application is the one thing this must not do.\n */\n theme?: DevicTheme;\n}\n\nfunction BrainIcon(): JSX.Element {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9.5 2a2.5 2.5 0 0 0-2.45 2A3.5 3.5 0 0 0 4.6 8.6 3.5 3.5 0 0 0 3 11.5c0 1.1.5 2.08 1.29 2.73A3.5 3.5 0 0 0 7 20a3 3 0 0 0 5-2.24V4.5A2.5 2.5 0 0 0 9.5 2Z\" />\n <path d=\"M14.5 2a2.5 2.5 0 0 1 2.45 2 3.5 3.5 0 0 1 2.45 4.6A3.5 3.5 0 0 1 21 11.5a3.49 3.49 0 0 1-1.29 2.73A3.5 3.5 0 0 1 17 20a3 3 0 0 1-5-2.24V4.5A2.5 2.5 0 0 1 14.5 2Z\" />\n </svg>\n );\n}\n\nfunction PinIcon(): JSX.Element {\n return (\n <svg\n width=\"11\"\n height=\"11\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n stroke=\"none\"\n aria-hidden=\"true\"\n >\n <path d=\"M16 3a1 1 0 0 1 .97 1.243l-1.2 4.8 3.937 3.938A1 1 0 0 1 19 14.688V15a1 1 0 0 1-1 1h-5v6l-1 1-1-1v-6H6a1 1 0 0 1-1-1v-.312a1 1 0 0 1 .293-.707L9.23 9.043l-1.2-4.8A1 1 0 0 1 9 3h7Z\" />\n </svg>\n );\n}\n\n/**\n * Modal showing — and, when `editable`, managing — the core memory of an\n * assistant for a tenant/subtenant combination: the standing entries\n * (persona, instructions, decisions, profile) injected into every prompt of\n * the bucket the assistant resolves there. Lets the end user see and control\n * what the assistant permanently remembers about them.\n *\n * Backed by the public memory API (`/api/v1/memory/assistants/:id/core`);\n * the API key needs that endpoint allowed.\n */\nexport function CoreMemoryModal({\n isOpen,\n onClose,\n assistantId,\n tenantId,\n subtenantId,\n apiKey,\n baseUrl,\n editable = true,\n title = \"Assistant memory\",\n theme,\n}: CoreMemoryModalProps): JSX.Element | null {\n const context = useOptionalDevicContext();\n const resolvedApiKey = apiKey || context?.apiKey;\n const resolvedBaseUrl = baseUrl || context?.baseUrl || \"https://api.devic.ai\";\n const resolvedTenantId = tenantId || context?.tenantId;\n const resolvedSubtenantId = subtenantId || context?.subtenantId;\n\n const client = useMemo(\n () =>\n resolvedApiKey\n ? new DevicApiClient({\n apiKey: resolvedApiKey,\n baseUrl: resolvedBaseUrl,\n })\n : null,\n [resolvedApiKey, resolvedBaseUrl]\n );\n\n const [loading, setLoading] = useState(false);\n const [enabled, setEnabled] = useState(true);\n const [entries, setEntries] = useState<CoreMemoryEntry[]>([]);\n const [limits, setLimits] = useState<CoreMemoryLimits | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n\n // Inline composer / editor state\n const [composerOpen, setComposerOpen] = useState(false);\n const [composerSection, setComposerSection] = useState<string>(\"profile\");\n const [composerText, setComposerText] = useState(\"\");\n const [editingId, setEditingId] = useState<number | null>(null);\n const [editingText, setEditingText] = useState(\"\");\n\n const scopeOptions = useMemo(\n () => ({\n tenantId: resolvedTenantId || undefined,\n subtenantId: resolvedSubtenantId || undefined,\n }),\n [resolvedTenantId, resolvedSubtenantId]\n );\n\n const refresh = useCallback(async () => {\n if (!client) {\n setError(\"API key not configured\");\n return;\n }\n setLoading(true);\n setError(null);\n try {\n const list = await client.getCoreMemory(assistantId, scopeOptions);\n setEnabled(list.enabled);\n setEntries(list.entries);\n setLimits(list.limits ?? null);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setLoading(false);\n }\n }, [client, assistantId, scopeOptions]);\n\n // Fetch on every open (memory may have changed since the last look).\n const wasOpenRef = useRef(false);\n useEffect(() => {\n if (isOpen && !wasOpenRef.current) {\n setComposerOpen(false);\n setComposerText(\"\");\n setEditingId(null);\n void refresh();\n }\n wasOpenRef.current = isOpen;\n }, [isOpen, refresh]);\n\n // Escape closes\n useEffect(() => {\n if (!isOpen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [isOpen, onClose]);\n\n const runWrite = useCallback(\n async (write: () => Promise<unknown>) => {\n setSaving(true);\n setError(null);\n try {\n await write();\n await refresh();\n return true;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n return false;\n } finally {\n setSaving(false);\n }\n },\n [refresh]\n );\n\n const handleAdd = async () => {\n if (!client || !composerText.trim()) return;\n const ok = await runWrite(() =>\n client.addCoreMemoryEntry(\n assistantId,\n { content: composerText.trim(), section: composerSection },\n scopeOptions\n )\n );\n if (ok) {\n setComposerOpen(false);\n setComposerText(\"\");\n }\n };\n\n const handleSaveEdit = async (entry: CoreMemoryEntry) => {\n if (!client || !editingText.trim()) return;\n const ok = await runWrite(() =>\n client.updateCoreMemoryEntry(\n assistantId,\n entry.id,\n { content: editingText.trim() },\n scopeOptions\n )\n );\n if (ok) setEditingId(null);\n };\n\n const handleRemove = async (entry: CoreMemoryEntry) => {\n if (!client) return;\n await runWrite(() =>\n client.deleteCoreMemoryEntry(assistantId, entry.id, scopeOptions)\n );\n };\n\n if (!isOpen) return null;\n\n // Group entries by section, canonical sections first.\n const sections: Array<{ name: string; entries: CoreMemoryEntry[] }> = [];\n const bySection = new Map<string, CoreMemoryEntry[]>();\n for (const entry of entries) {\n const list = bySection.get(entry.section);\n if (list) list.push(entry);\n else bySection.set(entry.section, [entry]);\n }\n for (const name of SECTIONS) {\n const list = bySection.get(name);\n if (list) {\n sections.push({ name, entries: list });\n bySection.delete(name);\n }\n }\n for (const [name, list] of bySection) sections.push({ name, entries: list });\n\n return createPortal(\n // The variables go on the overlay: a portal inherits nothing from the\n // drawer that opened it, so without this the dialog ignores the theme.\n <div\n className=\"devic-cm-overlay\"\n style={themeVars(theme)}\n onClick={onClose}\n >\n <div\n className=\"devic-cm-modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"devic-cm-header\">\n <h3 className=\"devic-cm-title\">\n <BrainIcon />\n {title}\n </h3>\n <button\n className=\"devic-cm-close\"\n onClick={onClose}\n type=\"button\"\n aria-label=\"Close\"\n >\n ×\n </button>\n </div>\n\n <div className=\"devic-cm-body\">\n {error && <div className=\"devic-cm-error\">{error}</div>}\n\n {loading ? (\n <div className=\"devic-cm-loading\">Loading memory…</div>\n ) : !enabled ? (\n <div className=\"devic-cm-empty\">\n Memory is not enabled for this assistant.\n </div>\n ) : entries.length === 0 && !composerOpen ? (\n <div className=\"devic-cm-empty\">\n Nothing remembered yet. What the assistant learns and is told to\n keep will appear here.\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.name} className=\"devic-cm-section\">\n <div className=\"devic-cm-section-title\">{section.name}</div>\n {section.entries.map((entry) => (\n <div key={entry.id} className=\"devic-cm-entry\">\n {editingId === entry.id ? (\n <div className=\"devic-cm-editor\">\n <textarea\n className=\"devic-cm-textarea\"\n value={editingText}\n maxLength={limits?.maxEntryChars || undefined}\n onChange={(e) => setEditingText(e.target.value)}\n disabled={saving}\n autoFocus\n />\n <div className=\"devic-cm-editor-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-primary\"\n onClick={() => handleSaveEdit(entry)}\n disabled={saving || !editingText.trim()}\n >\n Save\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => setEditingId(null)}\n disabled={saving}\n >\n Cancel\n </button>\n </div>\n </div>\n ) : (\n <>\n <div className=\"devic-cm-entry-content\">\n {entry.pinned && (\n <span\n className=\"devic-cm-pin\"\n title=\"Pinned: always kept, never changed by the assistant\"\n >\n <PinIcon />\n </span>\n )}\n <span>{entry.content}</span>\n </div>\n {editable && (\n <div className=\"devic-cm-entry-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-small\"\n onClick={() => {\n setEditingId(entry.id);\n setEditingText(entry.content);\n }}\n disabled={saving}\n >\n Edit\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-small devic-cm-btn-danger\"\n onClick={() => handleRemove(entry)}\n disabled={saving}\n >\n Remove\n </button>\n </div>\n )}\n </>\n )}\n </div>\n ))}\n </div>\n ))\n )}\n\n {editable && enabled && !loading && (\n <div className=\"devic-cm-composer\">\n {composerOpen ? (\n <>\n <div className=\"devic-cm-composer-row\">\n <select\n className=\"devic-cm-select\"\n value={composerSection}\n onChange={(e) => setComposerSection(e.target.value)}\n disabled={saving}\n >\n {SECTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <textarea\n className=\"devic-cm-textarea\"\n placeholder=\"Something the assistant should always remember…\"\n value={composerText}\n maxLength={limits?.maxEntryChars || undefined}\n onChange={(e) => setComposerText(e.target.value)}\n disabled={saving}\n autoFocus\n />\n <div className=\"devic-cm-editor-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-primary\"\n onClick={handleAdd}\n disabled={saving || !composerText.trim()}\n >\n Add\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => {\n setComposerOpen(false);\n setComposerText(\"\");\n }}\n disabled={saving}\n >\n Cancel\n </button>\n </div>\n </>\n ) : (\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => setComposerOpen(true)}\n disabled={saving}\n >\n + Add memory\n </button>\n )}\n </div>\n )}\n </div>\n\n {limits && enabled && (\n <div className=\"devic-cm-footer\">\n {entries.length}/{limits.maxEntries} entries · up to{\" \"}\n {limits.maxEntryChars} characters each\n </div>\n )}\n </div>\n </div>,\n document.body\n );\n}\n\nexport default CoreMemoryModal;\n"],"names":["_jsxs","_jsx","_Fragment"],"mappings":"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"}
1
+ {"version":3,"file":"CoreMemoryModal.js","sources":["../../../../src/components/CoreMemoryModal/CoreMemoryModal.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { DevicApiClient } from \"../../api/client\";\nimport type { CoreMemoryEntry, CoreMemoryLimits } from \"../../api/types\";\nimport { useOptionalDevicContext } from \"../../provider\";\nimport { themeVars, type DevicTheme } from \"../theme\";\nimport \"./CoreMemoryModal.css\";\n\n/** Canonical sections of the core tier, in display order. */\nconst SECTIONS = [\"persona\", \"instructions\", \"decisions\", \"profile\"] as const;\n\nexport interface CoreMemoryModalProps {\n /** Whether the modal is visible. */\n isOpen: boolean;\n onClose: () => void;\n /** Assistant whose memory bucket is shown. */\n assistantId: string;\n /** Tenant of the end user (falls back to the provider's tenantId). */\n tenantId?: string;\n /** Subtenant of the end user (falls back to the provider's subtenantId). */\n subtenantId?: string;\n /** API key override (falls back to the provider's). */\n apiKey?: string;\n /** Base URL override (falls back to the provider's). */\n baseUrl?: string;\n /**\n * Let the user add, edit and remove entries. Set false for a read-only\n * view of what the assistant remembers.\n * @default true\n */\n editable?: boolean;\n /** Modal title. @default \"Assistant memory\" */\n title?: string;\n /**\n * Colours and font. Same names as the drawer's style options, and the drawer\n * passes its own down — a dialog opening in the default light palette over a\n * themed application is the one thing this must not do.\n */\n theme?: DevicTheme;\n}\n\nfunction BrainIcon(): JSX.Element {\n return (\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M9.5 2a2.5 2.5 0 0 0-2.45 2A3.5 3.5 0 0 0 4.6 8.6 3.5 3.5 0 0 0 3 11.5c0 1.1.5 2.08 1.29 2.73A3.5 3.5 0 0 0 7 20a3 3 0 0 0 5-2.24V4.5A2.5 2.5 0 0 0 9.5 2Z\" />\n <path d=\"M14.5 2a2.5 2.5 0 0 1 2.45 2 3.5 3.5 0 0 1 2.45 4.6A3.5 3.5 0 0 1 21 11.5a3.49 3.49 0 0 1-1.29 2.73A3.5 3.5 0 0 1 17 20a3 3 0 0 1-5-2.24V4.5A2.5 2.5 0 0 1 14.5 2Z\" />\n </svg>\n );\n}\n\nfunction PinIcon(): JSX.Element {\n return (\n <svg\n width=\"11\"\n height=\"11\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n stroke=\"none\"\n aria-hidden=\"true\"\n >\n <path d=\"M16 3a1 1 0 0 1 .97 1.243l-1.2 4.8 3.937 3.938A1 1 0 0 1 19 14.688V15a1 1 0 0 1-1 1h-5v6l-1 1-1-1v-6H6a1 1 0 0 1-1-1v-.312a1 1 0 0 1 .293-.707L9.23 9.043l-1.2-4.8A1 1 0 0 1 9 3h7Z\" />\n </svg>\n );\n}\n\n/**\n * Modal showing — and, when `editable`, managing — the core memory of an\n * assistant for a tenant/subtenant combination: the standing entries\n * (persona, instructions, decisions, profile) injected into every prompt of\n * the bucket the assistant resolves there. Lets the end user see and control\n * what the assistant permanently remembers about them.\n *\n * Backed by the public memory API (`/api/v1/memory/assistants/:id/core`);\n * the API key needs that endpoint allowed.\n */\nexport function CoreMemoryModal({\n isOpen,\n onClose,\n assistantId,\n tenantId,\n subtenantId,\n apiKey,\n baseUrl,\n editable = true,\n title = \"Assistant memory\",\n theme,\n}: CoreMemoryModalProps): JSX.Element | null {\n const context = useOptionalDevicContext();\n const resolvedBaseUrl = baseUrl || context?.baseUrl || \"https://api.devic.ai\";\n const resolvedTenantId = tenantId || context?.tenantId;\n const resolvedSubtenantId = subtenantId || context?.subtenantId;\n\n const resolvedApiKey = apiKey || context?.apiKey;\n const resolvedTenantSession = context?.getTenantSession;\n const onSessionExpired = context?.onSessionExpired;\n\n const client = useMemo(\n () =>\n resolvedApiKey || resolvedTenantSession\n ? new DevicApiClient({\n apiKey: resolvedApiKey,\n baseUrl: resolvedBaseUrl,\n getTenantSession: resolvedTenantSession,\n onSessionExpired,\n })\n : null,\n [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]\n );\n\n const [loading, setLoading] = useState(false);\n const [enabled, setEnabled] = useState(true);\n const [entries, setEntries] = useState<CoreMemoryEntry[]>([]);\n const [limits, setLimits] = useState<CoreMemoryLimits | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n\n // Inline composer / editor state\n const [composerOpen, setComposerOpen] = useState(false);\n const [composerSection, setComposerSection] = useState<string>(\"profile\");\n const [composerText, setComposerText] = useState(\"\");\n const [editingId, setEditingId] = useState<number | null>(null);\n const [editingText, setEditingText] = useState(\"\");\n\n const scopeOptions = useMemo(\n () => ({\n tenantId: resolvedTenantId || undefined,\n subtenantId: resolvedSubtenantId || undefined,\n }),\n [resolvedTenantId, resolvedSubtenantId]\n );\n\n const refresh = useCallback(async () => {\n if (!client) {\n setError(\"API key not configured\");\n return;\n }\n setLoading(true);\n setError(null);\n try {\n const list = await client.getCoreMemory(assistantId, scopeOptions);\n setEnabled(list.enabled);\n setEntries(list.entries);\n setLimits(list.limits ?? null);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setLoading(false);\n }\n }, [client, assistantId, scopeOptions]);\n\n // Fetch on every open (memory may have changed since the last look).\n const wasOpenRef = useRef(false);\n useEffect(() => {\n if (isOpen && !wasOpenRef.current) {\n setComposerOpen(false);\n setComposerText(\"\");\n setEditingId(null);\n void refresh();\n }\n wasOpenRef.current = isOpen;\n }, [isOpen, refresh]);\n\n // Escape closes\n useEffect(() => {\n if (!isOpen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [isOpen, onClose]);\n\n const runWrite = useCallback(\n async (write: () => Promise<unknown>) => {\n setSaving(true);\n setError(null);\n try {\n await write();\n await refresh();\n return true;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n return false;\n } finally {\n setSaving(false);\n }\n },\n [refresh]\n );\n\n const handleAdd = async () => {\n if (!client || !composerText.trim()) return;\n const ok = await runWrite(() =>\n client.addCoreMemoryEntry(\n assistantId,\n { content: composerText.trim(), section: composerSection },\n scopeOptions\n )\n );\n if (ok) {\n setComposerOpen(false);\n setComposerText(\"\");\n }\n };\n\n const handleSaveEdit = async (entry: CoreMemoryEntry) => {\n if (!client || !editingText.trim()) return;\n const ok = await runWrite(() =>\n client.updateCoreMemoryEntry(\n assistantId,\n entry.id,\n { content: editingText.trim() },\n scopeOptions\n )\n );\n if (ok) setEditingId(null);\n };\n\n const handleRemove = async (entry: CoreMemoryEntry) => {\n if (!client) return;\n await runWrite(() =>\n client.deleteCoreMemoryEntry(assistantId, entry.id, scopeOptions)\n );\n };\n\n if (!isOpen) return null;\n\n // Group entries by section, canonical sections first.\n const sections: Array<{ name: string; entries: CoreMemoryEntry[] }> = [];\n const bySection = new Map<string, CoreMemoryEntry[]>();\n for (const entry of entries) {\n const list = bySection.get(entry.section);\n if (list) list.push(entry);\n else bySection.set(entry.section, [entry]);\n }\n for (const name of SECTIONS) {\n const list = bySection.get(name);\n if (list) {\n sections.push({ name, entries: list });\n bySection.delete(name);\n }\n }\n for (const [name, list] of bySection) sections.push({ name, entries: list });\n\n return createPortal(\n // The variables go on the overlay: a portal inherits nothing from the\n // drawer that opened it, so without this the dialog ignores the theme.\n <div\n className=\"devic-cm-overlay\"\n style={themeVars(theme)}\n onClick={onClose}\n >\n <div\n className=\"devic-cm-modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"devic-cm-header\">\n <h3 className=\"devic-cm-title\">\n <BrainIcon />\n {title}\n </h3>\n <button\n className=\"devic-cm-close\"\n onClick={onClose}\n type=\"button\"\n aria-label=\"Close\"\n >\n ×\n </button>\n </div>\n\n <div className=\"devic-cm-body\">\n {error && <div className=\"devic-cm-error\">{error}</div>}\n\n {loading ? (\n <div className=\"devic-cm-loading\">Loading memory…</div>\n ) : !enabled ? (\n <div className=\"devic-cm-empty\">\n Memory is not enabled for this assistant.\n </div>\n ) : entries.length === 0 && !composerOpen ? (\n <div className=\"devic-cm-empty\">\n Nothing remembered yet. What the assistant learns and is told to\n keep will appear here.\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.name} className=\"devic-cm-section\">\n <div className=\"devic-cm-section-title\">{section.name}</div>\n {section.entries.map((entry) => (\n <div key={entry.id} className=\"devic-cm-entry\">\n {editingId === entry.id ? (\n <div className=\"devic-cm-editor\">\n <textarea\n className=\"devic-cm-textarea\"\n value={editingText}\n maxLength={limits?.maxEntryChars || undefined}\n onChange={(e) => setEditingText(e.target.value)}\n disabled={saving}\n autoFocus\n />\n <div className=\"devic-cm-editor-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-primary\"\n onClick={() => handleSaveEdit(entry)}\n disabled={saving || !editingText.trim()}\n >\n Save\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => setEditingId(null)}\n disabled={saving}\n >\n Cancel\n </button>\n </div>\n </div>\n ) : (\n <>\n <div className=\"devic-cm-entry-content\">\n {entry.pinned && (\n <span\n className=\"devic-cm-pin\"\n title=\"Pinned: always kept, never changed by the assistant\"\n >\n <PinIcon />\n </span>\n )}\n <span>{entry.content}</span>\n </div>\n {editable && (\n <div className=\"devic-cm-entry-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-small\"\n onClick={() => {\n setEditingId(entry.id);\n setEditingText(entry.content);\n }}\n disabled={saving}\n >\n Edit\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-small devic-cm-btn-danger\"\n onClick={() => handleRemove(entry)}\n disabled={saving}\n >\n Remove\n </button>\n </div>\n )}\n </>\n )}\n </div>\n ))}\n </div>\n ))\n )}\n\n {editable && enabled && !loading && (\n <div className=\"devic-cm-composer\">\n {composerOpen ? (\n <>\n <div className=\"devic-cm-composer-row\">\n <select\n className=\"devic-cm-select\"\n value={composerSection}\n onChange={(e) => setComposerSection(e.target.value)}\n disabled={saving}\n >\n {SECTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <textarea\n className=\"devic-cm-textarea\"\n placeholder=\"Something the assistant should always remember…\"\n value={composerText}\n maxLength={limits?.maxEntryChars || undefined}\n onChange={(e) => setComposerText(e.target.value)}\n disabled={saving}\n autoFocus\n />\n <div className=\"devic-cm-editor-actions\">\n <button\n type=\"button\"\n className=\"devic-cm-btn devic-cm-btn-primary\"\n onClick={handleAdd}\n disabled={saving || !composerText.trim()}\n >\n Add\n </button>\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => {\n setComposerOpen(false);\n setComposerText(\"\");\n }}\n disabled={saving}\n >\n Cancel\n </button>\n </div>\n </>\n ) : (\n <button\n type=\"button\"\n className=\"devic-cm-btn\"\n onClick={() => setComposerOpen(true)}\n disabled={saving}\n >\n + Add memory\n </button>\n )}\n </div>\n )}\n </div>\n\n {limits && enabled && (\n <div className=\"devic-cm-footer\">\n {entries.length}/{limits.maxEntries} entries · up to{\" \"}\n {limits.maxEntryChars} characters each\n </div>\n )}\n </div>\n </div>,\n document.body\n );\n}\n\nexport default CoreMemoryModal;\n"],"names":["_jsxs","_jsx","_Fragment"],"mappings":"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"}
@@ -163,27 +163,42 @@ function IntegrationsHint({ state, onOpen, label, maxLogos = 6, storageKey, flyT
163
163
  const bowX = ((-dy / len) * bow) * side;
164
164
  const bowY = ((dx / len) * bow) * side;
165
165
  const lift = Math.min(70, Math.max(18, len * 0.18));
166
- const animation = ghost.animate?.([
167
- {
168
- transform: "translate(0, 0) scale(1)",
169
- opacity: 0.95,
170
- offset: 0,
171
- easing: "cubic-bezier(0.22, 0.7, 0.4, 1)",
172
- },
173
- {
174
- // Bigger in the middle: it grows as it leaves the bar and shrinks
175
- // into the header, so the eye follows the thing rather than the fade.
176
- transform: `translate(${dx * 0.45 + bowX}px, ${dy * 0.5 - lift + bowY}px) scale(1.2)`,
177
- opacity: 1,
178
- offset: 0.5,
179
- easing: "cubic-bezier(0.5, 0, 0.7, 0.9)",
180
- },
181
- {
182
- transform: `translate(${dx}px, ${dy}px) scale(${scale})`,
183
- opacity: 0,
184
- offset: 1,
185
- },
186
- ], { duration: 780, fill: "forwards" });
166
+ // The curve the copy travels, as a quadratic Bézier through that midpoint.
167
+ // The control point is derived so the curve actually passes through it —
168
+ // a Bézier does not go through its control.
169
+ const midX = dx * 0.45 + bowX;
170
+ const midY = dy * 0.5 - lift + bowY;
171
+ const ctrlX = 2 * midX - dx / 2;
172
+ const ctrlY = 2 * midY - dy / 2;
173
+ // Sampled into many linear steps rather than three keyframes with an easing
174
+ // each. Per-keyframe easings ease OUT into the midpoint and IN out of it,
175
+ // which is a stop halfway the shape was right and the motion was two
176
+ // hops. Here the keyframes carry only the shape, and one global easing
177
+ // carries the pace: speed builds to a peak at the centre and settles into
178
+ // the target.
179
+ const STEPS = 24;
180
+ // Above the 1.2 of a plain keyframe: this rides on top of the shrink
181
+ // towards the target's size, so the two partly cancel at the apex.
182
+ const PEAK = 1.35;
183
+ const frames = Array.from({ length: STEPS + 1 }, (_, i) => {
184
+ const t = i / STEPS;
185
+ const u = 1 - t;
186
+ return {
187
+ offset: t,
188
+ transform: `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +
189
+ `${2 * u * t * ctrlY + t * t * dy}px) ` +
190
+ // Swells on the way and shrinks into the header, so the eye follows
191
+ // the thing rather than the fade.
192
+ `scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,
193
+ opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),
194
+ easing: "linear",
195
+ };
196
+ });
197
+ const animation = ghost.animate?.(frames, {
198
+ duration: 780,
199
+ easing: "cubic-bezier(0.5, 0, 0.5, 1)",
200
+ fill: "forwards",
201
+ });
187
202
  const remove = () => ghost.remove();
188
203
  if (animation)
189
204
  animation.onfinish = remove;
@@ -1 +1 @@
1
- {"version":3,"file":"IntegrationsHint.js","sources":["../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n const animation = ghost.animate?.(\n [\n {\n transform: \"translate(0, 0) scale(1)\",\n opacity: 0.95,\n offset: 0,\n easing: \"cubic-bezier(0.22, 0.7, 0.4, 1)\",\n },\n {\n // Bigger in the middle: it grows as it leaves the bar and shrinks\n // into the header, so the eye follows the thing rather than the fade.\n transform: `translate(${dx * 0.45 + bowX}px, ${dy * 0.5 - lift + bowY}px) scale(1.2)`,\n opacity: 1,\n offset: 0.5,\n easing: \"cubic-bezier(0.5, 0, 0.7, 0.9)\",\n },\n {\n transform: `translate(${dx}px, ${dy}px) scale(${scale})`,\n opacity: 0,\n offset: 1,\n },\n ],\n { duration: 780, fill: \"forwards\" }\n );\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["_jsxs","_jsx"],"mappings":"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"}
1
+ {"version":3,"file":"IntegrationsHint.js","sources":["../../../../src/components/IntegrationsModal/IntegrationsHint.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type JSX,\n} from \"react\";\nimport type { Integration } from \"../../api/types\";\nimport { IntegrationLogo } from \"./IntegrationLogo\";\nimport type { IntegrationsState } from \"./useIntegrations\";\nimport \"./IntegrationsModal.css\";\n\nexport interface IntegrationsHintProps {\n /** Shared listing (see `useIntegrations`). */\n state: IntegrationsState;\n /** Opens the connected-apps modal. */\n onOpen: () => void;\n /**\n * Text on the left. Defaults to \"Connect your apps\", or \"Explore connected\n * apps\" once at least one is connected — an invitation the user has already\n * accepted stops being an invitation.\n */\n label?: string;\n /** Most logos to show before the `+N` box. @default 6 */\n maxLogos?: number;\n /**\n * Identity the dismissal is remembered against, so closing it for one end\n * user does not close it for the next one on the same browser.\n */\n storageKey?: string;\n /** Where the bar flies to when closed — the header control. */\n flyToSelector?: string;\n /** Light chips for a dark surface. */\n dark?: boolean;\n className?: string;\n}\n\nconst STORAGE_PREFIX = \"devic:apps-hint-dismissed:\";\n\n/** Reading storage throws in Safari's private mode and in sandboxed frames. */\nfunction wasDismissed(key?: string): boolean {\n if (!key) return false;\n try {\n return localStorage.getItem(STORAGE_PREFIX + key) === \"1\";\n } catch {\n return false;\n }\n}\n\nfunction rememberDismissal(key?: string): void {\n if (!key) return;\n try {\n localStorage.setItem(STORAGE_PREFIX + key, \"1\");\n } catch {\n // Then it comes back next session. Not worth a word to the user.\n }\n}\n\n/** Connected first, same order as the header control. */\nfunction order(integrations: Integration[]): Integration[] {\n return [...integrations].sort((a, b) => {\n if (a.connected !== b.connected) return a.connected ? -1 : 1;\n return 0;\n });\n}\n\n/**\n * How many 24px chips fit once the label and the close button are paid for.\n *\n * The label is the whole reason this row exists, so it gets its space first:\n * a strip reading \"Connect your …\" beside seven logos has lost the argument.\n * Never fewer than three chips, below which the row stops reading as a set of\n * apps at all.\n */\nfunction boxesThatFit(width: number, max: number): number {\n if (!width) return max + 1;\n const LABEL_AND_CLOSE = 195;\n return Math.max(3, Math.min(max + 1, Math.floor((width - LABEL_AND_CLOSE) / 30)));\n}\n\n/** Whether an element is on screen right now. */\nfunction isVisible(el: Element): boolean {\n const r = el.getBoundingClientRect();\n if (!r.width || !r.height) return false;\n return (\n r.bottom > 0 &&\n r.right > 0 &&\n r.top < window.innerHeight &&\n r.left < window.innerWidth\n );\n}\n\n/**\n * The header control this bar belongs to.\n *\n * Scoped to its own drawer first — a page can hold several, each with its own\n * header — and only then to the nearest visible one anywhere. The class name is\n * checked against both the drawer's own container and the legacy one, because\n * getting this wrong is silent: `closest` returns null, the search widens to the\n * whole document, and the copy flies to whichever control happens to be first\n * in the DOM.\n */\nfunction flyTarget(bar: Element, selector: string): HTMLElement | null {\n if (!selector) return null;\n\n const drawer = bar.closest(\".devic-chat-drawer, .devic-drawer\");\n const own = drawer?.querySelector(selector) as HTMLElement | null;\n if (own && isVisible(own)) return own;\n\n const barRect = bar.getBoundingClientRect();\n const candidates = [...document.querySelectorAll(selector)].filter(isVisible);\n if (!candidates.length) return null;\n\n // Nearest to the bar, so a page with several drawers still animates towards\n // the one the user is looking at.\n return candidates.reduce((best, el) => {\n const d = (e: Element) => {\n const r = e.getBoundingClientRect();\n return Math.hypot(r.left - barRect.left, r.top - barRect.top);\n };\n return d(el) < d(best) ? el : best;\n }) as HTMLElement;\n}\n\n/**\n * The strip above the composer that tells the end user their own apps can be\n * connected here.\n *\n * It exists because the header control is a row of small logos and nothing\n * else: discoverable once you know what it is, invisible until then. This says\n * it in words, once, and then gets out of the way for good — the dismissal is\n * remembered per end user, and closing it animates towards the header so that\n * what was dismissed is understood to still be there.\n */\nexport function IntegrationsHint({\n state,\n onOpen,\n label,\n maxLogos = 6,\n storageKey,\n flyToSelector = \".devic-int-launcher\",\n dark = false,\n className = \"\",\n}: IntegrationsHintProps): JSX.Element | null {\n const sorted = useMemo(() => order(state.integrations), [state.integrations]);\n const ref = useRef<HTMLDivElement>(null);\n const [boxes, setBoxes] = useState(maxLogos + 1);\n const [dismissed, setDismissed] = useState(() => wasDismissed(storageKey));\n const [leaving, setLeaving] = useState(false);\n\n // A different end user has their own answer to whether this was dismissed.\n useEffect(() => {\n setDismissed(wasDismissed(storageKey));\n setLeaving(false);\n }, [storageKey]);\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n const measure = () =>\n setBoxes(boxesThatFit(el.getBoundingClientRect().width, maxLogos));\n measure();\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [maxLogos, state.offered, dismissed]);\n\n /**\n * Sends a copy of the logos from the bar to the header control.\n *\n * A dismissal that simply blanks the row reads as \"that is gone now\". The\n * flight is the sentence \"it moved up there\" said without words — so if it\n * cannot be drawn (reduced motion, no target, no Web Animations API), the bar\n * just closes and nothing is lost.\n */\n const flyToLauncher = useCallback(() => {\n const bar = ref.current;\n if (!bar || typeof document === \"undefined\") return;\n if (window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const target = flyTarget(bar, flyToSelector);\n // Nothing to fly to, or nothing the user can see: the bar just closes.\n // A copy sailing off towards something 2000px down the page says the\n // opposite of what this animation is for.\n if (!target) return;\n\n const logos = bar.querySelector(\".devic-int-hint-logos\");\n const from = (logos ?? bar).getBoundingClientRect();\n const to = target.getBoundingClientRect();\n\n const ghost = (logos ?? bar).cloneNode(true) as HTMLElement;\n ghost.classList.add(\"devic-int-hint-ghost\");\n ghost.style.left = `${from.left}px`;\n ghost.style.top = `${from.top}px`;\n ghost.style.width = `${from.width}px`;\n ghost.style.height = `${from.height}px`;\n document.body.appendChild(ghost);\n\n const dx = to.left + to.width / 2 - (from.left + from.width / 2);\n const dy = to.top + to.height / 2 - (from.top + from.height / 2);\n const scale = from.width ? Math.max(0.4, to.width / from.width) : 0.6;\n\n // An arc, not a straight line. The bow is perpendicular to the path, which\n // is what actually reads as a curve: this trip is nearly vertical, and\n // pulling only on Y just makes it accelerate. It opens away from the\n // target's side so the copy swings out and comes back in, and both terms\n // scale with the distance — a short hop should not loop — and are capped so\n // a long page does not throw it off screen.\n const len = Math.hypot(dx, dy) || 1;\n const bow = Math.min(48, Math.max(16, len * 0.18));\n const side = dx <= 0 ? 1 : -1;\n const bowX = ((-dy / len) * bow) * side;\n const bowY = ((dx / len) * bow) * side;\n const lift = Math.min(70, Math.max(18, len * 0.18));\n\n // The curve the copy travels, as a quadratic Bézier through that midpoint.\n // The control point is derived so the curve actually passes through it —\n // a Bézier does not go through its control.\n const midX = dx * 0.45 + bowX;\n const midY = dy * 0.5 - lift + bowY;\n const ctrlX = 2 * midX - dx / 2;\n const ctrlY = 2 * midY - dy / 2;\n\n // Sampled into many linear steps rather than three keyframes with an easing\n // each. Per-keyframe easings ease OUT into the midpoint and IN out of it,\n // which is a stop halfway — the shape was right and the motion was two\n // hops. Here the keyframes carry only the shape, and one global easing\n // carries the pace: speed builds to a peak at the centre and settles into\n // the target.\n const STEPS = 24;\n // Above the 1.2 of a plain keyframe: this rides on top of the shrink\n // towards the target's size, so the two partly cancel at the apex.\n const PEAK = 1.35;\n const frames = Array.from({ length: STEPS + 1 }, (_, i) => {\n const t = i / STEPS;\n const u = 1 - t;\n return {\n offset: t,\n transform:\n `translate(${2 * u * t * ctrlX + t * t * dx}px, ` +\n `${2 * u * t * ctrlY + t * t * dy}px) ` +\n // Swells on the way and shrinks into the header, so the eye follows\n // the thing rather than the fade.\n `scale(${1 + (scale - 1) * t + (PEAK - 1) * Math.sin(Math.PI * t)})`,\n opacity: t <= 0.5 ? 1 : 1 - Math.pow((t - 0.5) * 2, 1.6),\n easing: \"linear\",\n };\n });\n\n const animation = ghost.animate?.(frames, {\n duration: 780,\n easing: \"cubic-bezier(0.5, 0, 0.5, 1)\",\n fill: \"forwards\",\n });\n\n const remove = () => ghost.remove();\n if (animation) animation.onfinish = remove;\n else remove();\n // Belt and braces: a tab backgrounded mid-flight never fires onfinish.\n window.setTimeout(remove, 2000);\n }, [flyToSelector]);\n\n const dismiss = () => {\n flyToLauncher();\n rememberDismissal(storageKey);\n setLeaving(true);\n window.setTimeout(() => setDismissed(true), 220);\n };\n\n if (dismissed || !state.offered || sorted.length === 0) return null;\n\n const text =\n label ??\n (sorted.some((i) => i.connected)\n ? \"Explore connected apps\"\n : \"Connect your apps\");\n\n // When some apps will not fit, the `+N` box takes one of the slots itself.\n const capacity = Math.max(1, boxes);\n const shown = sorted.slice(\n 0,\n sorted.length > capacity ? capacity - 1 : capacity\n );\n const extra = sorted.length - shown.length;\n\n return (\n <div\n ref={ref}\n className={`devic-int-hint ${className}`.trim()}\n data-dark={dark}\n data-leaving={leaving}\n >\n <button\n type=\"button\"\n className=\"devic-int-hint-main\"\n onClick={onOpen}\n >\n <span className=\"devic-int-hint-label\">{text}</span>\n <span className=\"devic-int-hint-logos\" aria-hidden=\"true\">\n {shown.map((integration) => (\n <span\n key={integration.app}\n className=\"devic-int-hint-item\"\n data-connected={integration.connected}\n >\n <IntegrationLogo\n integration={integration}\n className=\"devic-int-hint-logo\"\n />\n </span>\n ))}\n {extra > 0 && (\n <span className=\"devic-int-hint-item devic-int-hint-more\">\n +{extra}\n </span>\n )}\n </span>\n </button>\n <button\n type=\"button\"\n className=\"devic-int-hint-close\"\n onClick={dismiss}\n aria-label=\"Hide this\"\n title=\"Hide this. Your apps stay in the header.\"\n >\n ×\n </button>\n </div>\n );\n}\n\nexport default IntegrationsHint;\n"],"names":["_jsxs","_jsx"],"mappings":";;;;AAsCA,MAAM,cAAc,GAAG,4BAA4B;AAEnD;AACA,SAAS,YAAY,CAAC,GAAY,EAAA;AAChC,IAAA,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,KAAK;AACtB,IAAA,IAAI;QACF,OAAO,YAAY,CAAC,OAAO,CAAC,cAAc,GAAG,GAAG,CAAC,KAAK,GAAG;IAC3D;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,KAAK;IACd;AACF;AAEA,SAAS,iBAAiB,CAAC,GAAY,EAAA;AACrC,IAAA,IAAI,CAAC,GAAG;QAAE;AACV,IAAA,IAAI;QACF,YAAY,CAAC,OAAO,CAAC,cAAc,GAAG,GAAG,EAAE,GAAG,CAAC;IACjD;AAAE,IAAA,MAAM;;IAER;AACF;AAEA;AACA,SAAS,KAAK,CAAC,YAA2B,EAAA;AACxC,IAAA,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAI;AACrC,QAAA,IAAI,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS;AAAE,YAAA,OAAO,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC;AAC5D,QAAA,OAAO,CAAC;AACV,IAAA,CAAC,CAAC;AACJ;AAEA;;;;;;;AAOG;AACH,SAAS,YAAY,CAAC,KAAa,EAAE,GAAW,EAAA;AAC9C,IAAA,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,GAAG,CAAC;IAC1B,MAAM,eAAe,GAAG,GAAG;AAC3B,IAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC;AACnF;AAEA;AACA,SAAS,SAAS,CAAC,EAAW,EAAA;AAC5B,IAAA,MAAM,CAAC,GAAG,EAAE,CAAC,qBAAqB,EAAE;IACpC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM;AAAE,QAAA,OAAO,KAAK;AACvC,IAAA,QACE,CAAC,CAAC,MAAM,GAAG,CAAC;QACZ,CAAC,CAAC,KAAK,GAAG,CAAC;AACX,QAAA,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,WAAW;AAC1B,QAAA,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,UAAU;AAE9B;AAEA;;;;;;;;;AASG;AACH,SAAS,SAAS,CAAC,GAAY,EAAE,QAAgB,EAAA;AAC/C,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,IAAI;IAE1B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,mCAAmC,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAuB;AACjE,IAAA,IAAI,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC;AAAE,QAAA,OAAO,GAAG;AAErC,IAAA,MAAM,OAAO,GAAG,GAAG,CAAC,qBAAqB,EAAE;AAC3C,IAAA,MAAM,UAAU,GAAG,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;IAC7E,IAAI,CAAC,UAAU,CAAC,MAAM;AAAE,QAAA,OAAO,IAAI;;;IAInC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,KAAI;AACpC,QAAA,MAAM,CAAC,GAAG,CAAC,CAAU,KAAI;AACvB,YAAA,MAAM,CAAC,GAAG,CAAC,CAAC,qBAAqB,EAAE;YACnC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;AAC/D,QAAA,CAAC;AACD,QAAA,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI;AACpC,IAAA,CAAC,CAAgB;AACnB;AAEA;;;;;;;;;AASG;AACG,SAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,MAAM,EACN,KAAK,EACL,QAAQ,GAAG,CAAC,EACZ,UAAU,EACV,aAAa,GAAG,qBAAqB,EACrC,IAAI,GAAG,KAAK,EACZ,SAAS,GAAG,EAAE,GACQ,EAAA;IACtB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;AAC7E,IAAA,MAAM,GAAG,GAAG,MAAM,CAAiB,IAAI,CAAC;AACxC,IAAA,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,QAAQ,GAAG,CAAC,CAAC;AAChD,IAAA,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;IAC1E,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;;IAG7C,SAAS,CAAC,MAAK;AACb,QAAA,YAAY,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QACtC,UAAU,CAAC,KAAK,CAAC;AACnB,IAAA,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;IAEhB,SAAS,CAAC,MAAK;AACb,QAAA,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO;AACtB,QAAA,IAAI,CAAC,EAAE,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE;AAClD,QAAA,MAAM,OAAO,GAAG,MACd,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACpE,QAAA,OAAO,EAAE;AACT,QAAA,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC;AAC5C,QAAA,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;AACpB,QAAA,OAAO,MAAM,QAAQ,CAAC,UAAU,EAAE;IACpC,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAExC;;;;;;;AAOG;AACH,IAAA,MAAM,aAAa,GAAG,WAAW,CAAC,MAAK;AACrC,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO;AACvB,QAAA,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE;QAC7C,IAAI,MAAM,CAAC,UAAU,GAAG,kCAAkC,CAAC,CAAC,OAAO;YAAE;QAErE,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC;;;;AAI5C,QAAA,IAAI,CAAC,MAAM;YAAE;QAEb,MAAM,KAAK,GAAG,GAAG,CAAC,aAAa,CAAC,uBAAuB,CAAC;QACxD,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,GAAG,EAAE,qBAAqB,EAAE;AACnD,QAAA,MAAM,EAAE,GAAG,MAAM,CAAC,qBAAqB,EAAE;AAEzC,QAAA,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,GAAG,EAAE,SAAS,CAAC,IAAI,CAAgB;AAC3D,QAAA,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,sBAAsB,CAAC;QAC3C,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,CAAA,EAAA,CAAI;QACnC,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAA,EAAA,CAAI;QACjC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,KAAK,CAAA,EAAA,CAAI;QACrC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAA,EAAA,CAAI;AACvC,QAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QAEhC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QAChE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG;;;;;;;AAQrE,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;AACnC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;AAClD,QAAA,MAAM,IAAI,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE;AAC7B,QAAA,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,IAAI;AACvC,QAAA,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,IAAI;AACtC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,GAAG,IAAI,CAAC,CAAC;;;;AAKnD,QAAA,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;QAC7B,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI;QACnC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC;;;;;;;QAQ/B,MAAM,KAAK,GAAG,EAAE;;;QAGhB,MAAM,IAAI,GAAG,IAAI;AACjB,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAI;AACxD,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK;AACnB,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YACf,OAAO;AACL,gBAAA,MAAM,EAAE,CAAC;AACT,gBAAA,SAAS,EACP,CAAA,UAAA,EAAa,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAA,IAAA,CAAM;AACjD,oBAAA,CAAA,EAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAA,IAAA,CAAM;;;oBAGvC,CAAA,MAAA,EAAS,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA,CAAA,CAAG;gBACtE,OAAO,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC;AACxD,gBAAA,MAAM,EAAE,QAAQ;aACjB;AACH,QAAA,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,GAAG,MAAM,EAAE;AACxC,YAAA,QAAQ,EAAE,GAAG;AACb,YAAA,MAAM,EAAE,8BAA8B;AACtC,YAAA,IAAI,EAAE,UAAU;AACjB,SAAA,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;AACnC,QAAA,IAAI,SAAS;AAAE,YAAA,SAAS,CAAC,QAAQ,GAAG,MAAM;;AACrC,YAAA,MAAM,EAAE;;AAEb,QAAA,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;AACjC,IAAA,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC;IAEnB,MAAM,OAAO,GAAG,MAAK;AACnB,QAAA,aAAa,EAAE;QACf,iBAAiB,CAAC,UAAU,CAAC;QAC7B,UAAU,CAAC,IAAI,CAAC;AAChB,QAAA,MAAM,CAAC,UAAU,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC;AAClD,IAAA,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,IAAI;IAEnE,MAAM,IAAI,GACR,KAAK;AACL,SAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS;AAC7B,cAAE;cACA,mBAAmB,CAAC;;IAG1B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC;IACnC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CACxB,CAAC,EACD,MAAM,CAAC,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,CAAC,GAAG,QAAQ,CACnD;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;IAE1C,QACEA,IAAA,CAAA,KAAA,EAAA,EACE,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAA,eAAA,EAAkB,SAAS,CAAA,CAAE,CAAC,IAAI,EAAE,EAAA,WAAA,EACpC,IAAI,EAAA,cAAA,EACD,OAAO,EAAA,QAAA,EAAA,CAErBA,IAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,qBAAqB,EAC/B,OAAO,EAAE,MAAM,EAAA,QAAA,EAAA,CAEfC,GAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,sBAAsB,EAAA,QAAA,EAAE,IAAI,EAAA,CAAQ,EACpDD,IAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,sBAAsB,EAAA,aAAA,EAAa,MAAM,EAAA,QAAA,EAAA,CACtD,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,MACrBC,GAAA,CAAA,MAAA,EAAA,EAEE,SAAS,EAAC,qBAAqB,oBACf,WAAW,CAAC,SAAS,EAAA,QAAA,EAErCA,GAAA,CAAC,eAAe,EAAA,EACd,WAAW,EAAE,WAAW,EACxB,SAAS,EAAC,qBAAqB,EAAA,CAC/B,EAAA,EAPG,WAAW,CAAC,GAAG,CAQf,CACR,CAAC,EACD,KAAK,GAAG,CAAC,KACRD,IAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAC,yCAAyC,EAAA,QAAA,EAAA,CAAA,GAAA,EACrD,KAAK,CAAA,EAAA,CACF,CACR,CAAA,EAAA,CACI,CAAA,EAAA,CACA,EACTC,GAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,sBAAsB,EAChC,OAAO,EAAE,OAAO,EAAA,YAAA,EACL,WAAW,EACtB,KAAK,EAAC,0CAA0C,EAAA,QAAA,EAAA,QAAA,EAAA,CAGzC,CAAA,EAAA,CACL;AAEV;;;;"}
@@ -20,13 +20,20 @@ function isRefusal(err) {
20
20
  function useIntegrations(options) {
21
21
  const { assistantId, tenantId, subtenantId, apiKey, baseUrl, enabled = true, } = options;
22
22
  const context = useOptionalDevicContext();
23
- const resolvedApiKey = apiKey || context?.apiKey;
24
23
  const resolvedBaseUrl = baseUrl || context?.baseUrl || "https://api.devic.ai";
25
24
  const resolvedTenantId = tenantId || context?.tenantId;
26
25
  const resolvedSubtenantId = subtenantId || context?.subtenantId;
27
- const client = useMemo(() => resolvedApiKey
28
- ? new DevicApiClient({ apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl })
29
- : null, [resolvedApiKey, resolvedBaseUrl]);
26
+ const resolvedApiKey = apiKey || context?.apiKey;
27
+ const resolvedTenantSession = context?.getTenantSession;
28
+ const onSessionExpired = context?.onSessionExpired;
29
+ const client = useMemo(() => resolvedApiKey || resolvedTenantSession
30
+ ? new DevicApiClient({
31
+ apiKey: resolvedApiKey,
32
+ baseUrl: resolvedBaseUrl,
33
+ getTenantSession: resolvedTenantSession,
34
+ onSessionExpired,
35
+ })
36
+ : null, [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]);
30
37
  const scope = useMemo(() => ({
31
38
  assistantId,
32
39
  tenantId: resolvedTenantId || undefined,
@@ -39,7 +46,7 @@ function useIntegrations(options) {
39
46
  const [settled, setSettled] = useState(false);
40
47
  const refresh = useCallback(async (dropCache = false) => {
41
48
  if (!client) {
42
- setError("API key not configured");
49
+ setError("No credentials configured");
43
50
  setSettled(true);
44
51
  return;
45
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useIntegrations.js","sources":["../../../../src/components/IntegrationsModal/useIntegrations.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { DevicApiClient } from \"../../api/client\";\nimport type { DevicApiError } from \"../../api/client\";\nimport type { Integration } from \"../../api/types\";\nimport { useOptionalDevicContext } from \"../../provider\";\n\n/** What identifies the end user in front of the widget. */\nexport interface IntegrationsScope {\n assistantId: string;\n tenantId?: string;\n subtenantId?: string;\n}\n\nexport interface UseIntegrationsOptions extends IntegrationsScope {\n /** API key override (falls back to the provider's). */\n apiKey?: string;\n /** Base URL override (falls back to the provider's). */\n baseUrl?: string;\n /**\n * Fetch when this turns true, and only then. The launcher passes the drawer's\n * open state so a widget nobody opens costs no request at all.\n */\n enabled?: boolean;\n}\n\nexport interface IntegrationsState {\n integrations: Integration[];\n loading: boolean;\n error: string | null;\n /**\n * Whether this assistant offers connected apps to its tenants at all.\n *\n * False before the first answer arrives, and false again if the server said\n * no — which it does for a disabled catalogue, a missing tenant, and an\n * assistant that does not exist, deliberately without distinguishing them.\n * Anything that hangs off this flag stays hidden until the server has said\n * yes, so nothing ever appears and then disappears.\n */\n offered: boolean;\n /** True once a first answer (or refusal) has arrived. */\n settled: boolean;\n refresh: (dropCache?: boolean) => Promise<void>;\n client: DevicApiClient | null;\n scope: IntegrationsScope;\n}\n\n/** A 4xx here means \"not for you\", not \"something broke\". */\nfunction isRefusal(err: unknown): boolean {\n const status = (err as DevicApiError)?.statusCode;\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/**\n * The apps an assistant offers to its tenants, with the accounts THIS tenant\n * has connected — loaded once and shared.\n *\n * Both the header button and the modal need the same listing: the button to\n * know whether it should exist and which logos to show, the modal to fill\n * itself. Fetching it in each of them would double every round trip and let the\n * two disagree for a moment after connecting an account.\n */\nexport function useIntegrations(\n options: UseIntegrationsOptions\n): IntegrationsState {\n const {\n assistantId,\n tenantId,\n subtenantId,\n apiKey,\n baseUrl,\n enabled = true,\n } = options;\n\n const context = useOptionalDevicContext();\n const resolvedApiKey = apiKey || context?.apiKey;\n const resolvedBaseUrl = baseUrl || context?.baseUrl || \"https://api.devic.ai\";\n const resolvedTenantId = tenantId || context?.tenantId;\n const resolvedSubtenantId = subtenantId || context?.subtenantId;\n\n const client = useMemo(\n () =>\n resolvedApiKey\n ? new DevicApiClient({ apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl })\n : null,\n [resolvedApiKey, resolvedBaseUrl]\n );\n\n const scope = useMemo(\n () => ({\n assistantId,\n tenantId: resolvedTenantId || undefined,\n subtenantId: resolvedSubtenantId || undefined,\n }),\n [assistantId, resolvedTenantId, resolvedSubtenantId]\n );\n\n const [integrations, setIntegrations] = useState<Integration[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [offered, setOffered] = useState(false);\n const [settled, setSettled] = useState(false);\n\n const refresh = useCallback(\n async (dropCache = false) => {\n if (!client) {\n setError(\"API key not configured\");\n setSettled(true);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n // The server caches a scope's connections briefly; after an OAuth round\n // trip that cache is exactly one step behind, so drop it first.\n if (dropCache) {\n await client.refreshIntegrations(scope).catch(() => undefined);\n }\n const list = await client.getIntegrations(scope);\n setIntegrations(list);\n setOffered(list.length > 0);\n } catch (err) {\n setIntegrations([]);\n setOffered(false);\n setError(err instanceof Error ? err.message : String(err));\n if (!isRefusal(err)) {\n // A refusal is an ordinary answer and stays quiet. A 500 or a dead\n // network is not, and swallowing it entirely would leave an\n // integrator with a button that never appears and no reason why.\n console.warn(\"[devic-ui] could not load connected apps\", err);\n }\n } finally {\n setLoading(false);\n setSettled(true);\n }\n },\n [client, scope]\n );\n\n // Once per scope, not once per render of whatever switched it on. A new\n // assistant or tenant is a different listing altogether, so it loads again —\n // and the previous tenant's apps are dropped first, because showing them\n // while the new ones arrive would name accounts that are not this user's.\n const key = `${client ? \"k\" : \"-\"}|${scope.assistantId}|${scope.tenantId ?? \"\"}|${scope.subtenantId ?? \"\"}`;\n const loadedKeyRef = useRef<string | null>(null);\n useEffect(() => {\n if (!enabled || loadedKeyRef.current === key) return;\n if (loadedKeyRef.current !== null) {\n setIntegrations([]);\n setOffered(false);\n setSettled(false);\n }\n loadedKeyRef.current = key;\n void refresh();\n }, [enabled, key, refresh]);\n\n return {\n integrations,\n loading,\n error,\n offered,\n settled,\n refresh,\n client,\n scope,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useIntegrations.js","sources":["../../../../src/components/IntegrationsModal/useIntegrations.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { DevicApiError } from \"../../api/client\";\nimport { DevicApiClient } from \"../../api/client\";\nimport type { Integration } from \"../../api/types\";\nimport { useOptionalDevicContext } from \"../../provider\";\n\n/** What identifies the end user in front of the widget. */\nexport interface IntegrationsScope {\n assistantId: string;\n tenantId?: string;\n subtenantId?: string;\n}\n\nexport interface UseIntegrationsOptions extends IntegrationsScope {\n /** API key override (falls back to the provider's). */\n apiKey?: string;\n /** Base URL override (falls back to the provider's). */\n baseUrl?: string;\n /**\n * Fetch when this turns true, and only then. The launcher passes the drawer's\n * open state so a widget nobody opens costs no request at all.\n */\n enabled?: boolean;\n}\n\nexport interface IntegrationsState {\n integrations: Integration[];\n loading: boolean;\n error: string | null;\n /**\n * Whether this assistant offers connected apps to its tenants at all.\n *\n * False before the first answer arrives, and false again if the server said\n * no — which it does for a disabled catalogue, a missing tenant, and an\n * assistant that does not exist, deliberately without distinguishing them.\n * Anything that hangs off this flag stays hidden until the server has said\n * yes, so nothing ever appears and then disappears.\n */\n offered: boolean;\n /** True once a first answer (or refusal) has arrived. */\n settled: boolean;\n refresh: (dropCache?: boolean) => Promise<void>;\n client: DevicApiClient | null;\n scope: IntegrationsScope;\n}\n\n/** A 4xx here means \"not for you\", not \"something broke\". */\nfunction isRefusal(err: unknown): boolean {\n const status = (err as DevicApiError)?.statusCode;\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/**\n * The apps an assistant offers to its tenants, with the accounts THIS tenant\n * has connected — loaded once and shared.\n *\n * Both the header button and the modal need the same listing: the button to\n * know whether it should exist and which logos to show, the modal to fill\n * itself. Fetching it in each of them would double every round trip and let the\n * two disagree for a moment after connecting an account.\n */\nexport function useIntegrations(\n options: UseIntegrationsOptions\n): IntegrationsState {\n const {\n assistantId,\n tenantId,\n subtenantId,\n apiKey,\n baseUrl,\n enabled = true,\n } = options;\n\n const context = useOptionalDevicContext();\n const resolvedBaseUrl = baseUrl || context?.baseUrl || \"https://api.devic.ai\";\n const resolvedTenantId = tenantId || context?.tenantId;\n const resolvedSubtenantId = subtenantId || context?.subtenantId;\n\n const resolvedApiKey = apiKey || context?.apiKey;\n const resolvedTenantSession = context?.getTenantSession;\n const onSessionExpired = context?.onSessionExpired;\n\n const client = useMemo(\n () =>\n resolvedApiKey || resolvedTenantSession\n ? new DevicApiClient({\n apiKey: resolvedApiKey,\n baseUrl: resolvedBaseUrl,\n getTenantSession: resolvedTenantSession,\n onSessionExpired,\n })\n : null,\n [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]\n );\n\n const scope = useMemo(\n () => ({\n assistantId,\n tenantId: resolvedTenantId || undefined,\n subtenantId: resolvedSubtenantId || undefined,\n }),\n [assistantId, resolvedTenantId, resolvedSubtenantId]\n );\n\n const [integrations, setIntegrations] = useState<Integration[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [offered, setOffered] = useState(false);\n const [settled, setSettled] = useState(false);\n\n const refresh = useCallback(\n async (dropCache = false) => {\n if (!client) {\n setError(\"No credentials configured\");\n setSettled(true);\n return;\n }\n setLoading(true);\n setError(null);\n try {\n // The server caches a scope's connections briefly; after an OAuth round\n // trip that cache is exactly one step behind, so drop it first.\n if (dropCache) {\n await client.refreshIntegrations(scope).catch(() => undefined);\n }\n const list = await client.getIntegrations(scope);\n setIntegrations(list);\n setOffered(list.length > 0);\n } catch (err) {\n setIntegrations([]);\n setOffered(false);\n setError(err instanceof Error ? err.message : String(err));\n if (!isRefusal(err)) {\n // A refusal is an ordinary answer and stays quiet. A 500 or a dead\n // network is not, and swallowing it entirely would leave an\n // integrator with a button that never appears and no reason why.\n console.warn(\"[devic-ui] could not load connected apps\", err);\n }\n } finally {\n setLoading(false);\n setSettled(true);\n }\n },\n [client, scope]\n );\n\n // Once per scope, not once per render of whatever switched it on. A new\n // assistant or tenant is a different listing altogether, so it loads again —\n // and the previous tenant's apps are dropped first, because showing them\n // while the new ones arrive would name accounts that are not this user's.\n const key = `${client ? \"k\" : \"-\"}|${scope.assistantId}|${scope.tenantId ?? \"\"}|${scope.subtenantId ?? \"\"}`;\n const loadedKeyRef = useRef<string | null>(null);\n useEffect(() => {\n if (!enabled || loadedKeyRef.current === key) return;\n if (loadedKeyRef.current !== null) {\n setIntegrations([]);\n setOffered(false);\n setSettled(false);\n }\n loadedKeyRef.current = key;\n void refresh();\n }, [enabled, key, refresh]);\n\n return {\n integrations,\n loading,\n error,\n offered,\n settled,\n refresh,\n client,\n scope,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -195,6 +195,8 @@ function ConfirmModal({ open, onClose, onConfirm, title, icon, message, confirmT
195
195
  function ThreadStateTag({ state, threadId, agentName, showIcon = true, customIcon, pausedReason, approvalRejectedMessage, finishReason, onActionComplete, pauseUntil, subthreadCount, showAdminActions = false, apiKey, baseUrl, interactive = true, }) {
196
196
  const context = useOptionalDevicContext();
197
197
  const resolvedApiKey = apiKey || context?.apiKey;
198
+ const resolvedTenantSession = context?.getTenantSession;
199
+ const onSessionExpired = context?.onSessionExpired;
198
200
  const resolvedBaseUrl = baseUrl || context?.baseUrl || 'https://api.devic.ai';
199
201
  // State
200
202
  const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -229,10 +231,15 @@ function ThreadStateTag({ state, threadId, agentName, showIcon = true, customIco
229
231
  }, [dropdownOpen]);
230
232
  // API client
231
233
  const getClient = useCallback(() => {
232
- if (!resolvedApiKey)
234
+ if (!resolvedApiKey && !resolvedTenantSession)
233
235
  return null;
234
- return new DevicApiClient({ apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl });
235
- }, [resolvedApiKey, resolvedBaseUrl]);
236
+ return new DevicApiClient({
237
+ apiKey: resolvedApiKey,
238
+ baseUrl: resolvedBaseUrl,
239
+ getTenantSession: resolvedTenantSession,
240
+ onSessionExpired,
241
+ });
242
+ }, [resolvedApiKey, resolvedTenantSession, resolvedBaseUrl]);
236
243
  // Config
237
244
  const config = getStateConfig(state, subthreadCount);
238
245
  if (!state) {