@devicai/ui 0.38.1 → 0.40.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.
- package/README.md +48 -4
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js +29 -13
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ChatInput.js +2 -2
- package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/cjs/components/IntegrationsModal/IntegrationLogo.js +23 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationLogo.js.map +1 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js +152 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationsHint.js.map +1 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js +68 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -0
- package/dist/cjs/components/IntegrationsModal/IntegrationsModal.js +78 -78
- package/dist/cjs/components/IntegrationsModal/IntegrationsModal.js.map +1 -1
- package/dist/cjs/components/IntegrationsModal/useIntegrations.js +106 -0
- package/dist/cjs/components/IntegrationsModal/useIntegrations.js.map +1 -0
- package/dist/cjs/components/theme.js +40 -0
- package/dist/cjs/components/theme.js.map +1 -1
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/styles.css +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.js +29 -13
- package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +34 -7
- package/dist/esm/components/ChatDrawer/ChatInput.js +2 -2
- package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/esm/components/IntegrationsModal/IntegrationLogo.d.ts +16 -0
- package/dist/esm/components/IntegrationsModal/IntegrationLogo.js +21 -0
- package/dist/esm/components/IntegrationsModal/IntegrationLogo.js.map +1 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsHint.d.ts +39 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsHint.js +150 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsHint.js.map +1 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.d.ts +31 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js +65 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsLauncher.js.map +1 -0
- package/dist/esm/components/IntegrationsModal/IntegrationsModal.d.ts +10 -1
- package/dist/esm/components/IntegrationsModal/IntegrationsModal.js +78 -78
- package/dist/esm/components/IntegrationsModal/IntegrationsModal.js.map +1 -1
- package/dist/esm/components/IntegrationsModal/index.d.ts +8 -0
- package/dist/esm/components/IntegrationsModal/useIntegrations.d.ts +49 -0
- package/dist/esm/components/IntegrationsModal/useIntegrations.js +104 -0
- package/dist/esm/components/IntegrationsModal/useIntegrations.js.map +1 -0
- package/dist/esm/components/theme.d.ts +9 -0
- package/dist/esm/components/theme.js +40 -1
- package/dist/esm/components/theme.js.map +1 -1
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DevicApiClient } from "../../api/client";
|
|
2
|
+
import type { Integration } from "../../api/types";
|
|
3
|
+
/** What identifies the end user in front of the widget. */
|
|
4
|
+
export interface IntegrationsScope {
|
|
5
|
+
assistantId: string;
|
|
6
|
+
tenantId?: string;
|
|
7
|
+
subtenantId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UseIntegrationsOptions extends IntegrationsScope {
|
|
10
|
+
/** API key override (falls back to the provider's). */
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
/** Base URL override (falls back to the provider's). */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Fetch when this turns true, and only then. The launcher passes the drawer's
|
|
16
|
+
* open state so a widget nobody opens costs no request at all.
|
|
17
|
+
*/
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface IntegrationsState {
|
|
21
|
+
integrations: Integration[];
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Whether this assistant offers connected apps to its tenants at all.
|
|
26
|
+
*
|
|
27
|
+
* False before the first answer arrives, and false again if the server said
|
|
28
|
+
* no — which it does for a disabled catalogue, a missing tenant, and an
|
|
29
|
+
* assistant that does not exist, deliberately without distinguishing them.
|
|
30
|
+
* Anything that hangs off this flag stays hidden until the server has said
|
|
31
|
+
* yes, so nothing ever appears and then disappears.
|
|
32
|
+
*/
|
|
33
|
+
offered: boolean;
|
|
34
|
+
/** True once a first answer (or refusal) has arrived. */
|
|
35
|
+
settled: boolean;
|
|
36
|
+
refresh: (dropCache?: boolean) => Promise<void>;
|
|
37
|
+
client: DevicApiClient | null;
|
|
38
|
+
scope: IntegrationsScope;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The apps an assistant offers to its tenants, with the accounts THIS tenant
|
|
42
|
+
* has connected — loaded once and shared.
|
|
43
|
+
*
|
|
44
|
+
* Both the header button and the modal need the same listing: the button to
|
|
45
|
+
* know whether it should exist and which logos to show, the modal to fill
|
|
46
|
+
* itself. Fetching it in each of them would double every round trip and let the
|
|
47
|
+
* two disagree for a moment after connecting an account.
|
|
48
|
+
*/
|
|
49
|
+
export declare function useIntegrations(options: UseIntegrationsOptions): IntegrationsState;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useMemo, useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { DevicApiClient } from '../../api/client.js';
|
|
3
|
+
import 'react/jsx-runtime';
|
|
4
|
+
import { useOptionalDevicContext } from '../../provider/DevicContext.js';
|
|
5
|
+
|
|
6
|
+
/** A 4xx here means "not for you", not "something broke". */
|
|
7
|
+
function isRefusal(err) {
|
|
8
|
+
const status = err?.statusCode;
|
|
9
|
+
return typeof status === "number" && status >= 400 && status < 500;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The apps an assistant offers to its tenants, with the accounts THIS tenant
|
|
13
|
+
* has connected — loaded once and shared.
|
|
14
|
+
*
|
|
15
|
+
* Both the header button and the modal need the same listing: the button to
|
|
16
|
+
* know whether it should exist and which logos to show, the modal to fill
|
|
17
|
+
* itself. Fetching it in each of them would double every round trip and let the
|
|
18
|
+
* two disagree for a moment after connecting an account.
|
|
19
|
+
*/
|
|
20
|
+
function useIntegrations(options) {
|
|
21
|
+
const { assistantId, tenantId, subtenantId, apiKey, baseUrl, enabled = true, } = options;
|
|
22
|
+
const context = useOptionalDevicContext();
|
|
23
|
+
const resolvedApiKey = apiKey || context?.apiKey;
|
|
24
|
+
const resolvedBaseUrl = baseUrl || context?.baseUrl || "https://api.devic.ai";
|
|
25
|
+
const resolvedTenantId = tenantId || context?.tenantId;
|
|
26
|
+
const resolvedSubtenantId = subtenantId || context?.subtenantId;
|
|
27
|
+
const client = useMemo(() => resolvedApiKey
|
|
28
|
+
? new DevicApiClient({ apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl })
|
|
29
|
+
: null, [resolvedApiKey, resolvedBaseUrl]);
|
|
30
|
+
const scope = useMemo(() => ({
|
|
31
|
+
assistantId,
|
|
32
|
+
tenantId: resolvedTenantId || undefined,
|
|
33
|
+
subtenantId: resolvedSubtenantId || undefined,
|
|
34
|
+
}), [assistantId, resolvedTenantId, resolvedSubtenantId]);
|
|
35
|
+
const [integrations, setIntegrations] = useState([]);
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
const [error, setError] = useState(null);
|
|
38
|
+
const [offered, setOffered] = useState(false);
|
|
39
|
+
const [settled, setSettled] = useState(false);
|
|
40
|
+
const refresh = useCallback(async (dropCache = false) => {
|
|
41
|
+
if (!client) {
|
|
42
|
+
setError("API key not configured");
|
|
43
|
+
setSettled(true);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setLoading(true);
|
|
47
|
+
setError(null);
|
|
48
|
+
try {
|
|
49
|
+
// The server caches a scope's connections briefly; after an OAuth round
|
|
50
|
+
// trip that cache is exactly one step behind, so drop it first.
|
|
51
|
+
if (dropCache) {
|
|
52
|
+
await client.refreshIntegrations(scope).catch(() => undefined);
|
|
53
|
+
}
|
|
54
|
+
const list = await client.getIntegrations(scope);
|
|
55
|
+
setIntegrations(list);
|
|
56
|
+
setOffered(list.length > 0);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
setIntegrations([]);
|
|
60
|
+
setOffered(false);
|
|
61
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
62
|
+
if (!isRefusal(err)) {
|
|
63
|
+
// A refusal is an ordinary answer and stays quiet. A 500 or a dead
|
|
64
|
+
// network is not, and swallowing it entirely would leave an
|
|
65
|
+
// integrator with a button that never appears and no reason why.
|
|
66
|
+
console.warn("[devic-ui] could not load connected apps", err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
setLoading(false);
|
|
71
|
+
setSettled(true);
|
|
72
|
+
}
|
|
73
|
+
}, [client, scope]);
|
|
74
|
+
// Once per scope, not once per render of whatever switched it on. A new
|
|
75
|
+
// assistant or tenant is a different listing altogether, so it loads again —
|
|
76
|
+
// and the previous tenant's apps are dropped first, because showing them
|
|
77
|
+
// while the new ones arrive would name accounts that are not this user's.
|
|
78
|
+
const key = `${client ? "k" : "-"}|${scope.assistantId}|${scope.tenantId ?? ""}|${scope.subtenantId ?? ""}`;
|
|
79
|
+
const loadedKeyRef = useRef(null);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!enabled || loadedKeyRef.current === key)
|
|
82
|
+
return;
|
|
83
|
+
if (loadedKeyRef.current !== null) {
|
|
84
|
+
setIntegrations([]);
|
|
85
|
+
setOffered(false);
|
|
86
|
+
setSettled(false);
|
|
87
|
+
}
|
|
88
|
+
loadedKeyRef.current = key;
|
|
89
|
+
void refresh();
|
|
90
|
+
}, [enabled, key, refresh]);
|
|
91
|
+
return {
|
|
92
|
+
integrations,
|
|
93
|
+
loading,
|
|
94
|
+
error,
|
|
95
|
+
offered,
|
|
96
|
+
settled,
|
|
97
|
+
refresh,
|
|
98
|
+
client,
|
|
99
|
+
scope,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { useIntegrations };
|
|
104
|
+
//# sourceMappingURL=useIntegrations.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -31,3 +31,12 @@ export interface DevicTheme {
|
|
|
31
31
|
* to the stylesheet default rather than be pinned to `undefined`.
|
|
32
32
|
*/
|
|
33
33
|
export declare function themeVars(theme?: DevicTheme): CSSProperties;
|
|
34
|
+
/**
|
|
35
|
+
* Whether this theme paints on a dark surface.
|
|
36
|
+
*
|
|
37
|
+
* Used for the one thing CSS variables cannot express: third-party app logos.
|
|
38
|
+
* Many are solid black with a transparent background, and on a dark chip they
|
|
39
|
+
* turn into a smudge — so on a dark theme the chip behind them goes light.
|
|
40
|
+
* Unknown or unreadable colours count as light, which is the default anyway.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isDarkTheme(theme?: DevicTheme): boolean;
|
|
@@ -26,6 +26,45 @@ function themeVars(theme) {
|
|
|
26
26
|
set("--devic-text-muted", theme.mutedTextColor);
|
|
27
27
|
return vars;
|
|
28
28
|
}
|
|
29
|
+
/** Relative luminance of a CSS colour, or null when it cannot be read. */
|
|
30
|
+
function luminance(color) {
|
|
31
|
+
if (!color)
|
|
32
|
+
return null;
|
|
33
|
+
const value = color.trim().toLowerCase();
|
|
34
|
+
let r, g, b;
|
|
35
|
+
const hex = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
|
36
|
+
if (hex) {
|
|
37
|
+
const digits = hex[1].length === 3
|
|
38
|
+
? hex[1]
|
|
39
|
+
.split("")
|
|
40
|
+
.map((d) => d + d)
|
|
41
|
+
.join("")
|
|
42
|
+
: hex[1];
|
|
43
|
+
r = parseInt(digits.slice(0, 2), 16);
|
|
44
|
+
g = parseInt(digits.slice(2, 4), 16);
|
|
45
|
+
b = parseInt(digits.slice(4, 6), 16);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const rgb = value.match(/^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/);
|
|
49
|
+
if (!rgb)
|
|
50
|
+
return null;
|
|
51
|
+
[r, g, b] = [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
|
|
52
|
+
}
|
|
53
|
+
// Perceived brightness, good enough to tell a dark surface from a light one.
|
|
54
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether this theme paints on a dark surface.
|
|
58
|
+
*
|
|
59
|
+
* Used for the one thing CSS variables cannot express: third-party app logos.
|
|
60
|
+
* Many are solid black with a transparent background, and on a dark chip they
|
|
61
|
+
* turn into a smudge — so on a dark theme the chip behind them goes light.
|
|
62
|
+
* Unknown or unreadable colours count as light, which is the default anyway.
|
|
63
|
+
*/
|
|
64
|
+
function isDarkTheme(theme) {
|
|
65
|
+
const l = luminance(theme?.backgroundColor) ?? luminance(theme?.secondaryBackgroundColor);
|
|
66
|
+
return l !== null && l < 0.45;
|
|
67
|
+
}
|
|
29
68
|
|
|
30
|
-
export { themeVars };
|
|
69
|
+
export { isDarkTheme, themeVars };
|
|
31
70
|
//# sourceMappingURL=theme.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","sources":["../../../src/components/theme.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\n\n/**\n * Colours and font a component should paint itself with.\n *\n * The same names the drawer's options use, so passing them through is a\n * one-liner and an integrator who already themed the drawer has nothing new to\n * learn.\n */\nexport interface DevicTheme {\n /** Accent: primary buttons, focus rings, links. */\n color?: string;\n fontFamily?: string;\n /** Surface the component sits on. */\n backgroundColor?: string;\n textColor?: string;\n /** Secondary surface: rows, chips, hover states. */\n secondaryBackgroundColor?: string;\n borderColor?: string;\n /** Muted text: captions, timestamps, empty states. */\n mutedTextColor?: string;\n}\n\n/**\n * A theme as inline CSS variables.\n *\n * Needed because the modals render through a portal into `document.body`: the\n * drawer sets these variables on its own element, and a portal is not inside\n * it, so nothing cascades down. A white dialog then opens over a dark\n * application, which is what this exists to prevent.\n *\n * Only the values actually given are emitted — an absent one must fall through\n * to the stylesheet default rather than be pinned to `undefined`.\n */\nexport function themeVars(theme?: DevicTheme): CSSProperties {\n if (!theme) return {};\n const vars: Record<string, string> = {};\n const set = (name: string, value?: string) => {\n if (value) vars[name] = value;\n };\n\n set(\"--devic-primary\", theme.color);\n set(\"--devic-font-family\", theme.fontFamily);\n set(\"--devic-bg\", theme.backgroundColor);\n set(\"--devic-text\", theme.textColor);\n set(\"--devic-bg-secondary\", theme.secondaryBackgroundColor);\n set(\"--devic-border\", theme.borderColor);\n set(\"--devic-text-muted\", theme.mutedTextColor);\n\n return vars as CSSProperties;\n}\n"],"names":[],"mappings":"AAuBA;;;;;;;;;;AAUG;AACG,SAAU,SAAS,CAAC,KAAkB,EAAA;AAC1C,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE;IACrB,MAAM,IAAI,GAA2B,EAAE;AACvC,IAAA,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAc,KAAI;AAC3C,QAAA,IAAI,KAAK;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK;AAC/B,IAAA,CAAC;AAED,IAAA,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC;AACnC,IAAA,GAAG,CAAC,qBAAqB,EAAE,KAAK,CAAC,UAAU,CAAC;AAC5C,IAAA,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,eAAe,CAAC;AACxC,IAAA,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC;AACpC,IAAA,GAAG,CAAC,sBAAsB,EAAE,KAAK,CAAC,wBAAwB,CAAC;AAC3D,IAAA,GAAG,CAAC,gBAAgB,EAAE,KAAK,CAAC,WAAW,CAAC;AACxC,IAAA,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,cAAc,CAAC;AAE/C,IAAA,OAAO,IAAqB;AAC9B;;;;"}
|
|
1
|
+
{"version":3,"file":"theme.js","sources":["../../../src/components/theme.ts"],"sourcesContent":["import type { CSSProperties } from \"react\";\n\n/**\n * Colours and font a component should paint itself with.\n *\n * The same names the drawer's options use, so passing them through is a\n * one-liner and an integrator who already themed the drawer has nothing new to\n * learn.\n */\nexport interface DevicTheme {\n /** Accent: primary buttons, focus rings, links. */\n color?: string;\n fontFamily?: string;\n /** Surface the component sits on. */\n backgroundColor?: string;\n textColor?: string;\n /** Secondary surface: rows, chips, hover states. */\n secondaryBackgroundColor?: string;\n borderColor?: string;\n /** Muted text: captions, timestamps, empty states. */\n mutedTextColor?: string;\n}\n\n/**\n * A theme as inline CSS variables.\n *\n * Needed because the modals render through a portal into `document.body`: the\n * drawer sets these variables on its own element, and a portal is not inside\n * it, so nothing cascades down. A white dialog then opens over a dark\n * application, which is what this exists to prevent.\n *\n * Only the values actually given are emitted — an absent one must fall through\n * to the stylesheet default rather than be pinned to `undefined`.\n */\nexport function themeVars(theme?: DevicTheme): CSSProperties {\n if (!theme) return {};\n const vars: Record<string, string> = {};\n const set = (name: string, value?: string) => {\n if (value) vars[name] = value;\n };\n\n set(\"--devic-primary\", theme.color);\n set(\"--devic-font-family\", theme.fontFamily);\n set(\"--devic-bg\", theme.backgroundColor);\n set(\"--devic-text\", theme.textColor);\n set(\"--devic-bg-secondary\", theme.secondaryBackgroundColor);\n set(\"--devic-border\", theme.borderColor);\n set(\"--devic-text-muted\", theme.mutedTextColor);\n\n return vars as CSSProperties;\n}\n\n/** Relative luminance of a CSS colour, or null when it cannot be read. */\nfunction luminance(color?: string): number | null {\n if (!color) return null;\n const value = color.trim().toLowerCase();\n\n let r: number, g: number, b: number;\n const hex = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);\n if (hex) {\n const digits =\n hex[1].length === 3\n ? hex[1]\n .split(\"\")\n .map((d) => d + d)\n .join(\"\")\n : hex[1];\n r = parseInt(digits.slice(0, 2), 16);\n g = parseInt(digits.slice(2, 4), 16);\n b = parseInt(digits.slice(4, 6), 16);\n } else {\n const rgb = value.match(/^rgba?\\(\\s*([\\d.]+)[\\s,]+([\\d.]+)[\\s,]+([\\d.]+)/);\n if (!rgb) return null;\n [r, g, b] = [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n }\n\n // Perceived brightness, good enough to tell a dark surface from a light one.\n return (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n}\n\n/**\n * Whether this theme paints on a dark surface.\n *\n * Used for the one thing CSS variables cannot express: third-party app logos.\n * Many are solid black with a transparent background, and on a dark chip they\n * turn into a smudge — so on a dark theme the chip behind them goes light.\n * Unknown or unreadable colours count as light, which is the default anyway.\n */\nexport function isDarkTheme(theme?: DevicTheme): boolean {\n const l = luminance(theme?.backgroundColor) ?? luminance(theme?.secondaryBackgroundColor);\n return l !== null && l < 0.45;\n}\n"],"names":[],"mappings":"AAuBA;;;;;;;;;;AAUG;AACG,SAAU,SAAS,CAAC,KAAkB,EAAA;AAC1C,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE;IACrB,MAAM,IAAI,GAA2B,EAAE;AACvC,IAAA,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAc,KAAI;AAC3C,QAAA,IAAI,KAAK;AAAE,YAAA,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK;AAC/B,IAAA,CAAC;AAED,IAAA,GAAG,CAAC,iBAAiB,EAAE,KAAK,CAAC,KAAK,CAAC;AACnC,IAAA,GAAG,CAAC,qBAAqB,EAAE,KAAK,CAAC,UAAU,CAAC;AAC5C,IAAA,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,eAAe,CAAC;AACxC,IAAA,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC;AACpC,IAAA,GAAG,CAAC,sBAAsB,EAAE,KAAK,CAAC,wBAAwB,CAAC;AAC3D,IAAA,GAAG,CAAC,gBAAgB,EAAE,KAAK,CAAC,WAAW,CAAC;AACxC,IAAA,GAAG,CAAC,oBAAoB,EAAE,KAAK,CAAC,cAAc,CAAC;AAE/C,IAAA,OAAO,IAAqB;AAC9B;AAEA;AACA,SAAS,SAAS,CAAC,KAAc,EAAA;AAC/B,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,IAAI;IACvB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;AAExC,IAAA,IAAI,CAAS,EAAE,CAAS,EAAE,CAAS;IACnC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC;IACvD,IAAI,GAAG,EAAE;QACP,MAAM,MAAM,GACV,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK;AAChB,cAAE,GAAG,CAAC,CAAC;iBACF,KAAK,CAAC,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;iBAChB,IAAI,CAAC,EAAE;AACZ,cAAE,GAAG,CAAC,CAAC,CAAC;AACZ,QAAA,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;AACpC,QAAA,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;AACpC,QAAA,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;IACtC;SAAO;QACL,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,iDAAiD,CAAC;AAC1E,QAAA,IAAI,CAAC,GAAG;AAAE,YAAA,OAAO,IAAI;AACrB,QAAA,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D;;AAGA,IAAA,OAAO,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,GAAG;AAClD;AAEA;;;;;;;AAOG;AACG,SAAU,WAAW,CAAC,KAAkB,EAAA;AAC5C,IAAA,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,EAAE,eAAe,CAAC,IAAI,SAAS,CAAC,KAAK,EAAE,wBAAwB,CAAC;AACzF,IAAA,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,IAAI;AAC/B;;;;"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ export { ChatDrawer, ChatMessages, ChatInput, ToolTimeline, ConversationSelector
|
|
|
4
4
|
export type { ChatDrawerProps, ChatDrawerOptions, ChatDrawerHandle, ChatMessagesProps, ChatInputProps, CustomPromptBoxProps, MessageBubbleRenderer, MessageBubbleRendererProps, ToolTimelineProps, AllowedFileTypes, ConversationSelectorProps, HandoffSubagentWidgetProps, ReferenceChipProps, ReferenceChipVariant, SuggestedMessage, UsageBarProps, UsageBarDisplay, UsageBarData, LimitBannerProps, RecalledMemoriesWidgetProps, RecalledMemoriesRenderer, RecalledMemoriesRendererProps, } from './components/ChatDrawer';
|
|
5
5
|
export { CoreMemoryModal } from './components/CoreMemoryModal';
|
|
6
6
|
export type { CoreMemoryModalProps } from './components/CoreMemoryModal';
|
|
7
|
-
export { IntegrationsModal } from './components/IntegrationsModal';
|
|
8
|
-
export type { IntegrationsModalProps } from './components/IntegrationsModal';
|
|
9
|
-
export { themeVars } from './components/theme';
|
|
7
|
+
export { IntegrationsModal, IntegrationsLauncher, IntegrationsHint, IntegrationLogo, useIntegrations, } from './components/IntegrationsModal';
|
|
8
|
+
export type { IntegrationsModalProps, IntegrationsLauncherProps, IntegrationsHintProps, IntegrationLogoProps, IntegrationsState, IntegrationsScope, UseIntegrationsOptions, } from './components/IntegrationsModal';
|
|
9
|
+
export { themeVars, isDarkTheme } from './components/theme';
|
|
10
10
|
export type { DevicTheme } from './components/theme';
|
|
11
11
|
export { ThreadStateTag } from './components/ThreadStateTag';
|
|
12
12
|
export type { ThreadStateTagProps, StateConfig } from './components/ThreadStateTag';
|
package/dist/esm/index.js
CHANGED
|
@@ -12,7 +12,11 @@ export { LimitBanner } from './components/ChatDrawer/LimitBanner.js';
|
|
|
12
12
|
export { RecalledMemoriesWidget } from './components/ChatDrawer/RecalledMemoriesWidget.js';
|
|
13
13
|
export { CoreMemoryModal } from './components/CoreMemoryModal/CoreMemoryModal.js';
|
|
14
14
|
export { IntegrationsModal } from './components/IntegrationsModal/IntegrationsModal.js';
|
|
15
|
-
export {
|
|
15
|
+
export { IntegrationsLauncher } from './components/IntegrationsModal/IntegrationsLauncher.js';
|
|
16
|
+
export { IntegrationsHint } from './components/IntegrationsModal/IntegrationsHint.js';
|
|
17
|
+
export { IntegrationLogo } from './components/IntegrationsModal/IntegrationLogo.js';
|
|
18
|
+
export { useIntegrations } from './components/IntegrationsModal/useIntegrations.js';
|
|
19
|
+
export { isDarkTheme, themeVars } from './components/theme.js';
|
|
16
20
|
export { ThreadStateTag } from './components/ThreadStateTag/ThreadStateTag.js';
|
|
17
21
|
export { AICommandBar } from './components/AICommandBar/AICommandBar.js';
|
|
18
22
|
export { formatShortcut, useAICommandBar } from './components/AICommandBar/useAICommandBar.js';
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|