@cms.ai/brand_brain 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -38
- package/dist/index.cjs +272 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +560 -16
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +560 -16
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +261 -10
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.cjs +261 -13
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +579 -20
- package/dist/react/index.d.cts.map +1 -1
- package/dist/react/index.d.mts +579 -20
- package/dist/react/index.d.mts.map +1 -1
- package/dist/react/index.mjs +261 -13
- package/dist/react/index.mjs.map +1 -1
- package/llms.txt +115 -0
- package/package.json +6 -5
- package/src/GuideClient.test.ts +28 -0
- package/src/GuideClient.ts +24 -3
- package/src/chat-stream.test.ts +28 -0
- package/src/chat-stream.ts +28 -3
- package/src/index.ts +2 -0
- package/src/react/index.ts +1 -1
- package/src/react/useGuide.ts +107 -6
- package/src/skill-state.test.ts +144 -0
- package/src/skill-state.ts +206 -0
- package/src/types.ts +504 -17
package/dist/index.mjs
CHANGED
|
@@ -1385,7 +1385,11 @@ const SSEMessageType = {
|
|
|
1385
1385
|
TextMessageEnd: "TEXT_MESSAGE_END",
|
|
1386
1386
|
Custom: "CUSTOM"
|
|
1387
1387
|
};
|
|
1388
|
-
/**
|
|
1388
|
+
/**
|
|
1389
|
+
* The `name` on `CUSTOM` guide events (skill payloads). `SkillGate` and
|
|
1390
|
+
* `SkillError` never surface as `skill` stream events — the SDK re-maps them to
|
|
1391
|
+
* the dedicated `gate` / `error` {@link GuideStreamEvent} variants.
|
|
1392
|
+
*/
|
|
1389
1393
|
const GuideEvent = {
|
|
1390
1394
|
SkillStart: "skill_start",
|
|
1391
1395
|
SkillContent: "skill_content",
|
|
@@ -1405,8 +1409,7 @@ const GuideEvent = {
|
|
|
1405
1409
|
SkillSolutionDesignDiagram: "skill_solutiondesign_diagram",
|
|
1406
1410
|
SkillEnd: "skill_end",
|
|
1407
1411
|
SkillError: "skill_error",
|
|
1408
|
-
SkillGate: "skill_gate"
|
|
1409
|
-
InputSuggestions: "input_suggestions"
|
|
1412
|
+
SkillGate: "skill_gate"
|
|
1410
1413
|
};
|
|
1411
1414
|
/** The guide skill types. `answer` is always on; the rest are enabled per-guide. */
|
|
1412
1415
|
const SkillResponseType = {
|
|
@@ -1432,6 +1435,74 @@ const EmbedTrackingEvent = {
|
|
|
1432
1435
|
EmbedSideTabClicked: "embed_side_tab_clicked",
|
|
1433
1436
|
EmbedSideTabDismissed: "embed_side_tab_dismissed"
|
|
1434
1437
|
};
|
|
1438
|
+
/** How a brand kit colors the guide's skill icons. */
|
|
1439
|
+
const SkillIconColorMode = {
|
|
1440
|
+
Default: "default",
|
|
1441
|
+
Mono: "mono",
|
|
1442
|
+
PerSkill: "per_skill"
|
|
1443
|
+
};
|
|
1444
|
+
/** Kind of content a document/playlist item points at. */
|
|
1445
|
+
const ContentType = {
|
|
1446
|
+
PDF: "pdf",
|
|
1447
|
+
DOCX: "docx",
|
|
1448
|
+
PPTX: "pptx",
|
|
1449
|
+
Image: "image",
|
|
1450
|
+
VideoNative: "video_native",
|
|
1451
|
+
VideoYouTube: "video_youtube",
|
|
1452
|
+
VideoWistia: "video_wistia",
|
|
1453
|
+
Document: "document",
|
|
1454
|
+
URL: "url",
|
|
1455
|
+
Text: "text",
|
|
1456
|
+
Markdown: "markdown"
|
|
1457
|
+
};
|
|
1458
|
+
/** Confidence level attached to a content recommendation. */
|
|
1459
|
+
const Confidence = {
|
|
1460
|
+
High: "high",
|
|
1461
|
+
Medium: "medium",
|
|
1462
|
+
Low: "low"
|
|
1463
|
+
};
|
|
1464
|
+
/** The render kind of a surfaced call-to-action. */
|
|
1465
|
+
const SkillCtaKind = {
|
|
1466
|
+
Url: "url",
|
|
1467
|
+
Form: "form",
|
|
1468
|
+
Product: "product",
|
|
1469
|
+
Scheduler: "scheduler"
|
|
1470
|
+
};
|
|
1471
|
+
/** Where a surfaced CTA renders. One slot today: below the skill response. */
|
|
1472
|
+
const CtaPlacement = { ResponseFooter: "response_footer" };
|
|
1473
|
+
/** Which scheduler embed a scheduler CTA loads. */
|
|
1474
|
+
const SchedulerProvider = {
|
|
1475
|
+
HubSpot: "hubspot",
|
|
1476
|
+
Calendly: "calendly"
|
|
1477
|
+
};
|
|
1478
|
+
/** Diagnose skill mode. */
|
|
1479
|
+
const DiagnoseSubtype = {
|
|
1480
|
+
Standalone: "standalone",
|
|
1481
|
+
ContextGathering: "context_gathering"
|
|
1482
|
+
};
|
|
1483
|
+
/** Answer option labels for diagnose questions. */
|
|
1484
|
+
const DiagnoseOptionLabel = {
|
|
1485
|
+
A: "A",
|
|
1486
|
+
B: "B",
|
|
1487
|
+
C: "C",
|
|
1488
|
+
D: "D"
|
|
1489
|
+
};
|
|
1490
|
+
/** Business case format sub-types. */
|
|
1491
|
+
const BusinessCaseSubType = {
|
|
1492
|
+
RoiAnalysis: "roi-analysis",
|
|
1493
|
+
StakeholderBrief: "stakeholder-brief",
|
|
1494
|
+
VendorComparison: "vendor-comparison"
|
|
1495
|
+
};
|
|
1496
|
+
/** Node types in a solution design diagram. */
|
|
1497
|
+
const SolutionDesignNodeType = {
|
|
1498
|
+
Process: "process",
|
|
1499
|
+
Decision: "decision",
|
|
1500
|
+
Datastore: "datastore",
|
|
1501
|
+
External: "external",
|
|
1502
|
+
Trigger: "trigger",
|
|
1503
|
+
Start: "start",
|
|
1504
|
+
End: "end"
|
|
1505
|
+
};
|
|
1435
1506
|
//#endregion
|
|
1436
1507
|
//#region src/chat-stream.ts
|
|
1437
1508
|
const DATA_PREFIX = "data:";
|
|
@@ -1459,7 +1530,13 @@ async function* streamConversation(params) {
|
|
|
1459
1530
|
body: JSON.stringify(body),
|
|
1460
1531
|
signal
|
|
1461
1532
|
});
|
|
1462
|
-
if (!response.ok || !response.body)
|
|
1533
|
+
if (!response.ok || !response.body) {
|
|
1534
|
+
let detail = "";
|
|
1535
|
+
try {
|
|
1536
|
+
detail = (await response.text()).trim();
|
|
1537
|
+
} catch {}
|
|
1538
|
+
throw new Error(`[brand_brain] Conversation request failed with status ${response.status}${detail ? `: ${detail}` : ""}`);
|
|
1539
|
+
}
|
|
1463
1540
|
const reader = response.body.getReader();
|
|
1464
1541
|
const decoder = new TextDecoder();
|
|
1465
1542
|
let buffer = "";
|
|
@@ -1518,6 +1595,13 @@ function mapFrame(frame) {
|
|
|
1518
1595
|
default: return null;
|
|
1519
1596
|
}
|
|
1520
1597
|
}
|
|
1598
|
+
/**
|
|
1599
|
+
* Names that surface as typed `skill` events. `SkillGate`/`SkillError` are
|
|
1600
|
+
* re-mapped to the `gate`/`error` variants below, so they are excluded here;
|
|
1601
|
+
* any name outside this set is a server event this SDK version predates and
|
|
1602
|
+
* flows through as `skill-unknown`.
|
|
1603
|
+
*/
|
|
1604
|
+
const SKILL_EVENT_NAMES = new Set(Object.values(GuideEvent).filter((name) => name !== GuideEvent.SkillGate && name !== GuideEvent.SkillError));
|
|
1521
1605
|
function mapCustomFrame(frame) {
|
|
1522
1606
|
if (!frame.name) return null;
|
|
1523
1607
|
const value = frame.value ?? {};
|
|
@@ -1530,20 +1614,42 @@ function mapCustomFrame(frame) {
|
|
|
1530
1614
|
if (frame.name === GuideEvent.SkillError) return {
|
|
1531
1615
|
type: "error",
|
|
1532
1616
|
message: value.message ?? "Skill error",
|
|
1533
|
-
code: value.errorType
|
|
1617
|
+
code: value.errorType,
|
|
1618
|
+
retryable: value.retryable,
|
|
1619
|
+
retryHint: value.retryHint
|
|
1534
1620
|
};
|
|
1535
|
-
return {
|
|
1621
|
+
if (SKILL_EVENT_NAMES.has(frame.name)) return {
|
|
1536
1622
|
type: "skill",
|
|
1537
1623
|
name: frame.name,
|
|
1538
1624
|
value
|
|
1539
1625
|
};
|
|
1626
|
+
return {
|
|
1627
|
+
type: "skill-unknown",
|
|
1628
|
+
name: frame.name,
|
|
1629
|
+
value
|
|
1630
|
+
};
|
|
1540
1631
|
}
|
|
1541
1632
|
//#endregion
|
|
1542
1633
|
//#region src/GuideClient.ts
|
|
1634
|
+
/**
|
|
1635
|
+
* The account is resolved by the branded host the SDK talks to, and that host
|
|
1636
|
+
* must be same-site with `baseUrl` for the visitor cookies to attach — so the
|
|
1637
|
+
* `baseUrl` hostname IS the registered account domain. Derive it from there
|
|
1638
|
+
* rather than the host page's `window.location`, which is an unrelated origin
|
|
1639
|
+
* for cross-site embeds and produces a "Domain not found" 404 on resolve.
|
|
1640
|
+
*/
|
|
1543
1641
|
function resolveDomain(config) {
|
|
1544
1642
|
if (config.domain) return config.domain;
|
|
1545
|
-
|
|
1546
|
-
|
|
1643
|
+
return hostnameFromBaseUrl(config.baseUrl);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Normalize `baseUrl` to the bare host the server stores: lowercased, no port,
|
|
1647
|
+
* no leading `www.` (the domain write-path strips it, so a `www.` host can
|
|
1648
|
+
* never match). Accepts a bare host too, in case `baseUrl` omits the protocol.
|
|
1649
|
+
*/
|
|
1650
|
+
function hostnameFromBaseUrl(baseUrl) {
|
|
1651
|
+
const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseUrl) ? baseUrl : `https://${baseUrl}`;
|
|
1652
|
+
return new URL(withProtocol).hostname.toLowerCase().replace(/^www\./, "");
|
|
1547
1653
|
}
|
|
1548
1654
|
/**
|
|
1549
1655
|
* The client returned by {@link initGuide}. Holds the resolved guide, the
|
|
@@ -1597,7 +1703,9 @@ var GuideClient = class GuideClient {
|
|
|
1597
1703
|
this.#api = createApiClient(this.apiBaseUrl, this.fetchImpl);
|
|
1598
1704
|
}
|
|
1599
1705
|
static async init(config) {
|
|
1600
|
-
const
|
|
1706
|
+
const globalFetch = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
|
|
1707
|
+
const fetchImpl = config.fetch ?? globalFetch;
|
|
1708
|
+
if (!fetchImpl) throw new Error("[brand_brain] No `fetch` is available — pass `config.fetch` when running without a global fetch.");
|
|
1601
1709
|
const apiBaseUrl = toApiBaseUrl(config.baseUrl);
|
|
1602
1710
|
const domain = resolveDomain(config);
|
|
1603
1711
|
const guide = await createApiClient(apiBaseUrl, fetchImpl).guides.resolve({
|
|
@@ -1736,6 +1844,149 @@ async function initGuide(config) {
|
|
|
1736
1844
|
return GuideClient.init(config);
|
|
1737
1845
|
}
|
|
1738
1846
|
//#endregion
|
|
1739
|
-
|
|
1847
|
+
//#region src/skill-state.ts
|
|
1848
|
+
/** A fresh, empty turn state. */
|
|
1849
|
+
function createSkillTurn() {
|
|
1850
|
+
return {
|
|
1851
|
+
skill: null,
|
|
1852
|
+
status: "streaming",
|
|
1853
|
+
messageId: null,
|
|
1854
|
+
diagnoseSubtype: null,
|
|
1855
|
+
followUpSkill: null,
|
|
1856
|
+
content: "",
|
|
1857
|
+
documents: [],
|
|
1858
|
+
contentRecommendations: [],
|
|
1859
|
+
followUpRecommendations: [],
|
|
1860
|
+
ctas: [],
|
|
1861
|
+
diagnoseHeader: null,
|
|
1862
|
+
diagnoseQuestions: [],
|
|
1863
|
+
playlistHeader: null,
|
|
1864
|
+
playlistItems: [],
|
|
1865
|
+
howToOutline: null,
|
|
1866
|
+
howToSteps: [],
|
|
1867
|
+
businessCaseFormats: [],
|
|
1868
|
+
businessCaseDrafts: {},
|
|
1869
|
+
businessCaseContent: {},
|
|
1870
|
+
solutionDesignHeader: null,
|
|
1871
|
+
solutionDesignNodes: [],
|
|
1872
|
+
solutionDesignEdges: [],
|
|
1873
|
+
skillInstanceId: null
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
/** A recommendations payload is content cards iff its entries carry `targetSkill`. */
|
|
1877
|
+
function isContentRecommendations(recommendations) {
|
|
1878
|
+
return recommendations.length > 0 && "targetSkill" in recommendations[0];
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Fold one `skill` stream event into the turn state. Pure and immutable — the
|
|
1882
|
+
* returned object is a new reference whenever anything changed, so the result
|
|
1883
|
+
* can back React state directly:
|
|
1884
|
+
*
|
|
1885
|
+
* ```ts
|
|
1886
|
+
* let turn = createSkillTurn();
|
|
1887
|
+
* for await (const event of client.ask(message)) {
|
|
1888
|
+
* if (event.type === "skill") turn = reduceSkillEvent(turn, event);
|
|
1889
|
+
* }
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1892
|
+
function reduceSkillEvent(turn, event) {
|
|
1893
|
+
switch (event.name) {
|
|
1894
|
+
case GuideEvent.SkillStart: return {
|
|
1895
|
+
...event.value.skill === SkillResponseType.BusinessCase && turn.skill === SkillResponseType.BusinessCase ? {
|
|
1896
|
+
...createSkillTurn(),
|
|
1897
|
+
businessCaseFormats: turn.businessCaseFormats,
|
|
1898
|
+
businessCaseDrafts: turn.businessCaseDrafts,
|
|
1899
|
+
businessCaseContent: turn.businessCaseContent
|
|
1900
|
+
} : createSkillTurn(),
|
|
1901
|
+
skill: event.value.skill,
|
|
1902
|
+
messageId: event.value.messageId,
|
|
1903
|
+
diagnoseSubtype: event.value.diagnoseSubtype ?? null,
|
|
1904
|
+
followUpSkill: event.value.followUpSkill ?? null
|
|
1905
|
+
};
|
|
1906
|
+
case GuideEvent.SkillContent: return {
|
|
1907
|
+
...turn,
|
|
1908
|
+
content: turn.content + event.value.delta
|
|
1909
|
+
};
|
|
1910
|
+
case GuideEvent.SkillDocuments: return {
|
|
1911
|
+
...turn,
|
|
1912
|
+
documents: event.value.documents
|
|
1913
|
+
};
|
|
1914
|
+
case GuideEvent.SkillRecommendations: return isContentRecommendations(event.value.recommendations) ? {
|
|
1915
|
+
...turn,
|
|
1916
|
+
contentRecommendations: event.value.recommendations
|
|
1917
|
+
} : {
|
|
1918
|
+
...turn,
|
|
1919
|
+
followUpRecommendations: event.value.recommendations
|
|
1920
|
+
};
|
|
1921
|
+
case GuideEvent.SkillCTAs: return {
|
|
1922
|
+
...turn,
|
|
1923
|
+
ctas: event.value.ctas
|
|
1924
|
+
};
|
|
1925
|
+
case GuideEvent.SkillDiagnoseHeader: return {
|
|
1926
|
+
...turn,
|
|
1927
|
+
diagnoseHeader: event.value.header
|
|
1928
|
+
};
|
|
1929
|
+
case GuideEvent.SkillDiagnoseQuestion: return {
|
|
1930
|
+
...turn,
|
|
1931
|
+
diagnoseQuestions: [...turn.diagnoseQuestions, event.value.question]
|
|
1932
|
+
};
|
|
1933
|
+
case GuideEvent.SkillPlaylistHeader: return {
|
|
1934
|
+
...turn,
|
|
1935
|
+
playlistHeader: event.value.header
|
|
1936
|
+
};
|
|
1937
|
+
case GuideEvent.SkillPlaylistItems: return {
|
|
1938
|
+
...turn,
|
|
1939
|
+
playlistItems: event.value.items
|
|
1940
|
+
};
|
|
1941
|
+
case GuideEvent.SkillPlaylistRationale: return {
|
|
1942
|
+
...turn,
|
|
1943
|
+
playlistItems: turn.playlistItems.map((item) => item.id === event.value.itemId ? {
|
|
1944
|
+
...item,
|
|
1945
|
+
rationale: event.value.rationale
|
|
1946
|
+
} : item)
|
|
1947
|
+
};
|
|
1948
|
+
case GuideEvent.SkillHowToOutline: return {
|
|
1949
|
+
...turn,
|
|
1950
|
+
howToOutline: event.value.outline,
|
|
1951
|
+
skillInstanceId: event.value.skillInstanceId ?? turn.skillInstanceId
|
|
1952
|
+
};
|
|
1953
|
+
case GuideEvent.SkillHowToSteps: return {
|
|
1954
|
+
...turn,
|
|
1955
|
+
howToSteps: event.value.steps
|
|
1956
|
+
};
|
|
1957
|
+
case GuideEvent.SkillBusinessCaseChooser: return {
|
|
1958
|
+
...turn,
|
|
1959
|
+
businessCaseFormats: event.value.formats,
|
|
1960
|
+
businessCaseDrafts: event.value.drafts
|
|
1961
|
+
};
|
|
1962
|
+
case GuideEvent.SkillBusinessCaseDelta: {
|
|
1963
|
+
const existing = turn.businessCaseContent[event.value.subType] ?? "";
|
|
1964
|
+
return {
|
|
1965
|
+
...turn,
|
|
1966
|
+
businessCaseContent: {
|
|
1967
|
+
...turn.businessCaseContent,
|
|
1968
|
+
[event.value.subType]: existing + event.value.delta
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
case GuideEvent.SkillSolutionDesignHeader: return {
|
|
1973
|
+
...turn,
|
|
1974
|
+
solutionDesignHeader: event.value.header
|
|
1975
|
+
};
|
|
1976
|
+
case GuideEvent.SkillSolutionDesignDiagram: return {
|
|
1977
|
+
...turn,
|
|
1978
|
+
solutionDesignNodes: event.value.nodes,
|
|
1979
|
+
solutionDesignEdges: event.value.edges
|
|
1980
|
+
};
|
|
1981
|
+
case GuideEvent.SkillEnd: return {
|
|
1982
|
+
...turn,
|
|
1983
|
+
status: "complete",
|
|
1984
|
+
skillInstanceId: event.value.skillInstanceId ?? event.value.skillResponseId ?? turn.skillInstanceId
|
|
1985
|
+
};
|
|
1986
|
+
default: return event;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
//#endregion
|
|
1990
|
+
export { BusinessCaseSubType, Confidence, ConsentTier, ContentType, CtaPlacement, DiagnoseOptionLabel, DiagnoseSubtype, EmbedTrackingEvent, GuideClient, GuideEvent, SSEMessageType, SchedulerProvider, SkillCtaKind, SkillIconColorMode, SkillResponseType, SolutionDesignNodeType, createSkillTurn, initGuide, reduceSkillEvent };
|
|
1740
1991
|
|
|
1741
1992
|
//# sourceMappingURL=index.mjs.map
|