@frontmcp/ui 0.6.1 → 0.6.2
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/bridge/core/bridge-factory.d.ts +1 -0
- package/bridge/core/bridge-factory.d.ts.map +1 -1
- package/bridge/index.d.ts +1 -1
- package/bridge/index.d.ts.map +1 -1
- package/bridge/index.js +39 -881
- package/bundler/browser-components.d.ts +42 -0
- package/bundler/browser-components.d.ts.map +1 -0
- package/bundler/bundler.d.ts +78 -4
- package/bundler/bundler.d.ts.map +1 -1
- package/bundler/index.d.ts +8 -8
- package/bundler/index.d.ts.map +1 -1
- package/bundler/index.js +1315 -1854
- package/bundler/types.d.ts +188 -7
- package/bundler/types.d.ts.map +1 -1
- package/esm/bridge/{index.js → index.mjs} +40 -877
- package/esm/bundler/{index.js → index.mjs} +1391 -1895
- package/esm/{index.js → index.mjs} +215 -3091
- package/esm/layouts/{index.js → index.mjs} +3 -3
- package/esm/package.json +9 -8
- package/esm/react/index.mjs +1183 -0
- package/esm/renderers/index.mjs +611 -0
- package/esm/universal/{index.js → index.mjs} +266 -70
- package/index.d.ts +1 -4
- package/index.d.ts.map +1 -1
- package/index.js +208 -3113
- package/layouts/base.d.ts.map +1 -1
- package/layouts/index.js +3 -3
- package/layouts/presets.d.ts.map +1 -1
- package/package.json +9 -8
- package/react/Badge.d.ts.map +1 -1
- package/react/hooks/context.d.ts.map +1 -1
- package/react/index.d.ts +0 -1
- package/react/index.d.ts.map +1 -1
- package/react/index.js +57 -2001
- package/react/types.d.ts.map +1 -1
- package/renderers/index.d.ts +9 -4
- package/renderers/index.d.ts.map +1 -1
- package/renderers/index.js +328 -88
- package/renderers/mdx.renderer.d.ts +99 -0
- package/renderers/mdx.renderer.d.ts.map +1 -0
- package/renderers/react.renderer.d.ts +22 -13
- package/renderers/react.renderer.d.ts.map +1 -1
- package/renderers/transpiler.d.ts +49 -0
- package/renderers/transpiler.d.ts.map +1 -0
- package/universal/cached-runtime.d.ts +25 -1
- package/universal/cached-runtime.d.ts.map +1 -1
- package/universal/index.js +266 -70
- package/universal/runtime-builder.d.ts.map +1 -1
- package/universal/types.d.ts.map +1 -1
- package/web-components/elements/fmcp-input.d.ts.map +1 -1
- package/web-components/elements/fmcp-select.d.ts.map +1 -1
- package/web-components/index.d.ts +0 -1
- package/web-components/index.d.ts.map +1 -1
- package/bundler/cache.d.ts +0 -173
- package/bundler/cache.d.ts.map +0 -1
- package/bundler/file-cache/component-builder.d.ts +0 -167
- package/bundler/file-cache/component-builder.d.ts.map +0 -1
- package/bundler/file-cache/hash-calculator.d.ts +0 -155
- package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
- package/bundler/file-cache/index.d.ts +0 -12
- package/bundler/file-cache/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/filesystem.d.ts +0 -149
- package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
- package/bundler/file-cache/storage/index.d.ts +0 -11
- package/bundler/file-cache/storage/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/interface.d.ts +0 -152
- package/bundler/file-cache/storage/interface.d.ts.map +0 -1
- package/bundler/file-cache/storage/redis.d.ts +0 -139
- package/bundler/file-cache/storage/redis.d.ts.map +0 -1
- package/bundler/sandbox/enclave-adapter.d.ts +0 -121
- package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
- package/bundler/sandbox/executor.d.ts +0 -14
- package/bundler/sandbox/executor.d.ts.map +0 -1
- package/bundler/sandbox/policy.d.ts +0 -62
- package/bundler/sandbox/policy.d.ts.map +0 -1
- package/esm/bridge/adapters/base-adapter.d.ts +0 -104
- package/esm/bridge/adapters/base-adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/claude.adapter.d.ts +0 -67
- package/esm/bridge/adapters/claude.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/ext-apps.adapter.d.ts +0 -143
- package/esm/bridge/adapters/ext-apps.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/gemini.adapter.d.ts +0 -64
- package/esm/bridge/adapters/gemini.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/generic.adapter.d.ts +0 -56
- package/esm/bridge/adapters/generic.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/index.d.ts +0 -26
- package/esm/bridge/adapters/index.d.ts.map +0 -1
- package/esm/bridge/adapters/openai.adapter.d.ts +0 -65
- package/esm/bridge/adapters/openai.adapter.d.ts.map +0 -1
- package/esm/bridge/core/adapter-registry.d.ts +0 -122
- package/esm/bridge/core/adapter-registry.d.ts.map +0 -1
- package/esm/bridge/core/bridge-factory.d.ts +0 -199
- package/esm/bridge/core/bridge-factory.d.ts.map +0 -1
- package/esm/bridge/core/index.d.ts +0 -10
- package/esm/bridge/core/index.d.ts.map +0 -1
- package/esm/bridge/index.d.ts +0 -62
- package/esm/bridge/index.d.ts.map +0 -1
- package/esm/bridge/runtime/iife-generator.d.ts +0 -62
- package/esm/bridge/runtime/iife-generator.d.ts.map +0 -1
- package/esm/bridge/runtime/index.d.ts +0 -10
- package/esm/bridge/runtime/index.d.ts.map +0 -1
- package/esm/bridge/types.d.ts +0 -386
- package/esm/bridge/types.d.ts.map +0 -1
- package/esm/bundler/bundler.d.ts +0 -208
- package/esm/bundler/bundler.d.ts.map +0 -1
- package/esm/bundler/cache.d.ts +0 -173
- package/esm/bundler/cache.d.ts.map +0 -1
- package/esm/bundler/file-cache/component-builder.d.ts +0 -167
- package/esm/bundler/file-cache/component-builder.d.ts.map +0 -1
- package/esm/bundler/file-cache/hash-calculator.d.ts +0 -155
- package/esm/bundler/file-cache/hash-calculator.d.ts.map +0 -1
- package/esm/bundler/file-cache/index.d.ts +0 -12
- package/esm/bundler/file-cache/index.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/filesystem.d.ts +0 -149
- package/esm/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/index.d.ts +0 -11
- package/esm/bundler/file-cache/storage/index.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/interface.d.ts +0 -152
- package/esm/bundler/file-cache/storage/interface.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/redis.d.ts +0 -139
- package/esm/bundler/file-cache/storage/redis.d.ts.map +0 -1
- package/esm/bundler/index.d.ts +0 -43
- package/esm/bundler/index.d.ts.map +0 -1
- package/esm/bundler/sandbox/enclave-adapter.d.ts +0 -121
- package/esm/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
- package/esm/bundler/sandbox/executor.d.ts +0 -14
- package/esm/bundler/sandbox/executor.d.ts.map +0 -1
- package/esm/bundler/sandbox/policy.d.ts +0 -62
- package/esm/bundler/sandbox/policy.d.ts.map +0 -1
- package/esm/bundler/types.d.ts +0 -702
- package/esm/bundler/types.d.ts.map +0 -1
- package/esm/components/alert.d.ts +0 -66
- package/esm/components/alert.d.ts.map +0 -1
- package/esm/components/alert.schema.d.ts +0 -98
- package/esm/components/alert.schema.d.ts.map +0 -1
- package/esm/components/avatar.d.ts +0 -77
- package/esm/components/avatar.d.ts.map +0 -1
- package/esm/components/avatar.schema.d.ts +0 -170
- package/esm/components/avatar.schema.d.ts.map +0 -1
- package/esm/components/badge.d.ts +0 -64
- package/esm/components/badge.d.ts.map +0 -1
- package/esm/components/badge.schema.d.ts +0 -91
- package/esm/components/badge.schema.d.ts.map +0 -1
- package/esm/components/button.d.ts +0 -100
- package/esm/components/button.d.ts.map +0 -1
- package/esm/components/button.schema.d.ts +0 -120
- package/esm/components/button.schema.d.ts.map +0 -1
- package/esm/components/card.d.ts +0 -53
- package/esm/components/card.d.ts.map +0 -1
- package/esm/components/card.schema.d.ts +0 -93
- package/esm/components/card.schema.d.ts.map +0 -1
- package/esm/components/form.d.ts +0 -212
- package/esm/components/form.d.ts.map +0 -1
- package/esm/components/form.schema.d.ts +0 -365
- package/esm/components/form.schema.d.ts.map +0 -1
- package/esm/components/index.d.ts +0 -29
- package/esm/components/index.d.ts.map +0 -1
- package/esm/components/list.d.ts +0 -121
- package/esm/components/list.d.ts.map +0 -1
- package/esm/components/list.schema.d.ts +0 -129
- package/esm/components/list.schema.d.ts.map +0 -1
- package/esm/components/modal.d.ts +0 -100
- package/esm/components/modal.d.ts.map +0 -1
- package/esm/components/modal.schema.d.ts +0 -151
- package/esm/components/modal.schema.d.ts.map +0 -1
- package/esm/components/table.d.ts +0 -91
- package/esm/components/table.d.ts.map +0 -1
- package/esm/components/table.schema.d.ts +0 -123
- package/esm/components/table.schema.d.ts.map +0 -1
- package/esm/index.d.ts +0 -40
- package/esm/index.d.ts.map +0 -1
- package/esm/layouts/base.d.ts +0 -86
- package/esm/layouts/base.d.ts.map +0 -1
- package/esm/layouts/index.d.ts +0 -8
- package/esm/layouts/index.d.ts.map +0 -1
- package/esm/layouts/presets.d.ts +0 -134
- package/esm/layouts/presets.d.ts.map +0 -1
- package/esm/pages/consent.d.ts +0 -117
- package/esm/pages/consent.d.ts.map +0 -1
- package/esm/pages/error.d.ts +0 -101
- package/esm/pages/error.d.ts.map +0 -1
- package/esm/pages/index.d.ts +0 -9
- package/esm/pages/index.d.ts.map +0 -1
- package/esm/pages/index.js +0 -1036
- package/esm/react/Alert.d.ts +0 -101
- package/esm/react/Alert.d.ts.map +0 -1
- package/esm/react/Badge.d.ts +0 -100
- package/esm/react/Badge.d.ts.map +0 -1
- package/esm/react/Button.d.ts +0 -108
- package/esm/react/Button.d.ts.map +0 -1
- package/esm/react/Card.d.ts +0 -103
- package/esm/react/Card.d.ts.map +0 -1
- package/esm/react/hooks/context.d.ts +0 -179
- package/esm/react/hooks/context.d.ts.map +0 -1
- package/esm/react/hooks/index.d.ts +0 -42
- package/esm/react/hooks/index.d.ts.map +0 -1
- package/esm/react/hooks/tools.d.ts +0 -284
- package/esm/react/hooks/tools.d.ts.map +0 -1
- package/esm/react/index.d.ts +0 -80
- package/esm/react/index.d.ts.map +0 -1
- package/esm/react/index.js +0 -3124
- package/esm/react/types.d.ts +0 -105
- package/esm/react/types.d.ts.map +0 -1
- package/esm/react/utils.d.ts +0 -43
- package/esm/react/utils.d.ts.map +0 -1
- package/esm/render/index.d.ts +0 -8
- package/esm/render/index.d.ts.map +0 -1
- package/esm/render/prerender.d.ts +0 -57
- package/esm/render/prerender.d.ts.map +0 -1
- package/esm/renderers/index.d.ts +0 -21
- package/esm/renderers/index.d.ts.map +0 -1
- package/esm/renderers/index.js +0 -381
- package/esm/renderers/react.adapter.d.ts +0 -70
- package/esm/renderers/react.adapter.d.ts.map +0 -1
- package/esm/renderers/react.renderer.d.ts +0 -96
- package/esm/renderers/react.renderer.d.ts.map +0 -1
- package/esm/universal/UniversalApp.d.ts +0 -108
- package/esm/universal/UniversalApp.d.ts.map +0 -1
- package/esm/universal/cached-runtime.d.ts +0 -115
- package/esm/universal/cached-runtime.d.ts.map +0 -1
- package/esm/universal/context.d.ts +0 -122
- package/esm/universal/context.d.ts.map +0 -1
- package/esm/universal/index.d.ts +0 -57
- package/esm/universal/index.d.ts.map +0 -1
- package/esm/universal/renderers/html.renderer.d.ts +0 -37
- package/esm/universal/renderers/html.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/index.d.ts +0 -112
- package/esm/universal/renderers/index.d.ts.map +0 -1
- package/esm/universal/renderers/markdown.renderer.d.ts +0 -33
- package/esm/universal/renderers/markdown.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/mdx.renderer.d.ts +0 -38
- package/esm/universal/renderers/mdx.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/react.renderer.d.ts +0 -46
- package/esm/universal/renderers/react.renderer.d.ts.map +0 -1
- package/esm/universal/runtime-builder.d.ts +0 -33
- package/esm/universal/runtime-builder.d.ts.map +0 -1
- package/esm/universal/store.d.ts +0 -135
- package/esm/universal/store.d.ts.map +0 -1
- package/esm/universal/types.d.ts +0 -199
- package/esm/universal/types.d.ts.map +0 -1
- package/esm/web-components/core/attribute-parser.d.ts +0 -82
- package/esm/web-components/core/attribute-parser.d.ts.map +0 -1
- package/esm/web-components/core/base-element.d.ts +0 -197
- package/esm/web-components/core/base-element.d.ts.map +0 -1
- package/esm/web-components/core/index.d.ts +0 -9
- package/esm/web-components/core/index.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-alert.d.ts +0 -46
- package/esm/web-components/elements/fmcp-alert.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-badge.d.ts +0 -47
- package/esm/web-components/elements/fmcp-badge.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-button.d.ts +0 -117
- package/esm/web-components/elements/fmcp-button.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-card.d.ts +0 -53
- package/esm/web-components/elements/fmcp-card.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-input.d.ts +0 -96
- package/esm/web-components/elements/fmcp-input.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-select.d.ts +0 -100
- package/esm/web-components/elements/fmcp-select.d.ts.map +0 -1
- package/esm/web-components/elements/index.d.ts +0 -13
- package/esm/web-components/elements/index.d.ts.map +0 -1
- package/esm/web-components/index.d.ts +0 -50
- package/esm/web-components/index.d.ts.map +0 -1
- package/esm/web-components/register.d.ts +0 -57
- package/esm/web-components/register.d.ts.map +0 -1
- package/esm/web-components/types.d.ts +0 -122
- package/esm/web-components/types.d.ts.map +0 -1
- package/esm/widgets/index.d.ts +0 -8
- package/esm/widgets/index.d.ts.map +0 -1
- package/esm/widgets/index.js +0 -883
- package/esm/widgets/progress.d.ts +0 -133
- package/esm/widgets/progress.d.ts.map +0 -1
- package/esm/widgets/resource.d.ts +0 -163
- package/esm/widgets/resource.d.ts.map +0 -1
- package/pages/consent.d.ts +0 -117
- package/pages/consent.d.ts.map +0 -1
- package/pages/error.d.ts +0 -101
- package/pages/error.d.ts.map +0 -1
- package/pages/index.d.ts +0 -9
- package/pages/index.d.ts.map +0 -1
- package/pages/index.js +0 -1065
- package/react/utils.d.ts +0 -43
- package/react/utils.d.ts.map +0 -1
- package/widgets/index.d.ts +0 -8
- package/widgets/index.d.ts.map +0 -1
- package/widgets/index.js +0 -910
- package/widgets/progress.d.ts +0 -133
- package/widgets/progress.d.ts.map +0 -1
- package/widgets/resource.d.ts +0 -163
- package/widgets/resource.d.ts.map +0 -1
- /package/esm/components/{index.js → index.mjs} +0 -0
- /package/esm/render/{index.js → index.mjs} +0 -0
- /package/esm/web-components/{index.js → index.mjs} +0 -0
|
@@ -719,7 +719,7 @@ function getAlignmentClasses(alignment) {
|
|
|
719
719
|
};
|
|
720
720
|
return alignMap[alignment];
|
|
721
721
|
}
|
|
722
|
-
function getBackgroundClasses(background
|
|
722
|
+
function getBackgroundClasses(background) {
|
|
723
723
|
switch (background) {
|
|
724
724
|
case "gradient":
|
|
725
725
|
return "bg-gradient-to-br from-primary to-secondary";
|
|
@@ -764,7 +764,7 @@ function buildBodyAttrs(attrs) {
|
|
|
764
764
|
function baseLayout(content, options) {
|
|
765
765
|
const {
|
|
766
766
|
title,
|
|
767
|
-
pageType = "custom",
|
|
767
|
+
pageType: _pageType = "custom",
|
|
768
768
|
size = "md",
|
|
769
769
|
alignment = "center",
|
|
770
770
|
background = "solid",
|
|
@@ -801,7 +801,7 @@ function baseLayout(content, options) {
|
|
|
801
801
|
</style>` : "";
|
|
802
802
|
const sizeClass = getSizeClass(size);
|
|
803
803
|
const alignmentClasses = getAlignmentClasses(alignment);
|
|
804
|
-
const backgroundClasses = getBackgroundClasses(background
|
|
804
|
+
const backgroundClasses = getBackgroundClasses(background);
|
|
805
805
|
const allBodyClasses = [backgroundClasses, "font-sans antialiased", bodyClass].filter(Boolean).join(" ");
|
|
806
806
|
const metaTags = buildMetaTags(options);
|
|
807
807
|
const bodyAttrStr = buildBodyAttrs(bodyAttrs);
|
|
@@ -2668,926 +2668,6 @@ var errorLayoutBuilder = createLayoutBuilder({
|
|
|
2668
2668
|
background: "solid"
|
|
2669
2669
|
});
|
|
2670
2670
|
|
|
2671
|
-
// libs/ui/src/pages/consent.ts
|
|
2672
|
-
function consentPage(options) {
|
|
2673
|
-
const {
|
|
2674
|
-
client,
|
|
2675
|
-
user,
|
|
2676
|
-
permissions,
|
|
2677
|
-
approveUrl,
|
|
2678
|
-
denyUrl,
|
|
2679
|
-
csrfToken,
|
|
2680
|
-
state,
|
|
2681
|
-
redirectUri,
|
|
2682
|
-
responseType,
|
|
2683
|
-
nonce,
|
|
2684
|
-
codeChallenge,
|
|
2685
|
-
codeChallengeMethod,
|
|
2686
|
-
error,
|
|
2687
|
-
layout = {},
|
|
2688
|
-
warningMessage,
|
|
2689
|
-
allowScopeSelection = false,
|
|
2690
|
-
approveText = "Allow",
|
|
2691
|
-
denyText = "Deny"
|
|
2692
|
-
} = options;
|
|
2693
|
-
const errorAlert = error ? alert(error, { variant: "danger", dismissible: true }) : "";
|
|
2694
|
-
const unverifiedWarning = !client.verified ? alert(warningMessage || "This application has not been verified. Only authorize applications you trust.", {
|
|
2695
|
-
variant: "warning",
|
|
2696
|
-
title: "Unverified Application"
|
|
2697
|
-
}) : "";
|
|
2698
|
-
const clientHeader = `
|
|
2699
|
-
<div class="text-center mb-6">
|
|
2700
|
-
${client.icon ? `<img src="${escapeHtml2(client.icon)}" alt="${escapeHtml2(
|
|
2701
|
-
client.name
|
|
2702
|
-
)}" class="w-16 h-16 rounded-xl mx-auto mb-4 shadow-md">` : `<div class="inline-flex items-center justify-center w-16 h-16 rounded-xl bg-gradient-to-br from-primary to-secondary text-white font-bold text-2xl mx-auto mb-4 shadow-md">
|
|
2703
|
-
${escapeHtml2(client.name.charAt(0).toUpperCase())}
|
|
2704
|
-
</div>`}
|
|
2705
|
-
<h1 class="text-xl font-bold text-text-primary">
|
|
2706
|
-
${client.verified ? `<span class="inline-flex items-center gap-1">
|
|
2707
|
-
${escapeHtml2(client.name)}
|
|
2708
|
-
<svg class="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
|
2709
|
-
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
|
2710
|
-
</svg>
|
|
2711
|
-
</span>` : escapeHtml2(client.name)}
|
|
2712
|
-
</h1>
|
|
2713
|
-
<p class="text-text-secondary mt-1">wants to access your account</p>
|
|
2714
|
-
</div>
|
|
2715
|
-
`;
|
|
2716
|
-
const userSection = user ? `
|
|
2717
|
-
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg mb-6">
|
|
2718
|
-
${user.avatar ? `<img src="${escapeHtml2(user.avatar)}" class="w-12 h-12 rounded-full">` : `<div class="w-12 h-12 rounded-full bg-primary text-white flex items-center justify-center font-semibold text-lg">
|
|
2719
|
-
${escapeHtml2((user.name || user.email || "U").charAt(0).toUpperCase())}
|
|
2720
|
-
</div>`}
|
|
2721
|
-
<div class="flex-1 min-w-0">
|
|
2722
|
-
${user.name ? `<div class="font-medium text-text-primary truncate">${escapeHtml2(user.name)}</div>` : ""}
|
|
2723
|
-
${user.email ? `<div class="text-sm text-text-secondary truncate">${escapeHtml2(user.email)}</div>` : ""}
|
|
2724
|
-
</div>
|
|
2725
|
-
<a href="/login?prompt=select_account" class="text-sm text-primary hover:text-primary/80">
|
|
2726
|
-
Switch account
|
|
2727
|
-
</a>
|
|
2728
|
-
</div>
|
|
2729
|
-
` : "";
|
|
2730
|
-
const permissionsSection = `
|
|
2731
|
-
<div class="mb-6">
|
|
2732
|
-
<h3 class="font-medium text-text-primary mb-3">This will allow ${escapeHtml2(client.name)} to:</h3>
|
|
2733
|
-
${permissionList(permissions, {
|
|
2734
|
-
checkable: allowScopeSelection,
|
|
2735
|
-
inputName: "scope"
|
|
2736
|
-
})}
|
|
2737
|
-
</div>
|
|
2738
|
-
`;
|
|
2739
|
-
const hiddenFields = [
|
|
2740
|
-
csrfToken ? csrfInput(csrfToken) : "",
|
|
2741
|
-
state ? hiddenInput("state", state) : "",
|
|
2742
|
-
redirectUri ? hiddenInput("redirect_uri", redirectUri) : "",
|
|
2743
|
-
responseType ? hiddenInput("response_type", responseType) : "",
|
|
2744
|
-
nonce ? hiddenInput("nonce", nonce) : "",
|
|
2745
|
-
codeChallenge ? hiddenInput("code_challenge", codeChallenge) : "",
|
|
2746
|
-
codeChallengeMethod ? hiddenInput("code_challenge_method", codeChallengeMethod) : "",
|
|
2747
|
-
hiddenInput("client_id", client.clientId),
|
|
2748
|
-
// Include all scopes if not selectable
|
|
2749
|
-
!allowScopeSelection ? permissions.map((p) => hiddenInput("scope[]", p.scope)).join("\n") : ""
|
|
2750
|
-
].filter(Boolean).join("\n");
|
|
2751
|
-
const actionsHtml = `
|
|
2752
|
-
<div class="flex gap-3 pt-4">
|
|
2753
|
-
<form action="${escapeHtml2(denyUrl || approveUrl)}" method="post" class="flex-1">
|
|
2754
|
-
${hiddenFields}
|
|
2755
|
-
<input type="hidden" name="action" value="deny">
|
|
2756
|
-
${outlineButton(denyText, { type: "submit", fullWidth: true })}
|
|
2757
|
-
</form>
|
|
2758
|
-
<form action="${escapeHtml2(approveUrl)}" method="post" class="flex-1">
|
|
2759
|
-
${hiddenFields}
|
|
2760
|
-
<input type="hidden" name="action" value="approve">
|
|
2761
|
-
${primaryButton(approveText, { type: "submit", fullWidth: true })}
|
|
2762
|
-
</form>
|
|
2763
|
-
</div>
|
|
2764
|
-
`;
|
|
2765
|
-
const linksHtml = client.privacyUrl || client.termsUrl || client.websiteUrl ? `
|
|
2766
|
-
<div class="text-center text-xs text-text-secondary mt-6 space-x-3">
|
|
2767
|
-
${client.websiteUrl ? `<a href="${escapeHtml2(
|
|
2768
|
-
client.websiteUrl
|
|
2769
|
-
)}" target="_blank" rel="noopener" class="hover:text-primary">Website</a>` : ""}
|
|
2770
|
-
${client.privacyUrl ? `<a href="${escapeHtml2(
|
|
2771
|
-
client.privacyUrl
|
|
2772
|
-
)}" target="_blank" rel="noopener" class="hover:text-primary">Privacy Policy</a>` : ""}
|
|
2773
|
-
${client.termsUrl ? `<a href="${escapeHtml2(
|
|
2774
|
-
client.termsUrl
|
|
2775
|
-
)}" target="_blank" rel="noopener" class="hover:text-primary">Terms of Service</a>` : ""}
|
|
2776
|
-
</div>
|
|
2777
|
-
` : "";
|
|
2778
|
-
const content = `
|
|
2779
|
-
${errorAlert}
|
|
2780
|
-
${unverifiedWarning}
|
|
2781
|
-
${clientHeader}
|
|
2782
|
-
${userSection}
|
|
2783
|
-
${permissionsSection}
|
|
2784
|
-
${actionsHtml}
|
|
2785
|
-
${linksHtml}
|
|
2786
|
-
`;
|
|
2787
|
-
return consentLayout(content, {
|
|
2788
|
-
title: `Authorize ${client.name}`,
|
|
2789
|
-
clientName: client.name,
|
|
2790
|
-
clientIcon: client.icon,
|
|
2791
|
-
userInfo: user,
|
|
2792
|
-
...layout
|
|
2793
|
-
});
|
|
2794
|
-
}
|
|
2795
|
-
function consentSuccessPage(options) {
|
|
2796
|
-
const { client, redirectUrl, autoRedirectDelay = 3e3, layout = {} } = options;
|
|
2797
|
-
const redirectScript = redirectUrl && autoRedirectDelay > 0 ? `
|
|
2798
|
-
<script>
|
|
2799
|
-
setTimeout(() => {
|
|
2800
|
-
window.location.href = '${escapeHtml2(redirectUrl)}';
|
|
2801
|
-
}, ${autoRedirectDelay});
|
|
2802
|
-
</script>
|
|
2803
|
-
` : "";
|
|
2804
|
-
const content = `
|
|
2805
|
-
<div class="text-center">
|
|
2806
|
-
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-success/10 mb-6">
|
|
2807
|
-
<svg class="w-8 h-8 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2808
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
2809
|
-
</svg>
|
|
2810
|
-
</div>
|
|
2811
|
-
<h1 class="text-2xl font-bold text-text-primary mb-2">Authorization Successful</h1>
|
|
2812
|
-
<p class="text-text-secondary mb-4">
|
|
2813
|
-
You have authorized <strong>${escapeHtml2(client.name)}</strong> to access your account.
|
|
2814
|
-
</p>
|
|
2815
|
-
${redirectUrl ? `<p class="text-sm text-text-secondary">Redirecting you back to ${escapeHtml2(client.name)}...</p>` : ""}
|
|
2816
|
-
</div>
|
|
2817
|
-
${redirectScript}
|
|
2818
|
-
`;
|
|
2819
|
-
return consentLayout(content, {
|
|
2820
|
-
title: "Authorization Successful",
|
|
2821
|
-
clientName: client.name,
|
|
2822
|
-
clientIcon: client.icon,
|
|
2823
|
-
...layout
|
|
2824
|
-
});
|
|
2825
|
-
}
|
|
2826
|
-
function consentDeniedPage(options) {
|
|
2827
|
-
const { client, redirectUrl, layout = {} } = options;
|
|
2828
|
-
const content = `
|
|
2829
|
-
<div class="text-center">
|
|
2830
|
-
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-gray-100 mb-6">
|
|
2831
|
-
<svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2832
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
2833
|
-
</svg>
|
|
2834
|
-
</div>
|
|
2835
|
-
<h1 class="text-2xl font-bold text-text-primary mb-2">Authorization Denied</h1>
|
|
2836
|
-
<p class="text-text-secondary mb-6">
|
|
2837
|
-
You denied <strong>${escapeHtml2(client.name)}</strong> access to your account.
|
|
2838
|
-
</p>
|
|
2839
|
-
${redirectUrl ? `
|
|
2840
|
-
<a href="${escapeHtml2(
|
|
2841
|
-
redirectUrl
|
|
2842
|
-
)}" class="inline-block px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
|
|
2843
|
-
Return to ${escapeHtml2(client.name)}
|
|
2844
|
-
</a>
|
|
2845
|
-
` : ""}
|
|
2846
|
-
</div>
|
|
2847
|
-
`;
|
|
2848
|
-
return consentLayout(content, {
|
|
2849
|
-
title: "Authorization Denied",
|
|
2850
|
-
clientName: client.name,
|
|
2851
|
-
clientIcon: client.icon,
|
|
2852
|
-
...layout
|
|
2853
|
-
});
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
// libs/ui/src/pages/error.ts
|
|
2857
|
-
function errorPage(options) {
|
|
2858
|
-
const {
|
|
2859
|
-
code,
|
|
2860
|
-
title = "Something went wrong",
|
|
2861
|
-
message,
|
|
2862
|
-
details,
|
|
2863
|
-
showStack = false,
|
|
2864
|
-
stack,
|
|
2865
|
-
showRetry = true,
|
|
2866
|
-
retryUrl,
|
|
2867
|
-
showHome = true,
|
|
2868
|
-
homeUrl = "/",
|
|
2869
|
-
showBack = false,
|
|
2870
|
-
actions,
|
|
2871
|
-
layout = {},
|
|
2872
|
-
requestId
|
|
2873
|
-
} = options;
|
|
2874
|
-
const detailsHtml = details || showStack && stack ? `
|
|
2875
|
-
<div class="mt-8 text-left">
|
|
2876
|
-
${details ? `
|
|
2877
|
-
<div class="p-4 bg-gray-50 rounded-lg text-sm text-text-secondary mb-4">
|
|
2878
|
-
<strong class="text-text-primary">Details:</strong>
|
|
2879
|
-
<p class="mt-1">${escapeHtml2(details)}</p>
|
|
2880
|
-
</div>
|
|
2881
|
-
` : ""}
|
|
2882
|
-
${showStack && stack ? `
|
|
2883
|
-
<details class="p-4 bg-gray-900 rounded-lg text-sm">
|
|
2884
|
-
<summary class="text-gray-300 cursor-pointer hover:text-white">Stack Trace</summary>
|
|
2885
|
-
<pre class="mt-2 text-xs text-gray-400 overflow-x-auto whitespace-pre-wrap">${escapeHtml2(stack)}</pre>
|
|
2886
|
-
</details>
|
|
2887
|
-
` : ""}
|
|
2888
|
-
</div>
|
|
2889
|
-
` : "";
|
|
2890
|
-
const requestIdHtml = requestId ? `
|
|
2891
|
-
<p class="text-xs text-text-secondary mt-6">
|
|
2892
|
-
Request ID: <code class="px-1.5 py-0.5 bg-gray-100 rounded text-xs">${escapeHtml2(requestId)}</code>
|
|
2893
|
-
</p>
|
|
2894
|
-
` : "";
|
|
2895
|
-
const content = `
|
|
2896
|
-
${detailsHtml}
|
|
2897
|
-
${actions || ""}
|
|
2898
|
-
${requestIdHtml}
|
|
2899
|
-
`;
|
|
2900
|
-
return errorLayout(content, {
|
|
2901
|
-
title: `${code ? `Error ${code} - ` : ""}${title}`,
|
|
2902
|
-
errorCode: code?.toString(),
|
|
2903
|
-
errorTitle: title,
|
|
2904
|
-
errorMessage: message,
|
|
2905
|
-
showRetry,
|
|
2906
|
-
retryUrl,
|
|
2907
|
-
showHome,
|
|
2908
|
-
homeUrl,
|
|
2909
|
-
...layout
|
|
2910
|
-
});
|
|
2911
|
-
}
|
|
2912
|
-
function notFoundPage(options = {}) {
|
|
2913
|
-
return errorPage({
|
|
2914
|
-
code: 404,
|
|
2915
|
-
title: "Page Not Found",
|
|
2916
|
-
message: "The page you're looking for doesn't exist or has been moved.",
|
|
2917
|
-
showRetry: false,
|
|
2918
|
-
...options
|
|
2919
|
-
});
|
|
2920
|
-
}
|
|
2921
|
-
function forbiddenPage(options = {}) {
|
|
2922
|
-
return errorPage({
|
|
2923
|
-
code: 403,
|
|
2924
|
-
title: "Access Denied",
|
|
2925
|
-
message: "You don't have permission to access this resource.",
|
|
2926
|
-
showRetry: false,
|
|
2927
|
-
...options
|
|
2928
|
-
});
|
|
2929
|
-
}
|
|
2930
|
-
function unauthorizedPage(options = {}) {
|
|
2931
|
-
const { loginUrl = "/login", ...rest } = options;
|
|
2932
|
-
return errorPage({
|
|
2933
|
-
code: 401,
|
|
2934
|
-
title: "Authentication Required",
|
|
2935
|
-
message: "Please sign in to access this resource.",
|
|
2936
|
-
showRetry: false,
|
|
2937
|
-
showHome: false,
|
|
2938
|
-
actions: `
|
|
2939
|
-
<div class="flex justify-center mt-8">
|
|
2940
|
-
<a href="${escapeHtml2(
|
|
2941
|
-
loginUrl
|
|
2942
|
-
)}" class="px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
|
|
2943
|
-
Sign In
|
|
2944
|
-
</a>
|
|
2945
|
-
</div>
|
|
2946
|
-
`,
|
|
2947
|
-
...rest
|
|
2948
|
-
});
|
|
2949
|
-
}
|
|
2950
|
-
function serverErrorPage(options = {}) {
|
|
2951
|
-
return errorPage({
|
|
2952
|
-
code: 500,
|
|
2953
|
-
title: "Server Error",
|
|
2954
|
-
message: "We're having trouble processing your request. Please try again later.",
|
|
2955
|
-
showRetry: true,
|
|
2956
|
-
...options
|
|
2957
|
-
});
|
|
2958
|
-
}
|
|
2959
|
-
function maintenancePage(options = {}) {
|
|
2960
|
-
const { estimatedTime, ...rest } = options;
|
|
2961
|
-
const timeMessage = estimatedTime ? `We expect to be back by ${escapeHtml2(estimatedTime)}.` : "We'll be back shortly.";
|
|
2962
|
-
return errorPage({
|
|
2963
|
-
code: 503,
|
|
2964
|
-
title: "Under Maintenance",
|
|
2965
|
-
message: `We're currently performing scheduled maintenance. ${timeMessage}`,
|
|
2966
|
-
showRetry: true,
|
|
2967
|
-
showHome: false,
|
|
2968
|
-
...rest
|
|
2969
|
-
});
|
|
2970
|
-
}
|
|
2971
|
-
function rateLimitPage(options = {}) {
|
|
2972
|
-
const { retryAfter, ...rest } = options;
|
|
2973
|
-
const retryMessage = retryAfter ? `Please wait ${retryAfter} seconds before trying again.` : "Please wait a moment before trying again.";
|
|
2974
|
-
return errorPage({
|
|
2975
|
-
code: 429,
|
|
2976
|
-
title: "Too Many Requests",
|
|
2977
|
-
message: `You've made too many requests. ${retryMessage}`,
|
|
2978
|
-
showRetry: true,
|
|
2979
|
-
showHome: true,
|
|
2980
|
-
...rest
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
2983
|
-
function offlinePage(options = {}) {
|
|
2984
|
-
return errorPage({
|
|
2985
|
-
title: "You're Offline",
|
|
2986
|
-
message: "Please check your internet connection and try again.",
|
|
2987
|
-
showRetry: true,
|
|
2988
|
-
showHome: false,
|
|
2989
|
-
...options,
|
|
2990
|
-
layout: {
|
|
2991
|
-
...options.layout
|
|
2992
|
-
}
|
|
2993
|
-
});
|
|
2994
|
-
}
|
|
2995
|
-
function sessionExpiredPage(options = {}) {
|
|
2996
|
-
const { loginUrl = "/login", ...rest } = options;
|
|
2997
|
-
return errorPage({
|
|
2998
|
-
title: "Session Expired",
|
|
2999
|
-
message: "Your session has expired. Please sign in again to continue.",
|
|
3000
|
-
showRetry: false,
|
|
3001
|
-
showHome: false,
|
|
3002
|
-
actions: `
|
|
3003
|
-
<div class="flex justify-center mt-8">
|
|
3004
|
-
<a href="${escapeHtml2(
|
|
3005
|
-
loginUrl
|
|
3006
|
-
)}" class="px-6 py-3 bg-primary hover:bg-primary/90 text-white font-medium rounded-lg transition-colors">
|
|
3007
|
-
Sign In Again
|
|
3008
|
-
</a>
|
|
3009
|
-
</div>
|
|
3010
|
-
`,
|
|
3011
|
-
...rest
|
|
3012
|
-
});
|
|
3013
|
-
}
|
|
3014
|
-
function oauthErrorPage(options) {
|
|
3015
|
-
const { errorCode, errorDescription, redirectUri, clientName, ...rest } = options;
|
|
3016
|
-
const errorMessages = {
|
|
3017
|
-
invalid_request: {
|
|
3018
|
-
title: "Invalid Request",
|
|
3019
|
-
message: "The authorization request is missing required parameters or is malformed."
|
|
3020
|
-
},
|
|
3021
|
-
unauthorized_client: {
|
|
3022
|
-
title: "Unauthorized Client",
|
|
3023
|
-
message: "The client is not authorized to request an authorization code."
|
|
3024
|
-
},
|
|
3025
|
-
access_denied: {
|
|
3026
|
-
title: "Access Denied",
|
|
3027
|
-
message: "The resource owner denied the authorization request."
|
|
3028
|
-
},
|
|
3029
|
-
unsupported_response_type: {
|
|
3030
|
-
title: "Unsupported Response Type",
|
|
3031
|
-
message: "The authorization server does not support the requested response type."
|
|
3032
|
-
},
|
|
3033
|
-
invalid_scope: {
|
|
3034
|
-
title: "Invalid Scope",
|
|
3035
|
-
message: "The requested scope is invalid, unknown, or malformed."
|
|
3036
|
-
},
|
|
3037
|
-
server_error: {
|
|
3038
|
-
title: "Server Error",
|
|
3039
|
-
message: "The authorization server encountered an unexpected error."
|
|
3040
|
-
},
|
|
3041
|
-
temporarily_unavailable: {
|
|
3042
|
-
title: "Temporarily Unavailable",
|
|
3043
|
-
message: "The authorization server is temporarily unavailable. Please try again later."
|
|
3044
|
-
}
|
|
3045
|
-
};
|
|
3046
|
-
const errorInfo = errorCode && errorMessages[errorCode] ? errorMessages[errorCode] : { title: "Authorization Error", message: errorDescription || "An error occurred during authorization." };
|
|
3047
|
-
const clientMessage = clientName ? ` while connecting to ${escapeHtml2(clientName)}` : "";
|
|
3048
|
-
const redirectAction = redirectUri ? `
|
|
3049
|
-
<a href="${escapeHtml2(
|
|
3050
|
-
redirectUri
|
|
3051
|
-
)}" class="px-6 py-3 bg-gray-100 hover:bg-gray-200 text-text-primary font-medium rounded-lg transition-colors">
|
|
3052
|
-
Return to ${clientName ? escapeHtml2(clientName) : "Application"}
|
|
3053
|
-
</a>
|
|
3054
|
-
` : "";
|
|
3055
|
-
return errorPage({
|
|
3056
|
-
title: errorInfo.title,
|
|
3057
|
-
message: `${errorInfo.message}${clientMessage}`,
|
|
3058
|
-
details: errorCode && errorDescription ? `Error: ${errorCode}
|
|
3059
|
-
${errorDescription}` : void 0,
|
|
3060
|
-
showRetry: errorCode === "server_error" || errorCode === "temporarily_unavailable",
|
|
3061
|
-
showHome: true,
|
|
3062
|
-
actions: redirectAction ? `<div class="flex justify-center gap-4 mt-8">${redirectAction}</div>` : void 0,
|
|
3063
|
-
...rest
|
|
3064
|
-
});
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
|
-
// libs/ui/src/widgets/resource.ts
|
|
3068
|
-
var resourceIcons = {
|
|
3069
|
-
document: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3070
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
|
3071
|
-
</svg>`,
|
|
3072
|
-
image: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3073
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
3074
|
-
</svg>`,
|
|
3075
|
-
code: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3076
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
|
|
3077
|
-
</svg>`,
|
|
3078
|
-
data: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3079
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
|
|
3080
|
-
</svg>`,
|
|
3081
|
-
file: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3082
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
|
3083
|
-
</svg>`,
|
|
3084
|
-
link: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3085
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
|
3086
|
-
</svg>`,
|
|
3087
|
-
user: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3088
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
|
3089
|
-
</svg>`,
|
|
3090
|
-
event: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3091
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
|
3092
|
-
</svg>`,
|
|
3093
|
-
message: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3094
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
|
3095
|
-
</svg>`,
|
|
3096
|
-
task: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3097
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
|
|
3098
|
-
</svg>`,
|
|
3099
|
-
custom: `<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3100
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
3101
|
-
</svg>`
|
|
3102
|
-
};
|
|
3103
|
-
function formatFileSize(bytes) {
|
|
3104
|
-
if (bytes === 0) return "0 B";
|
|
3105
|
-
const k = 1024;
|
|
3106
|
-
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3107
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
3108
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
3109
|
-
}
|
|
3110
|
-
function formatDate(date) {
|
|
3111
|
-
const d = typeof date === "string" ? new Date(date) : date;
|
|
3112
|
-
return d.toLocaleDateString("en-US", {
|
|
3113
|
-
year: "numeric",
|
|
3114
|
-
month: "short",
|
|
3115
|
-
day: "numeric"
|
|
3116
|
-
});
|
|
3117
|
-
}
|
|
3118
|
-
function resourceWidget(options) {
|
|
3119
|
-
const {
|
|
3120
|
-
type,
|
|
3121
|
-
title,
|
|
3122
|
-
description,
|
|
3123
|
-
icon,
|
|
3124
|
-
thumbnail,
|
|
3125
|
-
url,
|
|
3126
|
-
meta,
|
|
3127
|
-
status,
|
|
3128
|
-
actions = [],
|
|
3129
|
-
className = "",
|
|
3130
|
-
cardOptions = {}
|
|
3131
|
-
} = options;
|
|
3132
|
-
const iconHtml = thumbnail ? `<div class="w-16 h-16 rounded-lg overflow-hidden bg-gray-100 flex-shrink-0">
|
|
3133
|
-
<img src="${escapeHtml2(thumbnail)}" alt="${escapeHtml2(title)}" class="w-full h-full object-cover">
|
|
3134
|
-
</div>` : `<div class="w-16 h-16 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0 text-gray-400">
|
|
3135
|
-
${icon || resourceIcons[type]}
|
|
3136
|
-
</div>`;
|
|
3137
|
-
const statusHtml = status ? badge(status.label, { variant: status.variant, size: "sm" }) : "";
|
|
3138
|
-
const metaItems = [];
|
|
3139
|
-
if (meta?.size) {
|
|
3140
|
-
metaItems.push(formatFileSize(meta.size));
|
|
3141
|
-
}
|
|
3142
|
-
if (meta?.mimeType) {
|
|
3143
|
-
metaItems.push(meta.mimeType);
|
|
3144
|
-
}
|
|
3145
|
-
if (meta?.updatedAt) {
|
|
3146
|
-
metaItems.push(`Updated ${formatDate(meta.updatedAt)}`);
|
|
3147
|
-
} else if (meta?.createdAt) {
|
|
3148
|
-
metaItems.push(`Created ${formatDate(meta.createdAt)}`);
|
|
3149
|
-
}
|
|
3150
|
-
if (meta?.author) {
|
|
3151
|
-
metaItems.push(`by ${meta.author}`);
|
|
3152
|
-
}
|
|
3153
|
-
const metaHtml = metaItems.length > 0 ? `<div class="text-xs text-text-secondary mt-1">${metaItems.join(" \u2022 ")}</div>` : "";
|
|
3154
|
-
const tagsHtml = meta?.tags && meta.tags.length > 0 ? `<div class="flex flex-wrap gap-1 mt-2">
|
|
3155
|
-
${meta.tags.map((tag) => badge(tag, { variant: "default", size: "sm" })).join("")}
|
|
3156
|
-
</div>` : "";
|
|
3157
|
-
const actionsHtml = actions.length > 0 ? `<div class="flex gap-2 mt-4">
|
|
3158
|
-
${actions.map((action) => {
|
|
3159
|
-
const variantMap = {
|
|
3160
|
-
primary: "primary",
|
|
3161
|
-
secondary: "secondary",
|
|
3162
|
-
danger: "danger",
|
|
3163
|
-
ghost: "ghost"
|
|
3164
|
-
};
|
|
3165
|
-
const variant = action.variant ? variantMap[action.variant] : "ghost";
|
|
3166
|
-
const htmxAttrs = [];
|
|
3167
|
-
if (action.htmx) {
|
|
3168
|
-
if (action.htmx.get) htmxAttrs.push(`hx-get="${escapeHtml2(action.htmx.get)}"`);
|
|
3169
|
-
if (action.htmx.post) htmxAttrs.push(`hx-post="${escapeHtml2(action.htmx.post)}"`);
|
|
3170
|
-
if (action.htmx.delete) htmxAttrs.push(`hx-delete="${escapeHtml2(action.htmx.delete)}"`);
|
|
3171
|
-
if (action.htmx.target) htmxAttrs.push(`hx-target="${escapeHtml2(action.htmx.target)}"`);
|
|
3172
|
-
if (action.htmx.swap) htmxAttrs.push(`hx-swap="${escapeHtml2(action.htmx.swap)}"`);
|
|
3173
|
-
if (action.htmx.confirm) htmxAttrs.push(`hx-confirm="${escapeHtml2(action.htmx.confirm)}"`);
|
|
3174
|
-
}
|
|
3175
|
-
return button(action.label, {
|
|
3176
|
-
variant,
|
|
3177
|
-
size: "sm",
|
|
3178
|
-
href: action.href,
|
|
3179
|
-
disabled: action.disabled,
|
|
3180
|
-
iconBefore: action.icon
|
|
3181
|
-
});
|
|
3182
|
-
}).join("")}
|
|
3183
|
-
</div>` : "";
|
|
3184
|
-
const content = `
|
|
3185
|
-
<div class="flex gap-4">
|
|
3186
|
-
${iconHtml}
|
|
3187
|
-
<div class="flex-1 min-w-0">
|
|
3188
|
-
<div class="flex items-start justify-between gap-2">
|
|
3189
|
-
<div class="min-w-0">
|
|
3190
|
-
${url ? `<a href="${escapeHtml2(
|
|
3191
|
-
url
|
|
3192
|
-
)}" class="font-semibold text-text-primary hover:text-primary truncate block">${escapeHtml2(
|
|
3193
|
-
title
|
|
3194
|
-
)}</a>` : `<h3 class="font-semibold text-text-primary truncate">${escapeHtml2(title)}</h3>`}
|
|
3195
|
-
${description ? `<p class="text-sm text-text-secondary mt-0.5 line-clamp-2">${escapeHtml2(description)}</p>` : ""}
|
|
3196
|
-
${metaHtml}
|
|
3197
|
-
${tagsHtml}
|
|
3198
|
-
</div>
|
|
3199
|
-
${statusHtml}
|
|
3200
|
-
</div>
|
|
3201
|
-
${actionsHtml}
|
|
3202
|
-
</div>
|
|
3203
|
-
</div>
|
|
3204
|
-
`;
|
|
3205
|
-
return card(content, {
|
|
3206
|
-
variant: "default",
|
|
3207
|
-
size: "md",
|
|
3208
|
-
className: `resource-widget resource-${type} ${className}`,
|
|
3209
|
-
...cardOptions
|
|
3210
|
-
});
|
|
3211
|
-
}
|
|
3212
|
-
function resourceList(options) {
|
|
3213
|
-
const {
|
|
3214
|
-
resources,
|
|
3215
|
-
title,
|
|
3216
|
-
emptyMessage = "No resources found",
|
|
3217
|
-
layout = "list",
|
|
3218
|
-
columns = 2,
|
|
3219
|
-
className = "",
|
|
3220
|
-
showLoadMore = false,
|
|
3221
|
-
loadMoreUrl
|
|
3222
|
-
} = options;
|
|
3223
|
-
const titleHtml = title ? `<h2 class="text-lg font-semibold text-text-primary mb-4">${escapeHtml2(title)}</h2>` : "";
|
|
3224
|
-
if (resources.length === 0) {
|
|
3225
|
-
return `<div class="${className}">
|
|
3226
|
-
${titleHtml}
|
|
3227
|
-
<div class="text-center py-12 text-text-secondary">
|
|
3228
|
-
<svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3229
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"/>
|
|
3230
|
-
</svg>
|
|
3231
|
-
<p>${escapeHtml2(emptyMessage)}</p>
|
|
3232
|
-
</div>
|
|
3233
|
-
</div>`;
|
|
3234
|
-
}
|
|
3235
|
-
const layoutClasses = layout === "grid" ? `grid grid-cols-1 md:grid-cols-${columns} gap-4` : "space-y-4";
|
|
3236
|
-
const resourcesHtml = resources.map((r) => resourceWidget(r)).join("\n");
|
|
3237
|
-
const loadMoreHtml = showLoadMore && loadMoreUrl ? `<div class="text-center mt-6">
|
|
3238
|
-
${button("Load More", {
|
|
3239
|
-
variant: "outline",
|
|
3240
|
-
href: loadMoreUrl
|
|
3241
|
-
})}
|
|
3242
|
-
</div>` : "";
|
|
3243
|
-
return `<div class="resource-list ${className}">
|
|
3244
|
-
${titleHtml}
|
|
3245
|
-
<div class="${layoutClasses}">
|
|
3246
|
-
${resourcesHtml}
|
|
3247
|
-
</div>
|
|
3248
|
-
${loadMoreHtml}
|
|
3249
|
-
</div>`;
|
|
3250
|
-
}
|
|
3251
|
-
function resourceItem(options) {
|
|
3252
|
-
const { type, title, description, icon, url, meta, status } = options;
|
|
3253
|
-
const iconHtml = `<div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center flex-shrink-0 text-gray-400">
|
|
3254
|
-
${icon || resourceIcons[type]}
|
|
3255
|
-
</div>`;
|
|
3256
|
-
const statusHtml = status ? badge(status.label, { variant: status.variant, size: "sm" }) : "";
|
|
3257
|
-
const metaText = meta?.size ? formatFileSize(meta.size) : "";
|
|
3258
|
-
const titleElement = url ? `<a href="${escapeHtml2(url)}" class="font-medium text-text-primary hover:text-primary">${escapeHtml2(title)}</a>` : `<span class="font-medium text-text-primary">${escapeHtml2(title)}</span>`;
|
|
3259
|
-
return `<div class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors">
|
|
3260
|
-
${iconHtml}
|
|
3261
|
-
<div class="flex-1 min-w-0">
|
|
3262
|
-
<div class="flex items-center gap-2">
|
|
3263
|
-
${titleElement}
|
|
3264
|
-
${statusHtml}
|
|
3265
|
-
</div>
|
|
3266
|
-
${description || metaText ? `<p class="text-sm text-text-secondary truncate">${escapeHtml2(description || metaText)}</p>` : ""}
|
|
3267
|
-
</div>
|
|
3268
|
-
</div>`;
|
|
3269
|
-
}
|
|
3270
|
-
function codePreview(options) {
|
|
3271
|
-
const {
|
|
3272
|
-
code,
|
|
3273
|
-
language = "text",
|
|
3274
|
-
filename,
|
|
3275
|
-
lineNumbers = true,
|
|
3276
|
-
maxHeight = "400px",
|
|
3277
|
-
showCopy = true,
|
|
3278
|
-
className = ""
|
|
3279
|
-
} = options;
|
|
3280
|
-
const lines = code.split("\n");
|
|
3281
|
-
const lineNumbersHtml = lineNumbers ? `<div class="text-right select-none pr-4 text-gray-500">
|
|
3282
|
-
${lines.map((_, i) => `<div>${i + 1}</div>`).join("")}
|
|
3283
|
-
</div>` : "";
|
|
3284
|
-
const copyScript = showCopy ? `<script>
|
|
3285
|
-
function copyCode(btn, code) {
|
|
3286
|
-
navigator.clipboard.writeText(code).then(() => {
|
|
3287
|
-
const original = btn.innerHTML;
|
|
3288
|
-
btn.innerHTML = '<svg class="w-4 h-4 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>';
|
|
3289
|
-
setTimeout(() => btn.innerHTML = original, 2000);
|
|
3290
|
-
});
|
|
3291
|
-
}
|
|
3292
|
-
</script>` : "";
|
|
3293
|
-
const copyButton = showCopy ? `<button
|
|
3294
|
-
type="button"
|
|
3295
|
-
onclick="copyCode(this, ${escapeHtml2(JSON.stringify(code))})"
|
|
3296
|
-
class="p-1.5 rounded hover:bg-gray-700 transition-colors"
|
|
3297
|
-
title="Copy code"
|
|
3298
|
-
>
|
|
3299
|
-
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3300
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
3301
|
-
</svg>
|
|
3302
|
-
</button>` : "";
|
|
3303
|
-
return `<div class="code-preview rounded-lg overflow-hidden ${className}">
|
|
3304
|
-
${filename || showCopy ? `
|
|
3305
|
-
<div class="flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700">
|
|
3306
|
-
${filename ? `<span class="text-sm text-gray-300">${escapeHtml2(filename)}</span>` : "<span></span>"}
|
|
3307
|
-
<div class="flex items-center gap-2">
|
|
3308
|
-
${language ? `<span class="text-xs text-gray-500">${escapeHtml2(language)}</span>` : ""}
|
|
3309
|
-
${copyButton}
|
|
3310
|
-
</div>
|
|
3311
|
-
</div>
|
|
3312
|
-
` : ""}
|
|
3313
|
-
<div class="bg-gray-900 p-4 overflow-auto" style="max-height: ${maxHeight}">
|
|
3314
|
-
<div class="flex text-sm font-mono">
|
|
3315
|
-
${lineNumbersHtml}
|
|
3316
|
-
<pre class="flex-1 text-gray-100"><code>${escapeHtml2(code)}</code></pre>
|
|
3317
|
-
</div>
|
|
3318
|
-
</div>
|
|
3319
|
-
${copyScript}
|
|
3320
|
-
</div>`;
|
|
3321
|
-
}
|
|
3322
|
-
function imagePreview(options) {
|
|
3323
|
-
const { src, alt, caption, maxHeight = "400px", clickable = true, className = "" } = options;
|
|
3324
|
-
const imageHtml = `<img
|
|
3325
|
-
src="${escapeHtml2(src)}"
|
|
3326
|
-
alt="${escapeHtml2(alt)}"
|
|
3327
|
-
class="max-w-full h-auto rounded-lg"
|
|
3328
|
-
style="max-height: ${maxHeight}"
|
|
3329
|
-
>`;
|
|
3330
|
-
const captionHtml = caption ? `<p class="text-sm text-text-secondary mt-2 text-center">${escapeHtml2(caption)}</p>` : "";
|
|
3331
|
-
const content = clickable ? `<a href="${escapeHtml2(src)}" target="_blank" rel="noopener" class="block">${imageHtml}</a>` : imageHtml;
|
|
3332
|
-
return `<div class="image-preview ${className}">
|
|
3333
|
-
${content}
|
|
3334
|
-
${captionHtml}
|
|
3335
|
-
</div>`;
|
|
3336
|
-
}
|
|
3337
|
-
|
|
3338
|
-
// libs/ui/src/widgets/progress.ts
|
|
3339
|
-
function progressBar(options) {
|
|
3340
|
-
const {
|
|
3341
|
-
value,
|
|
3342
|
-
showLabel = true,
|
|
3343
|
-
labelPosition = "outside",
|
|
3344
|
-
size = "md",
|
|
3345
|
-
variant = "primary",
|
|
3346
|
-
animated = false,
|
|
3347
|
-
className = "",
|
|
3348
|
-
label
|
|
3349
|
-
} = options;
|
|
3350
|
-
const clampedValue = Math.min(100, Math.max(0, value));
|
|
3351
|
-
const sizeClasses = {
|
|
3352
|
-
sm: "h-1.5",
|
|
3353
|
-
md: "h-2.5",
|
|
3354
|
-
lg: "h-4"
|
|
3355
|
-
};
|
|
3356
|
-
const variantClasses = {
|
|
3357
|
-
primary: "bg-primary",
|
|
3358
|
-
success: "bg-success",
|
|
3359
|
-
warning: "bg-warning",
|
|
3360
|
-
danger: "bg-danger",
|
|
3361
|
-
info: "bg-blue-500"
|
|
3362
|
-
};
|
|
3363
|
-
const animatedClass = animated ? "bg-stripes animate-stripes" : "";
|
|
3364
|
-
const displayLabel = label || `${Math.round(clampedValue)}%`;
|
|
3365
|
-
const insideLabel = labelPosition === "inside" && size === "lg" && clampedValue > 10 ? `<span class="absolute inset-0 flex items-center justify-center text-xs font-medium text-white">${escapeHtml2(
|
|
3366
|
-
displayLabel
|
|
3367
|
-
)}</span>` : "";
|
|
3368
|
-
const outsideLabel = showLabel && labelPosition === "outside" ? `<div class="flex justify-between mb-1">
|
|
3369
|
-
<span class="text-sm font-medium text-text-primary">${label ? escapeHtml2(label) : "Progress"}</span>
|
|
3370
|
-
<span class="text-sm text-text-secondary">${Math.round(clampedValue)}%</span>
|
|
3371
|
-
</div>` : "";
|
|
3372
|
-
return `<div class="progress-bar ${className}">
|
|
3373
|
-
${outsideLabel}
|
|
3374
|
-
<div class="relative w-full ${sizeClasses[size]} bg-gray-200 rounded-full overflow-hidden">
|
|
3375
|
-
<div
|
|
3376
|
-
class="${variantClasses[variant]} ${sizeClasses[size]} ${animatedClass} rounded-full transition-all duration-300"
|
|
3377
|
-
style="width: ${clampedValue}%"
|
|
3378
|
-
role="progressbar"
|
|
3379
|
-
aria-valuenow="${clampedValue}"
|
|
3380
|
-
aria-valuemin="0"
|
|
3381
|
-
aria-valuemax="100"
|
|
3382
|
-
></div>
|
|
3383
|
-
${insideLabel}
|
|
3384
|
-
</div>
|
|
3385
|
-
</div>
|
|
3386
|
-
${animated ? `<style>
|
|
3387
|
-
.bg-stripes {
|
|
3388
|
-
background-image: linear-gradient(
|
|
3389
|
-
45deg,
|
|
3390
|
-
rgba(255,255,255,0.15) 25%,
|
|
3391
|
-
transparent 25%,
|
|
3392
|
-
transparent 50%,
|
|
3393
|
-
rgba(255,255,255,0.15) 50%,
|
|
3394
|
-
rgba(255,255,255,0.15) 75%,
|
|
3395
|
-
transparent 75%,
|
|
3396
|
-
transparent
|
|
3397
|
-
);
|
|
3398
|
-
background-size: 1rem 1rem;
|
|
3399
|
-
}
|
|
3400
|
-
@keyframes stripes {
|
|
3401
|
-
from { background-position: 1rem 0; }
|
|
3402
|
-
to { background-position: 0 0; }
|
|
3403
|
-
}
|
|
3404
|
-
.animate-stripes {
|
|
3405
|
-
animation: stripes 1s linear infinite;
|
|
3406
|
-
}
|
|
3407
|
-
</style>` : ""}`;
|
|
3408
|
-
}
|
|
3409
|
-
function stepProgress(options) {
|
|
3410
|
-
const { steps, orientation = "horizontal", connector = "line", className = "" } = options;
|
|
3411
|
-
const getStepIcon = (step, index) => {
|
|
3412
|
-
if (step.icon) return step.icon;
|
|
3413
|
-
if (step.status === "completed") {
|
|
3414
|
-
return `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3415
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
|
3416
|
-
</svg>`;
|
|
3417
|
-
}
|
|
3418
|
-
return `<span class="font-medium">${index + 1}</span>`;
|
|
3419
|
-
};
|
|
3420
|
-
const getStepClasses = (status) => {
|
|
3421
|
-
switch (status) {
|
|
3422
|
-
case "completed":
|
|
3423
|
-
return {
|
|
3424
|
-
circle: "bg-success text-white",
|
|
3425
|
-
text: "text-text-primary"
|
|
3426
|
-
};
|
|
3427
|
-
case "current":
|
|
3428
|
-
return {
|
|
3429
|
-
circle: "bg-primary text-white ring-4 ring-primary/20",
|
|
3430
|
-
text: "text-primary font-medium"
|
|
3431
|
-
};
|
|
3432
|
-
case "upcoming":
|
|
3433
|
-
default:
|
|
3434
|
-
return {
|
|
3435
|
-
circle: "bg-gray-200 text-gray-500",
|
|
3436
|
-
text: "text-text-secondary"
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
};
|
|
3440
|
-
if (orientation === "vertical") {
|
|
3441
|
-
const stepsHtml2 = steps.map((step, index) => {
|
|
3442
|
-
const classes = getStepClasses(step.status);
|
|
3443
|
-
const isLast = index === steps.length - 1;
|
|
3444
|
-
const connectorHtml = !isLast && connector !== "none" ? `<div class="ml-5 w-0.5 h-8 ${connector === "dashed" ? "border-l-2 border-dashed border-gray-300" : "bg-gray-200"} ${step.status === "completed" ? "bg-success" : ""}"></div>` : "";
|
|
3445
|
-
const stepContent = `
|
|
3446
|
-
<div class="flex items-start gap-4">
|
|
3447
|
-
<div class="w-10 h-10 rounded-full ${classes.circle} flex items-center justify-center flex-shrink-0">
|
|
3448
|
-
${getStepIcon(step, index)}
|
|
3449
|
-
</div>
|
|
3450
|
-
<div class="pt-2">
|
|
3451
|
-
<div class="${classes.text}">${escapeHtml2(step.label)}</div>
|
|
3452
|
-
${step.description ? `<p class="text-sm text-text-secondary mt-0.5">${escapeHtml2(step.description)}</p>` : ""}
|
|
3453
|
-
</div>
|
|
3454
|
-
</div>
|
|
3455
|
-
${connectorHtml}
|
|
3456
|
-
`;
|
|
3457
|
-
return step.href && step.status === "completed" ? `<a href="${escapeHtml2(step.href)}" class="block hover:opacity-80">${stepContent}</a>` : `<div>${stepContent}</div>`;
|
|
3458
|
-
}).join("\n");
|
|
3459
|
-
return `<div class="step-progress ${className}">${stepsHtml2}</div>`;
|
|
3460
|
-
}
|
|
3461
|
-
const stepsHtml = steps.map((step, index) => {
|
|
3462
|
-
const classes = getStepClasses(step.status);
|
|
3463
|
-
const isLast = index === steps.length - 1;
|
|
3464
|
-
const connectorHtml = !isLast && connector !== "none" ? `<div class="flex-1 h-0.5 mx-2 ${connector === "dashed" ? "border-t-2 border-dashed border-gray-300" : "bg-gray-200"} ${step.status === "completed" ? "bg-success" : ""}"></div>` : "";
|
|
3465
|
-
const stepHtml = `
|
|
3466
|
-
<div class="flex flex-col items-center">
|
|
3467
|
-
<div class="w-10 h-10 rounded-full ${classes.circle} flex items-center justify-center">
|
|
3468
|
-
${getStepIcon(step, index)}
|
|
3469
|
-
</div>
|
|
3470
|
-
<div class="mt-2 text-center">
|
|
3471
|
-
<div class="${classes.text} text-sm">${escapeHtml2(step.label)}</div>
|
|
3472
|
-
${step.description ? `<p class="text-xs text-text-secondary mt-0.5 max-w-[120px]">${escapeHtml2(step.description)}</p>` : ""}
|
|
3473
|
-
</div>
|
|
3474
|
-
</div>
|
|
3475
|
-
`;
|
|
3476
|
-
const clickableStep = step.href && step.status === "completed" ? `<a href="${escapeHtml2(step.href)}" class="hover:opacity-80">${stepHtml}</a>` : stepHtml;
|
|
3477
|
-
return `${clickableStep}${connectorHtml}`;
|
|
3478
|
-
}).join("\n");
|
|
3479
|
-
return `<div class="step-progress flex items-start ${className}">${stepsHtml}</div>`;
|
|
3480
|
-
}
|
|
3481
|
-
function circularProgress(options) {
|
|
3482
|
-
const { value, size = 80, strokeWidth = 8, variant = "primary", showLabel = true, label, className = "" } = options;
|
|
3483
|
-
const clampedValue = Math.min(100, Math.max(0, value));
|
|
3484
|
-
const radius = (size - strokeWidth) / 2;
|
|
3485
|
-
const circumference = radius * 2 * Math.PI;
|
|
3486
|
-
const offset = circumference - clampedValue / 100 * circumference;
|
|
3487
|
-
const variantColors = {
|
|
3488
|
-
primary: "text-primary",
|
|
3489
|
-
success: "text-success",
|
|
3490
|
-
warning: "text-warning",
|
|
3491
|
-
danger: "text-danger"
|
|
3492
|
-
};
|
|
3493
|
-
const displayLabel = label || `${Math.round(clampedValue)}%`;
|
|
3494
|
-
return `<div class="circular-progress inline-flex items-center justify-center ${className}" style="width: ${size}px; height: ${size}px;">
|
|
3495
|
-
<svg class="transform -rotate-90" width="${size}" height="${size}">
|
|
3496
|
-
<!-- Background circle -->
|
|
3497
|
-
<circle
|
|
3498
|
-
cx="${size / 2}"
|
|
3499
|
-
cy="${size / 2}"
|
|
3500
|
-
r="${radius}"
|
|
3501
|
-
fill="none"
|
|
3502
|
-
stroke="currentColor"
|
|
3503
|
-
stroke-width="${strokeWidth}"
|
|
3504
|
-
class="text-gray-200"
|
|
3505
|
-
/>
|
|
3506
|
-
<!-- Progress circle -->
|
|
3507
|
-
<circle
|
|
3508
|
-
cx="${size / 2}"
|
|
3509
|
-
cy="${size / 2}"
|
|
3510
|
-
r="${radius}"
|
|
3511
|
-
fill="none"
|
|
3512
|
-
stroke="currentColor"
|
|
3513
|
-
stroke-width="${strokeWidth}"
|
|
3514
|
-
stroke-linecap="round"
|
|
3515
|
-
class="${variantColors[variant]}"
|
|
3516
|
-
style="stroke-dasharray: ${circumference}; stroke-dashoffset: ${offset}; transition: stroke-dashoffset 0.3s ease;"
|
|
3517
|
-
/>
|
|
3518
|
-
</svg>
|
|
3519
|
-
${showLabel ? `<span class="absolute text-sm font-semibold text-text-primary">${escapeHtml2(displayLabel)}</span>` : ""}
|
|
3520
|
-
</div>`;
|
|
3521
|
-
}
|
|
3522
|
-
function statusIndicator(options) {
|
|
3523
|
-
const { status, label, size = "md", pulse = false, className = "" } = options;
|
|
3524
|
-
const sizeClasses = {
|
|
3525
|
-
sm: { dot: "w-2 h-2", text: "text-xs" },
|
|
3526
|
-
md: { dot: "w-2.5 h-2.5", text: "text-sm" },
|
|
3527
|
-
lg: { dot: "w-3 h-3", text: "text-base" }
|
|
3528
|
-
};
|
|
3529
|
-
const statusClasses = {
|
|
3530
|
-
online: { color: "bg-success", label: "Online" },
|
|
3531
|
-
offline: { color: "bg-gray-400", label: "Offline" },
|
|
3532
|
-
busy: { color: "bg-danger", label: "Busy" },
|
|
3533
|
-
away: { color: "bg-warning", label: "Away" },
|
|
3534
|
-
loading: { color: "bg-blue-500", label: "Loading" },
|
|
3535
|
-
error: { color: "bg-danger", label: "Error" },
|
|
3536
|
-
success: { color: "bg-success", label: "Success" }
|
|
3537
|
-
};
|
|
3538
|
-
const statusInfo = statusClasses[status];
|
|
3539
|
-
const sizeInfo = sizeClasses[size];
|
|
3540
|
-
const displayLabel = label || statusInfo.label;
|
|
3541
|
-
const pulseHtml = pulse || status === "loading" ? `<span class="absolute ${sizeInfo.dot} ${statusInfo.color} rounded-full animate-ping opacity-75"></span>` : "";
|
|
3542
|
-
return `<div class="status-indicator inline-flex items-center gap-2 ${className}">
|
|
3543
|
-
<span class="relative flex">
|
|
3544
|
-
${pulseHtml}
|
|
3545
|
-
<span class="relative ${sizeInfo.dot} ${statusInfo.color} rounded-full"></span>
|
|
3546
|
-
</span>
|
|
3547
|
-
${displayLabel ? `<span class="${sizeInfo.text} text-text-secondary">${escapeHtml2(displayLabel)}</span>` : ""}
|
|
3548
|
-
</div>`;
|
|
3549
|
-
}
|
|
3550
|
-
function skeleton(options = {}) {
|
|
3551
|
-
const { type = "text", width, height, lines = 3, animated = true, className = "" } = options;
|
|
3552
|
-
const animateClass = animated ? "animate-pulse" : "";
|
|
3553
|
-
const baseClass = `bg-gray-200 ${animateClass}`;
|
|
3554
|
-
switch (type) {
|
|
3555
|
-
case "circle":
|
|
3556
|
-
return `<div class="${baseClass} rounded-full ${className}" style="width: ${width || "40px"}; height: ${height || "40px"}"></div>`;
|
|
3557
|
-
case "rect":
|
|
3558
|
-
return `<div class="${baseClass} rounded ${className}" style="width: ${width || "100%"}; height: ${height || "100px"}"></div>`;
|
|
3559
|
-
case "card":
|
|
3560
|
-
return `<div class="${animateClass} space-y-4 ${className}">
|
|
3561
|
-
<div class="bg-gray-200 rounded h-40"></div>
|
|
3562
|
-
<div class="space-y-2">
|
|
3563
|
-
<div class="bg-gray-200 h-4 rounded w-3/4"></div>
|
|
3564
|
-
<div class="bg-gray-200 h-4 rounded w-1/2"></div>
|
|
3565
|
-
</div>
|
|
3566
|
-
</div>`;
|
|
3567
|
-
case "text":
|
|
3568
|
-
default: {
|
|
3569
|
-
const linesHtml = Array(lines).fill(0).map((_, i) => {
|
|
3570
|
-
const lineWidth = i === lines - 1 ? "60%" : i === 0 ? "90%" : "80%";
|
|
3571
|
-
return `<div class="bg-gray-200 h-4 rounded" style="width: ${lineWidth}"></div>`;
|
|
3572
|
-
}).join("\n");
|
|
3573
|
-
return `<div class="${animateClass} space-y-2 ${className}" style="width: ${width || "100%"}">
|
|
3574
|
-
${linesHtml}
|
|
3575
|
-
</div>`;
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
function contentSkeleton(options = {}) {
|
|
3580
|
-
const { animated = true, className = "" } = options;
|
|
3581
|
-
const animateClass = animated ? "animate-pulse" : "";
|
|
3582
|
-
return `<div class="${animateClass} flex gap-4 ${className}">
|
|
3583
|
-
<div class="bg-gray-200 rounded-full w-12 h-12 flex-shrink-0"></div>
|
|
3584
|
-
<div class="flex-1 space-y-2 py-1">
|
|
3585
|
-
<div class="bg-gray-200 h-4 rounded w-3/4"></div>
|
|
3586
|
-
<div class="bg-gray-200 h-4 rounded w-1/2"></div>
|
|
3587
|
-
</div>
|
|
3588
|
-
</div>`;
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3591
2671
|
// libs/ui/src/bridge/core/adapter-registry.ts
|
|
3592
2672
|
var AdapterRegistry = class {
|
|
3593
2673
|
_adapters = /* @__PURE__ */ new Map();
|
|
@@ -3952,50 +3032,50 @@ var FrontMcpBridge = class {
|
|
|
3952
3032
|
* Get current theme.
|
|
3953
3033
|
*/
|
|
3954
3034
|
getTheme() {
|
|
3955
|
-
this._ensureInitialized();
|
|
3956
|
-
return
|
|
3035
|
+
const adapter = this._ensureInitialized();
|
|
3036
|
+
return adapter.getTheme();
|
|
3957
3037
|
}
|
|
3958
3038
|
/**
|
|
3959
3039
|
* Get current display mode.
|
|
3960
3040
|
*/
|
|
3961
3041
|
getDisplayMode() {
|
|
3962
|
-
this._ensureInitialized();
|
|
3963
|
-
return
|
|
3042
|
+
const adapter = this._ensureInitialized();
|
|
3043
|
+
return adapter.getDisplayMode();
|
|
3964
3044
|
}
|
|
3965
3045
|
/**
|
|
3966
3046
|
* Get tool input arguments.
|
|
3967
3047
|
*/
|
|
3968
3048
|
getToolInput() {
|
|
3969
|
-
this._ensureInitialized();
|
|
3970
|
-
return
|
|
3049
|
+
const adapter = this._ensureInitialized();
|
|
3050
|
+
return adapter.getToolInput();
|
|
3971
3051
|
}
|
|
3972
3052
|
/**
|
|
3973
3053
|
* Get tool output/result.
|
|
3974
3054
|
*/
|
|
3975
3055
|
getToolOutput() {
|
|
3976
|
-
this._ensureInitialized();
|
|
3977
|
-
return
|
|
3056
|
+
const adapter = this._ensureInitialized();
|
|
3057
|
+
return adapter.getToolOutput();
|
|
3978
3058
|
}
|
|
3979
3059
|
/**
|
|
3980
3060
|
* Get structured content (parsed output).
|
|
3981
3061
|
*/
|
|
3982
3062
|
getStructuredContent() {
|
|
3983
|
-
this._ensureInitialized();
|
|
3984
|
-
return
|
|
3063
|
+
const adapter = this._ensureInitialized();
|
|
3064
|
+
return adapter.getStructuredContent();
|
|
3985
3065
|
}
|
|
3986
3066
|
/**
|
|
3987
3067
|
* Get persisted widget state.
|
|
3988
3068
|
*/
|
|
3989
3069
|
getWidgetState() {
|
|
3990
|
-
this._ensureInitialized();
|
|
3991
|
-
return
|
|
3070
|
+
const adapter = this._ensureInitialized();
|
|
3071
|
+
return adapter.getWidgetState();
|
|
3992
3072
|
}
|
|
3993
3073
|
/**
|
|
3994
3074
|
* Get full host context.
|
|
3995
3075
|
*/
|
|
3996
3076
|
getHostContext() {
|
|
3997
|
-
this._ensureInitialized();
|
|
3998
|
-
return
|
|
3077
|
+
const adapter = this._ensureInitialized();
|
|
3078
|
+
return adapter.getHostContext();
|
|
3999
3079
|
}
|
|
4000
3080
|
// ============================================
|
|
4001
3081
|
// Actions (delegate to adapter)
|
|
@@ -4006,2026 +3086,139 @@ var FrontMcpBridge = class {
|
|
|
4006
3086
|
* @param args - Tool arguments
|
|
4007
3087
|
*/
|
|
4008
3088
|
async callTool(name, args) {
|
|
4009
|
-
this._ensureInitialized();
|
|
3089
|
+
const adapter = this._ensureInitialized();
|
|
4010
3090
|
if (!this.hasCapability("canCallTools")) {
|
|
4011
3091
|
throw new Error("Tool calls are not supported by the current adapter");
|
|
4012
3092
|
}
|
|
4013
|
-
return
|
|
3093
|
+
return adapter.callTool(name, args);
|
|
4014
3094
|
}
|
|
4015
3095
|
/**
|
|
4016
3096
|
* Send a follow-up message to the conversation.
|
|
4017
3097
|
* @param content - Message content
|
|
4018
3098
|
*/
|
|
4019
3099
|
async sendMessage(content) {
|
|
4020
|
-
this._ensureInitialized();
|
|
3100
|
+
const adapter = this._ensureInitialized();
|
|
4021
3101
|
if (!this.hasCapability("canSendMessages")) {
|
|
4022
3102
|
throw new Error("Sending messages is not supported by the current adapter");
|
|
4023
3103
|
}
|
|
4024
|
-
return
|
|
3104
|
+
return adapter.sendMessage(content);
|
|
4025
3105
|
}
|
|
4026
3106
|
/**
|
|
4027
3107
|
* Open an external link.
|
|
4028
3108
|
* @param url - URL to open
|
|
4029
3109
|
*/
|
|
4030
3110
|
async openLink(url) {
|
|
4031
|
-
this._ensureInitialized();
|
|
4032
|
-
return
|
|
3111
|
+
const adapter = this._ensureInitialized();
|
|
3112
|
+
return adapter.openLink(url);
|
|
4033
3113
|
}
|
|
4034
3114
|
/**
|
|
4035
3115
|
* Request a display mode change.
|
|
4036
3116
|
* @param mode - Desired display mode
|
|
4037
3117
|
*/
|
|
4038
3118
|
async requestDisplayMode(mode) {
|
|
4039
|
-
this._ensureInitialized();
|
|
4040
|
-
return
|
|
3119
|
+
const adapter = this._ensureInitialized();
|
|
3120
|
+
return adapter.requestDisplayMode(mode);
|
|
4041
3121
|
}
|
|
4042
3122
|
/**
|
|
4043
3123
|
* Request widget close.
|
|
4044
3124
|
*/
|
|
4045
3125
|
async requestClose() {
|
|
4046
|
-
this._ensureInitialized();
|
|
4047
|
-
return
|
|
3126
|
+
const adapter = this._ensureInitialized();
|
|
3127
|
+
return adapter.requestClose();
|
|
4048
3128
|
}
|
|
4049
3129
|
/**
|
|
4050
3130
|
* Set widget state (persisted across sessions).
|
|
4051
3131
|
* @param state - State object to persist
|
|
4052
3132
|
*/
|
|
4053
3133
|
setWidgetState(state) {
|
|
4054
|
-
this._ensureInitialized();
|
|
4055
|
-
|
|
4056
|
-
}
|
|
4057
|
-
// ============================================
|
|
4058
|
-
// Events (delegate to adapter)
|
|
4059
|
-
// ============================================
|
|
4060
|
-
/**
|
|
4061
|
-
* Subscribe to host context changes.
|
|
4062
|
-
* @param callback - Called when context changes
|
|
4063
|
-
* @returns Unsubscribe function
|
|
4064
|
-
*/
|
|
4065
|
-
onContextChange(callback) {
|
|
4066
|
-
this._ensureInitialized();
|
|
4067
|
-
return this._adapter.onContextChange(callback);
|
|
4068
|
-
}
|
|
4069
|
-
/**
|
|
4070
|
-
* Subscribe to tool result updates.
|
|
4071
|
-
* @param callback - Called when tool result is received
|
|
4072
|
-
* @returns Unsubscribe function
|
|
4073
|
-
*/
|
|
4074
|
-
onToolResult(callback) {
|
|
4075
|
-
this._ensureInitialized();
|
|
4076
|
-
return this._adapter.onToolResult(callback);
|
|
3134
|
+
const adapter = this._ensureInitialized();
|
|
3135
|
+
adapter.setWidgetState(state);
|
|
4077
3136
|
}
|
|
4078
3137
|
// ============================================
|
|
4079
|
-
//
|
|
4080
|
-
// ============================================
|
|
4081
|
-
/**
|
|
4082
|
-
*
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
/**
|
|
4090
|
-
* Wrap a promise with a timeout.
|
|
4091
|
-
*/
|
|
4092
|
-
_withTimeout(promise, ms) {
|
|
4093
|
-
return new Promise((resolve, reject) => {
|
|
4094
|
-
const timer = setTimeout(() => {
|
|
4095
|
-
reject(new Error(`Operation timed out after ${ms}ms`));
|
|
4096
|
-
}, ms);
|
|
4097
|
-
promise.then((result) => {
|
|
4098
|
-
clearTimeout(timer);
|
|
4099
|
-
resolve(result);
|
|
4100
|
-
}).catch((error) => {
|
|
4101
|
-
clearTimeout(timer);
|
|
4102
|
-
reject(error);
|
|
4103
|
-
});
|
|
4104
|
-
});
|
|
4105
|
-
}
|
|
4106
|
-
/**
|
|
4107
|
-
* Emit a bridge event via CustomEvent.
|
|
4108
|
-
*/
|
|
4109
|
-
_emitEvent(type, payload) {
|
|
4110
|
-
if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
|
|
4111
|
-
try {
|
|
4112
|
-
const event = new CustomEvent(type, { detail: payload });
|
|
4113
|
-
window.dispatchEvent(event);
|
|
4114
|
-
} catch {
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
}
|
|
4118
|
-
/**
|
|
4119
|
-
* Log debug message if debugging is enabled.
|
|
4120
|
-
*/
|
|
4121
|
-
_log(message) {
|
|
4122
|
-
if (this._config.debug) {
|
|
4123
|
-
console.log(`[FrontMcpBridge] ${message}`);
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
};
|
|
4127
|
-
async function createBridge(config, registry) {
|
|
4128
|
-
const bridge = new FrontMcpBridge(config, registry);
|
|
4129
|
-
await bridge.initialize();
|
|
4130
|
-
return bridge;
|
|
4131
|
-
}
|
|
4132
|
-
|
|
4133
|
-
// libs/ui/src/bridge/adapters/base-adapter.ts
|
|
4134
|
-
var DEFAULT_CAPABILITIES = {
|
|
4135
|
-
canCallTools: false,
|
|
4136
|
-
canSendMessages: false,
|
|
4137
|
-
canOpenLinks: false,
|
|
4138
|
-
canPersistState: true,
|
|
4139
|
-
// localStorage fallback
|
|
4140
|
-
hasNetworkAccess: true,
|
|
4141
|
-
supportsDisplayModes: false,
|
|
4142
|
-
supportsTheme: true
|
|
4143
|
-
};
|
|
4144
|
-
var DEFAULT_SAFE_AREA = {
|
|
4145
|
-
top: 0,
|
|
4146
|
-
bottom: 0,
|
|
4147
|
-
left: 0,
|
|
4148
|
-
right: 0
|
|
4149
|
-
};
|
|
4150
|
-
var BaseAdapter = class {
|
|
4151
|
-
_capabilities = { ...DEFAULT_CAPABILITIES };
|
|
4152
|
-
_hostContext;
|
|
4153
|
-
_widgetState = {};
|
|
4154
|
-
_toolInput = {};
|
|
4155
|
-
_toolOutput = void 0;
|
|
4156
|
-
_structuredContent = void 0;
|
|
4157
|
-
_initialized = false;
|
|
4158
|
-
_contextListeners = /* @__PURE__ */ new Set();
|
|
4159
|
-
_toolResultListeners = /* @__PURE__ */ new Set();
|
|
4160
|
-
constructor() {
|
|
4161
|
-
this._hostContext = this._createDefaultHostContext();
|
|
4162
|
-
}
|
|
4163
|
-
get capabilities() {
|
|
4164
|
-
return this._capabilities;
|
|
4165
|
-
}
|
|
4166
|
-
async initialize() {
|
|
4167
|
-
if (this._initialized) return;
|
|
4168
|
-
this._loadWidgetState();
|
|
4169
|
-
this._readInjectedData();
|
|
4170
|
-
this._initialized = true;
|
|
4171
|
-
}
|
|
4172
|
-
dispose() {
|
|
4173
|
-
this._contextListeners.clear();
|
|
4174
|
-
this._toolResultListeners.clear();
|
|
4175
|
-
this._initialized = false;
|
|
4176
|
-
}
|
|
4177
|
-
// ============================================
|
|
4178
|
-
// Data Access
|
|
4179
|
-
// ============================================
|
|
4180
|
-
getTheme() {
|
|
4181
|
-
return this._hostContext.theme;
|
|
4182
|
-
}
|
|
4183
|
-
getDisplayMode() {
|
|
4184
|
-
return this._hostContext.displayMode;
|
|
4185
|
-
}
|
|
4186
|
-
getUserAgent() {
|
|
4187
|
-
return this._hostContext.userAgent;
|
|
4188
|
-
}
|
|
4189
|
-
getLocale() {
|
|
4190
|
-
return this._hostContext.locale;
|
|
4191
|
-
}
|
|
4192
|
-
getToolInput() {
|
|
4193
|
-
return this._toolInput;
|
|
4194
|
-
}
|
|
4195
|
-
getToolOutput() {
|
|
4196
|
-
return this._toolOutput;
|
|
4197
|
-
}
|
|
4198
|
-
getStructuredContent() {
|
|
4199
|
-
return this._structuredContent;
|
|
4200
|
-
}
|
|
4201
|
-
getWidgetState() {
|
|
4202
|
-
return this._widgetState;
|
|
4203
|
-
}
|
|
4204
|
-
getSafeArea() {
|
|
4205
|
-
return this._hostContext.safeArea;
|
|
4206
|
-
}
|
|
4207
|
-
getViewport() {
|
|
4208
|
-
return this._hostContext.viewport;
|
|
4209
|
-
}
|
|
4210
|
-
getHostContext() {
|
|
4211
|
-
return { ...this._hostContext };
|
|
4212
|
-
}
|
|
4213
|
-
// ============================================
|
|
4214
|
-
// Actions (override in subclasses for real functionality)
|
|
4215
|
-
// ============================================
|
|
4216
|
-
async callTool(_name, _args) {
|
|
4217
|
-
if (!this._capabilities.canCallTools) {
|
|
4218
|
-
throw new Error(`Tool calls are not supported by ${this.name} adapter`);
|
|
4219
|
-
}
|
|
4220
|
-
throw new Error("callTool not implemented");
|
|
4221
|
-
}
|
|
4222
|
-
async sendMessage(_content) {
|
|
4223
|
-
if (!this._capabilities.canSendMessages) {
|
|
4224
|
-
throw new Error(`Sending messages is not supported by ${this.name} adapter`);
|
|
4225
|
-
}
|
|
4226
|
-
throw new Error("sendMessage not implemented");
|
|
4227
|
-
}
|
|
4228
|
-
async openLink(url) {
|
|
4229
|
-
if (!this._capabilities.canOpenLinks) {
|
|
4230
|
-
if (typeof window !== "undefined") {
|
|
4231
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
4232
|
-
return;
|
|
4233
|
-
}
|
|
4234
|
-
throw new Error(`Opening links is not supported by ${this.name} adapter`);
|
|
4235
|
-
}
|
|
4236
|
-
throw new Error("openLink not implemented");
|
|
4237
|
-
}
|
|
4238
|
-
async requestDisplayMode(_mode) {
|
|
4239
|
-
if (!this._capabilities.supportsDisplayModes) {
|
|
4240
|
-
return;
|
|
4241
|
-
}
|
|
4242
|
-
throw new Error("requestDisplayMode not implemented");
|
|
4243
|
-
}
|
|
4244
|
-
async requestClose() {
|
|
4245
|
-
}
|
|
4246
|
-
setWidgetState(state) {
|
|
4247
|
-
this._widgetState = { ...this._widgetState, ...state };
|
|
4248
|
-
this._persistWidgetState();
|
|
4249
|
-
}
|
|
4250
|
-
// ============================================
|
|
4251
|
-
// Events
|
|
4252
|
-
// ============================================
|
|
4253
|
-
onContextChange(callback) {
|
|
4254
|
-
this._contextListeners.add(callback);
|
|
4255
|
-
return () => {
|
|
4256
|
-
this._contextListeners.delete(callback);
|
|
4257
|
-
};
|
|
4258
|
-
}
|
|
4259
|
-
onToolResult(callback) {
|
|
4260
|
-
this._toolResultListeners.add(callback);
|
|
4261
|
-
return () => {
|
|
4262
|
-
this._toolResultListeners.delete(callback);
|
|
4263
|
-
};
|
|
4264
|
-
}
|
|
4265
|
-
// ============================================
|
|
4266
|
-
// Protected Helpers
|
|
4267
|
-
// ============================================
|
|
4268
|
-
/**
|
|
4269
|
-
* Create default host context from environment detection.
|
|
4270
|
-
*/
|
|
4271
|
-
_createDefaultHostContext() {
|
|
4272
|
-
return {
|
|
4273
|
-
theme: this._detectTheme(),
|
|
4274
|
-
displayMode: "inline",
|
|
4275
|
-
locale: this._detectLocale(),
|
|
4276
|
-
userAgent: this._detectUserAgent(),
|
|
4277
|
-
safeArea: DEFAULT_SAFE_AREA,
|
|
4278
|
-
viewport: this._detectViewport()
|
|
4279
|
-
};
|
|
4280
|
-
}
|
|
4281
|
-
/**
|
|
4282
|
-
* Detect theme from CSS media query.
|
|
4283
|
-
*/
|
|
4284
|
-
_detectTheme() {
|
|
4285
|
-
if (typeof window !== "undefined" && window.matchMedia) {
|
|
4286
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
4287
|
-
}
|
|
4288
|
-
return "light";
|
|
4289
|
-
}
|
|
4290
|
-
/**
|
|
4291
|
-
* Detect locale from navigator.
|
|
4292
|
-
*/
|
|
4293
|
-
_detectLocale() {
|
|
4294
|
-
if (typeof navigator !== "undefined") {
|
|
4295
|
-
return navigator.language || "en-US";
|
|
4296
|
-
}
|
|
4297
|
-
return "en-US";
|
|
4298
|
-
}
|
|
4299
|
-
/**
|
|
4300
|
-
* Detect user agent capabilities.
|
|
4301
|
-
*/
|
|
4302
|
-
_detectUserAgent() {
|
|
4303
|
-
if (typeof navigator === "undefined") {
|
|
4304
|
-
return { type: "web", hover: true, touch: false };
|
|
4305
|
-
}
|
|
4306
|
-
const ua = navigator.userAgent || "";
|
|
4307
|
-
const isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
|
|
4308
|
-
const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
4309
|
-
const hasHover = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(hover: hover)").matches;
|
|
4310
|
-
return {
|
|
4311
|
-
type: isMobile ? "mobile" : "web",
|
|
4312
|
-
hover: hasHover !== false,
|
|
4313
|
-
touch: hasTouch
|
|
4314
|
-
};
|
|
4315
|
-
}
|
|
4316
|
-
/**
|
|
4317
|
-
* Detect viewport dimensions.
|
|
4318
|
-
*/
|
|
4319
|
-
_detectViewport() {
|
|
4320
|
-
if (typeof window !== "undefined") {
|
|
4321
|
-
return {
|
|
4322
|
-
width: window.innerWidth,
|
|
4323
|
-
height: window.innerHeight
|
|
4324
|
-
};
|
|
4325
|
-
}
|
|
4326
|
-
return void 0;
|
|
4327
|
-
}
|
|
4328
|
-
/**
|
|
4329
|
-
* Read injected tool data from window globals.
|
|
4330
|
-
*/
|
|
4331
|
-
_readInjectedData() {
|
|
4332
|
-
if (typeof window === "undefined") return;
|
|
4333
|
-
const win = window;
|
|
4334
|
-
if (win.__mcpToolInput) {
|
|
4335
|
-
this._toolInput = win.__mcpToolInput;
|
|
4336
|
-
}
|
|
4337
|
-
if (win.__mcpToolOutput) {
|
|
4338
|
-
this._toolOutput = win.__mcpToolOutput;
|
|
4339
|
-
}
|
|
4340
|
-
if (win.__mcpStructuredContent) {
|
|
4341
|
-
this._structuredContent = win.__mcpStructuredContent;
|
|
4342
|
-
}
|
|
4343
|
-
if (win.__mcpHostContext) {
|
|
4344
|
-
this._hostContext = { ...this._hostContext, ...win.__mcpHostContext };
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
/**
|
|
4348
|
-
* Load widget state from localStorage.
|
|
4349
|
-
*/
|
|
4350
|
-
_loadWidgetState() {
|
|
4351
|
-
if (typeof localStorage === "undefined") return;
|
|
4352
|
-
try {
|
|
4353
|
-
const key = this._getStateKey();
|
|
4354
|
-
const stored = localStorage.getItem(key);
|
|
4355
|
-
if (stored) {
|
|
4356
|
-
this._widgetState = JSON.parse(stored);
|
|
4357
|
-
}
|
|
4358
|
-
} catch {
|
|
4359
|
-
}
|
|
4360
|
-
}
|
|
4361
|
-
/**
|
|
4362
|
-
* Persist widget state to localStorage.
|
|
4363
|
-
*/
|
|
4364
|
-
_persistWidgetState() {
|
|
4365
|
-
if (typeof localStorage === "undefined") return;
|
|
4366
|
-
try {
|
|
4367
|
-
const key = this._getStateKey();
|
|
4368
|
-
localStorage.setItem(key, JSON.stringify(this._widgetState));
|
|
4369
|
-
} catch {
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
/**
|
|
4373
|
-
* Get localStorage key for widget state.
|
|
4374
|
-
*/
|
|
4375
|
-
_getStateKey() {
|
|
4376
|
-
if (typeof window !== "undefined") {
|
|
4377
|
-
const toolName = window.__mcpToolName || "unknown";
|
|
4378
|
-
return `frontmcp:widget:${toolName}`;
|
|
4379
|
-
}
|
|
4380
|
-
return "frontmcp:widget:unknown";
|
|
4381
|
-
}
|
|
4382
|
-
/**
|
|
4383
|
-
* Notify context change listeners.
|
|
4384
|
-
*/
|
|
4385
|
-
_notifyContextChange(changes) {
|
|
4386
|
-
this._hostContext = { ...this._hostContext, ...changes };
|
|
4387
|
-
this._contextListeners.forEach((cb) => {
|
|
4388
|
-
try {
|
|
4389
|
-
cb(changes);
|
|
4390
|
-
} catch (e) {
|
|
4391
|
-
console.error("[FrontMcpBridge] Context change listener error:", e);
|
|
4392
|
-
}
|
|
4393
|
-
});
|
|
4394
|
-
}
|
|
4395
|
-
/**
|
|
4396
|
-
* Notify tool result listeners.
|
|
4397
|
-
*/
|
|
4398
|
-
_notifyToolResult(result) {
|
|
4399
|
-
this._toolOutput = result;
|
|
4400
|
-
this._toolResultListeners.forEach((cb) => {
|
|
4401
|
-
try {
|
|
4402
|
-
cb(result);
|
|
4403
|
-
} catch (e) {
|
|
4404
|
-
console.error("[FrontMcpBridge] Tool result listener error:", e);
|
|
4405
|
-
}
|
|
4406
|
-
});
|
|
4407
|
-
}
|
|
4408
|
-
};
|
|
4409
|
-
|
|
4410
|
-
// libs/ui/src/bridge/adapters/openai.adapter.ts
|
|
4411
|
-
var OpenAIAdapter = class extends BaseAdapter {
|
|
4412
|
-
id = "openai";
|
|
4413
|
-
name = "OpenAI ChatGPT";
|
|
4414
|
-
priority = 100;
|
|
4415
|
-
// Highest priority
|
|
4416
|
-
_openai;
|
|
4417
|
-
_unsubscribeContext;
|
|
4418
|
-
_unsubscribeToolResult;
|
|
4419
|
-
constructor() {
|
|
4420
|
-
super();
|
|
4421
|
-
this._capabilities = {
|
|
4422
|
-
...DEFAULT_CAPABILITIES,
|
|
4423
|
-
canCallTools: true,
|
|
4424
|
-
canSendMessages: true,
|
|
4425
|
-
canOpenLinks: true,
|
|
4426
|
-
canPersistState: true,
|
|
4427
|
-
hasNetworkAccess: true,
|
|
4428
|
-
supportsDisplayModes: true,
|
|
4429
|
-
supportsTheme: true
|
|
4430
|
-
};
|
|
4431
|
-
}
|
|
4432
|
-
/**
|
|
4433
|
-
* Check if OpenAI Apps SDK is available.
|
|
4434
|
-
*/
|
|
4435
|
-
canHandle() {
|
|
4436
|
-
if (typeof window === "undefined") return false;
|
|
4437
|
-
const win = window;
|
|
4438
|
-
return Boolean(win.openai?.canvas);
|
|
4439
|
-
}
|
|
4440
|
-
/**
|
|
4441
|
-
* Initialize the OpenAI adapter.
|
|
4442
|
-
*/
|
|
4443
|
-
async initialize() {
|
|
4444
|
-
if (this._initialized) return;
|
|
4445
|
-
this._openai = window.openai;
|
|
4446
|
-
await super.initialize();
|
|
4447
|
-
this._syncContextFromSDK();
|
|
4448
|
-
if (this._openai?.canvas?.onContextChange) {
|
|
4449
|
-
this._unsubscribeContext = this._openai.canvas.onContextChange((changes) => {
|
|
4450
|
-
this._notifyContextChange(changes);
|
|
4451
|
-
});
|
|
4452
|
-
}
|
|
4453
|
-
if (this._openai?.canvas?.onToolResult) {
|
|
4454
|
-
this._unsubscribeToolResult = this._openai.canvas.onToolResult((result) => {
|
|
4455
|
-
this._notifyToolResult(result);
|
|
4456
|
-
});
|
|
4457
|
-
}
|
|
4458
|
-
}
|
|
4459
|
-
/**
|
|
4460
|
-
* Dispose adapter resources.
|
|
4461
|
-
*/
|
|
4462
|
-
dispose() {
|
|
4463
|
-
if (this._unsubscribeContext) {
|
|
4464
|
-
this._unsubscribeContext();
|
|
4465
|
-
this._unsubscribeContext = void 0;
|
|
4466
|
-
}
|
|
4467
|
-
if (this._unsubscribeToolResult) {
|
|
4468
|
-
this._unsubscribeToolResult();
|
|
4469
|
-
this._unsubscribeToolResult = void 0;
|
|
4470
|
-
}
|
|
4471
|
-
this._openai = void 0;
|
|
4472
|
-
super.dispose();
|
|
4473
|
-
}
|
|
4474
|
-
// ============================================
|
|
4475
|
-
// Data Access (override with SDK calls)
|
|
4476
|
-
// ============================================
|
|
4477
|
-
getTheme() {
|
|
4478
|
-
if (this._openai?.canvas?.getTheme) {
|
|
4479
|
-
const theme = this._openai.canvas.getTheme();
|
|
4480
|
-
return theme === "dark" ? "dark" : "light";
|
|
4481
|
-
}
|
|
4482
|
-
return super.getTheme();
|
|
4483
|
-
}
|
|
4484
|
-
getDisplayMode() {
|
|
4485
|
-
if (this._openai?.canvas?.getDisplayMode) {
|
|
4486
|
-
const mode = this._openai.canvas.getDisplayMode();
|
|
4487
|
-
if (mode === "fullscreen" || mode === "pip" || mode === "carousel") {
|
|
4488
|
-
return mode;
|
|
4489
|
-
}
|
|
4490
|
-
return "inline";
|
|
4491
|
-
}
|
|
4492
|
-
return super.getDisplayMode();
|
|
4493
|
-
}
|
|
4494
|
-
// ============================================
|
|
4495
|
-
// Actions (proxy to SDK)
|
|
4496
|
-
// ============================================
|
|
4497
|
-
async callTool(name, args) {
|
|
4498
|
-
if (!this._openai?.canvas?.callServerTool) {
|
|
4499
|
-
throw new Error("callServerTool not available in OpenAI SDK");
|
|
4500
|
-
}
|
|
4501
|
-
return this._openai.canvas.callServerTool(name, args);
|
|
4502
|
-
}
|
|
4503
|
-
async sendMessage(content) {
|
|
4504
|
-
if (!this._openai?.canvas?.sendMessage) {
|
|
4505
|
-
throw new Error("sendMessage not available in OpenAI SDK");
|
|
4506
|
-
}
|
|
4507
|
-
await this._openai.canvas.sendMessage(content);
|
|
4508
|
-
}
|
|
4509
|
-
async openLink(url) {
|
|
4510
|
-
if (!this._openai?.canvas?.openLink) {
|
|
4511
|
-
return super.openLink(url);
|
|
4512
|
-
}
|
|
4513
|
-
await this._openai.canvas.openLink(url);
|
|
4514
|
-
}
|
|
4515
|
-
async requestDisplayMode(mode) {
|
|
4516
|
-
if (!this._openai?.canvas?.setDisplayMode) {
|
|
4517
|
-
return super.requestDisplayMode(mode);
|
|
4518
|
-
}
|
|
4519
|
-
await this._openai.canvas.setDisplayMode(mode);
|
|
4520
|
-
this._hostContext = { ...this._hostContext, displayMode: mode };
|
|
4521
|
-
}
|
|
4522
|
-
async requestClose() {
|
|
4523
|
-
if (this._openai?.canvas?.close) {
|
|
4524
|
-
await this._openai.canvas.close();
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
// ============================================
|
|
4528
|
-
// Private Helpers
|
|
4529
|
-
// ============================================
|
|
4530
|
-
/**
|
|
4531
|
-
* Sync context from OpenAI SDK.
|
|
4532
|
-
*/
|
|
4533
|
-
_syncContextFromSDK() {
|
|
4534
|
-
if (!this._openai?.canvas) return;
|
|
4535
|
-
if (this._openai.canvas.getTheme) {
|
|
4536
|
-
const theme = this._openai.canvas.getTheme();
|
|
4537
|
-
this._hostContext.theme = theme === "dark" ? "dark" : "light";
|
|
4538
|
-
}
|
|
4539
|
-
if (this._openai.canvas.getDisplayMode) {
|
|
4540
|
-
const mode = this._openai.canvas.getDisplayMode();
|
|
4541
|
-
if (mode === "fullscreen" || mode === "pip" || mode === "carousel" || mode === "inline") {
|
|
4542
|
-
this._hostContext.displayMode = mode;
|
|
4543
|
-
}
|
|
4544
|
-
}
|
|
4545
|
-
if (this._openai.canvas.getContext) {
|
|
4546
|
-
const ctx = this._openai.canvas.getContext();
|
|
4547
|
-
if (ctx) {
|
|
4548
|
-
this._hostContext = { ...this._hostContext, ...ctx };
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
};
|
|
4553
|
-
function createOpenAIAdapter() {
|
|
4554
|
-
return new OpenAIAdapter();
|
|
4555
|
-
}
|
|
4556
|
-
|
|
4557
|
-
// libs/ui/src/bridge/adapters/ext-apps.adapter.ts
|
|
4558
|
-
var ExtAppsAdapter = class extends BaseAdapter {
|
|
4559
|
-
id = "ext-apps";
|
|
4560
|
-
name = "ext-apps (SEP-1865)";
|
|
4561
|
-
priority = 80;
|
|
4562
|
-
// High priority, but below OpenAI native
|
|
4563
|
-
_config;
|
|
4564
|
-
_messageListener;
|
|
4565
|
-
_pendingRequests = /* @__PURE__ */ new Map();
|
|
4566
|
-
_requestId = 0;
|
|
4567
|
-
_trustedOrigin;
|
|
4568
|
-
_hostCapabilities = {};
|
|
4569
|
-
constructor(config) {
|
|
4570
|
-
super();
|
|
4571
|
-
this._config = config || {};
|
|
4572
|
-
this._capabilities = {
|
|
4573
|
-
...DEFAULT_CAPABILITIES,
|
|
4574
|
-
canPersistState: true,
|
|
4575
|
-
hasNetworkAccess: true,
|
|
4576
|
-
// ext-apps usually allows network
|
|
4577
|
-
supportsTheme: true
|
|
4578
|
-
};
|
|
4579
|
-
}
|
|
4580
|
-
/**
|
|
4581
|
-
* Check if we're in an iframe (potential ext-apps context).
|
|
4582
|
-
*/
|
|
4583
|
-
canHandle() {
|
|
4584
|
-
if (typeof window === "undefined") return false;
|
|
4585
|
-
const inIframe = window.parent !== window;
|
|
4586
|
-
if (!inIframe) return false;
|
|
4587
|
-
const win = window;
|
|
4588
|
-
if (win.openai?.canvas) return false;
|
|
4589
|
-
if (win.__mcpPlatform === "ext-apps") return true;
|
|
4590
|
-
return true;
|
|
4591
|
-
}
|
|
4592
|
-
/**
|
|
4593
|
-
* Initialize the ext-apps adapter with protocol handshake.
|
|
4594
|
-
*/
|
|
4595
|
-
async initialize() {
|
|
4596
|
-
if (this._initialized) return;
|
|
4597
|
-
this._setupMessageListener();
|
|
4598
|
-
await super.initialize();
|
|
4599
|
-
await this._performHandshake();
|
|
4600
|
-
this._initialized = true;
|
|
4601
|
-
}
|
|
4602
|
-
/**
|
|
4603
|
-
* Dispose adapter resources.
|
|
4604
|
-
*/
|
|
4605
|
-
dispose() {
|
|
4606
|
-
if (this._messageListener && typeof window !== "undefined") {
|
|
4607
|
-
window.removeEventListener("message", this._messageListener);
|
|
4608
|
-
this._messageListener = void 0;
|
|
4609
|
-
}
|
|
4610
|
-
for (const [id, pending] of this._pendingRequests) {
|
|
4611
|
-
clearTimeout(pending.timeout);
|
|
4612
|
-
pending.reject(new Error("Adapter disposed"));
|
|
4613
|
-
}
|
|
4614
|
-
this._pendingRequests.clear();
|
|
4615
|
-
super.dispose();
|
|
4616
|
-
}
|
|
4617
|
-
// ============================================
|
|
4618
|
-
// Actions (via JSON-RPC)
|
|
4619
|
-
// ============================================
|
|
4620
|
-
async callTool(name, args) {
|
|
4621
|
-
if (!this._hostCapabilities.serverToolProxy) {
|
|
4622
|
-
throw new Error("Server tool proxy not supported by host");
|
|
4623
|
-
}
|
|
4624
|
-
return this._sendRequest("ui/callServerTool", {
|
|
4625
|
-
name,
|
|
4626
|
-
arguments: args
|
|
4627
|
-
});
|
|
4628
|
-
}
|
|
4629
|
-
async sendMessage(content) {
|
|
4630
|
-
await this._sendRequest("ui/message", { content });
|
|
4631
|
-
}
|
|
4632
|
-
async openLink(url) {
|
|
4633
|
-
if (!this._hostCapabilities.openLink) {
|
|
4634
|
-
return super.openLink(url);
|
|
4635
|
-
}
|
|
4636
|
-
await this._sendRequest("ui/openLink", { url });
|
|
4637
|
-
}
|
|
4638
|
-
async requestDisplayMode(mode) {
|
|
4639
|
-
await this._sendRequest("ui/setDisplayMode", { mode });
|
|
4640
|
-
this._hostContext = { ...this._hostContext, displayMode: mode };
|
|
4641
|
-
}
|
|
4642
|
-
async requestClose() {
|
|
4643
|
-
await this._sendRequest("ui/close", {});
|
|
4644
|
-
}
|
|
4645
|
-
// ============================================
|
|
4646
|
-
// Private: Message Handling
|
|
4647
|
-
// ============================================
|
|
4648
|
-
/**
|
|
4649
|
-
* Setup postMessage listener for incoming messages.
|
|
4650
|
-
*/
|
|
4651
|
-
_setupMessageListener() {
|
|
4652
|
-
if (typeof window === "undefined") return;
|
|
4653
|
-
this._messageListener = (event) => {
|
|
4654
|
-
this._handleMessage(event);
|
|
4655
|
-
};
|
|
4656
|
-
window.addEventListener("message", this._messageListener);
|
|
4657
|
-
}
|
|
4658
|
-
/**
|
|
4659
|
-
* Handle incoming postMessage events.
|
|
4660
|
-
*/
|
|
4661
|
-
_handleMessage(event) {
|
|
4662
|
-
if (!this._isOriginTrusted(event.origin)) {
|
|
4663
|
-
return;
|
|
4664
|
-
}
|
|
4665
|
-
const data = event.data;
|
|
4666
|
-
if (!data || typeof data !== "object") return;
|
|
4667
|
-
if (data.jsonrpc !== "2.0") return;
|
|
4668
|
-
if ("id" in data && (data.result !== void 0 || data.error !== void 0)) {
|
|
4669
|
-
this._handleResponse(data);
|
|
4670
|
-
return;
|
|
4671
|
-
}
|
|
4672
|
-
if ("method" in data && !("id" in data)) {
|
|
4673
|
-
this._handleNotification(data);
|
|
4674
|
-
return;
|
|
4675
|
-
}
|
|
4676
|
-
}
|
|
4677
|
-
/**
|
|
4678
|
-
* Handle JSON-RPC response.
|
|
4679
|
-
*/
|
|
4680
|
-
_handleResponse(response) {
|
|
4681
|
-
const pending = this._pendingRequests.get(response.id);
|
|
4682
|
-
if (!pending) return;
|
|
4683
|
-
clearTimeout(pending.timeout);
|
|
4684
|
-
this._pendingRequests.delete(response.id);
|
|
4685
|
-
if (response.error) {
|
|
4686
|
-
pending.reject(new Error(`${response.error.message} (code: ${response.error.code})`));
|
|
4687
|
-
} else {
|
|
4688
|
-
pending.resolve(response.result);
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
/**
|
|
4692
|
-
* Handle JSON-RPC notification from host.
|
|
4693
|
-
*/
|
|
4694
|
-
_handleNotification(notification) {
|
|
4695
|
-
switch (notification.method) {
|
|
4696
|
-
case "ui/notifications/tool-input":
|
|
4697
|
-
this._handleToolInput(notification.params);
|
|
4698
|
-
break;
|
|
4699
|
-
case "ui/notifications/tool-input-partial":
|
|
4700
|
-
this._handleToolInputPartial(notification.params);
|
|
4701
|
-
break;
|
|
4702
|
-
case "ui/notifications/tool-result":
|
|
4703
|
-
this._handleToolResult(notification.params);
|
|
4704
|
-
break;
|
|
4705
|
-
case "ui/notifications/host-context-changed":
|
|
4706
|
-
this._handleHostContextChange(notification.params);
|
|
4707
|
-
break;
|
|
4708
|
-
case "ui/notifications/initialized":
|
|
4709
|
-
break;
|
|
4710
|
-
case "ui/notifications/cancelled":
|
|
4711
|
-
this._handleCancelled(notification.params);
|
|
4712
|
-
break;
|
|
4713
|
-
}
|
|
4714
|
-
}
|
|
4715
|
-
/**
|
|
4716
|
-
* Handle tool input notification.
|
|
4717
|
-
*/
|
|
4718
|
-
_handleToolInput(params) {
|
|
4719
|
-
this._toolInput = params.arguments || {};
|
|
4720
|
-
this._emitBridgeEvent("tool:input", { arguments: this._toolInput });
|
|
4721
|
-
}
|
|
4722
|
-
/**
|
|
4723
|
-
* Handle partial tool input (streaming).
|
|
4724
|
-
*/
|
|
4725
|
-
_handleToolInputPartial(params) {
|
|
4726
|
-
this._toolInput = { ...this._toolInput, ...params.arguments };
|
|
4727
|
-
this._emitBridgeEvent("tool:input-partial", { arguments: this._toolInput });
|
|
4728
|
-
}
|
|
4729
|
-
/**
|
|
4730
|
-
* Handle tool result notification.
|
|
4731
|
-
*/
|
|
4732
|
-
_handleToolResult(params) {
|
|
4733
|
-
this._toolOutput = params.content;
|
|
4734
|
-
this._structuredContent = params.structuredContent;
|
|
4735
|
-
this._notifyToolResult(params.content);
|
|
4736
|
-
this._emitBridgeEvent("tool:result", {
|
|
4737
|
-
content: params.content,
|
|
4738
|
-
structuredContent: params.structuredContent
|
|
4739
|
-
});
|
|
4740
|
-
}
|
|
4741
|
-
/**
|
|
4742
|
-
* Handle host context change notification.
|
|
4743
|
-
*/
|
|
4744
|
-
_handleHostContextChange(params) {
|
|
4745
|
-
const changes = {};
|
|
4746
|
-
if (params.theme !== void 0) {
|
|
4747
|
-
changes.theme = params.theme;
|
|
4748
|
-
}
|
|
4749
|
-
if (params.displayMode !== void 0) {
|
|
4750
|
-
changes.displayMode = params.displayMode;
|
|
4751
|
-
}
|
|
4752
|
-
if (params.viewport !== void 0) {
|
|
4753
|
-
changes.viewport = params.viewport;
|
|
4754
|
-
}
|
|
4755
|
-
if (params.locale !== void 0) {
|
|
4756
|
-
changes.locale = params.locale;
|
|
4757
|
-
}
|
|
4758
|
-
if (params.timezone !== void 0) {
|
|
4759
|
-
changes.timezone = params.timezone;
|
|
4760
|
-
}
|
|
4761
|
-
this._notifyContextChange(changes);
|
|
4762
|
-
}
|
|
4763
|
-
/**
|
|
4764
|
-
* Handle cancellation notification.
|
|
4765
|
-
*/
|
|
4766
|
-
_handleCancelled(params) {
|
|
4767
|
-
const reason = params?.reason;
|
|
4768
|
-
this._emitBridgeEvent("tool:cancelled", { reason });
|
|
4769
|
-
}
|
|
4770
|
-
// ============================================
|
|
4771
|
-
// Private: JSON-RPC Transport
|
|
4772
|
-
// ============================================
|
|
4773
|
-
/**
|
|
4774
|
-
* Send a JSON-RPC request to the host.
|
|
4775
|
-
*/
|
|
4776
|
-
_sendRequest(method, params) {
|
|
4777
|
-
return new Promise((resolve, reject) => {
|
|
4778
|
-
const id = ++this._requestId;
|
|
4779
|
-
const timeout = this._config.options?.initTimeout || 1e4;
|
|
4780
|
-
const request = {
|
|
4781
|
-
jsonrpc: "2.0",
|
|
4782
|
-
id,
|
|
4783
|
-
method,
|
|
4784
|
-
params
|
|
4785
|
-
};
|
|
4786
|
-
const timeoutHandle = setTimeout(() => {
|
|
4787
|
-
this._pendingRequests.delete(id);
|
|
4788
|
-
reject(new Error(`Request ${method} timed out after ${timeout}ms`));
|
|
4789
|
-
}, timeout);
|
|
4790
|
-
this._pendingRequests.set(id, {
|
|
4791
|
-
resolve,
|
|
4792
|
-
reject,
|
|
4793
|
-
timeout: timeoutHandle
|
|
4794
|
-
});
|
|
4795
|
-
this._postMessage(request);
|
|
4796
|
-
});
|
|
4797
|
-
}
|
|
4798
|
-
/**
|
|
4799
|
-
* Send a JSON-RPC notification (no response expected).
|
|
4800
|
-
*/
|
|
4801
|
-
_sendNotification(method, params) {
|
|
4802
|
-
const notification = {
|
|
4803
|
-
jsonrpc: "2.0",
|
|
4804
|
-
method,
|
|
4805
|
-
params
|
|
4806
|
-
};
|
|
4807
|
-
this._postMessage(notification);
|
|
4808
|
-
}
|
|
4809
|
-
/**
|
|
4810
|
-
* Post a message to the parent window.
|
|
4811
|
-
*/
|
|
4812
|
-
_postMessage(message) {
|
|
4813
|
-
if (typeof window === "undefined") return;
|
|
4814
|
-
const targetOrigin = this._trustedOrigin || "*";
|
|
4815
|
-
window.parent.postMessage(message, targetOrigin);
|
|
4816
|
-
}
|
|
4817
|
-
// ============================================
|
|
4818
|
-
// Private: Handshake
|
|
4819
|
-
// ============================================
|
|
4820
|
-
/**
|
|
4821
|
-
* Perform the ui/initialize handshake with the host.
|
|
4822
|
-
*/
|
|
4823
|
-
async _performHandshake() {
|
|
4824
|
-
const params = {
|
|
4825
|
-
appInfo: {
|
|
4826
|
-
name: this._config.options?.appName || "FrontMCP Widget",
|
|
4827
|
-
version: this._config.options?.appVersion || "1.0.0"
|
|
4828
|
-
},
|
|
4829
|
-
appCapabilities: {
|
|
4830
|
-
tools: {
|
|
4831
|
-
listChanged: false
|
|
4832
|
-
}
|
|
4833
|
-
},
|
|
4834
|
-
protocolVersion: this._config.options?.protocolVersion || "2024-11-05"
|
|
4835
|
-
};
|
|
4836
|
-
try {
|
|
4837
|
-
const result = await this._sendRequest("ui/initialize", params);
|
|
4838
|
-
this._hostCapabilities = result.hostCapabilities || {};
|
|
4839
|
-
this._capabilities = {
|
|
4840
|
-
...this._capabilities,
|
|
4841
|
-
canCallTools: Boolean(this._hostCapabilities.serverToolProxy),
|
|
4842
|
-
canSendMessages: true,
|
|
4843
|
-
canOpenLinks: Boolean(this._hostCapabilities.openLink),
|
|
4844
|
-
supportsDisplayModes: true
|
|
4845
|
-
};
|
|
4846
|
-
if (result.hostContext) {
|
|
4847
|
-
this._hostContext = {
|
|
4848
|
-
...this._hostContext,
|
|
4849
|
-
...result.hostContext
|
|
4850
|
-
};
|
|
4851
|
-
}
|
|
4852
|
-
if (!this._config.options?.trustedOrigins?.length) {
|
|
4853
|
-
}
|
|
4854
|
-
} catch (error) {
|
|
4855
|
-
throw new Error(`ext-apps handshake failed: ${error}`);
|
|
4856
|
-
}
|
|
4857
|
-
}
|
|
4858
|
-
// ============================================
|
|
4859
|
-
// Private: Origin Security
|
|
4860
|
-
// ============================================
|
|
4861
|
-
/**
|
|
4862
|
-
* Check if an origin is trusted.
|
|
4863
|
-
* Uses trust-on-first-use if no explicit origins configured.
|
|
4864
|
-
*/
|
|
4865
|
-
_isOriginTrusted(origin) {
|
|
4866
|
-
const trustedOrigins = this._config.options?.trustedOrigins;
|
|
4867
|
-
if (trustedOrigins && trustedOrigins.length > 0) {
|
|
4868
|
-
return trustedOrigins.includes(origin);
|
|
4869
|
-
}
|
|
4870
|
-
if (!this._trustedOrigin) {
|
|
4871
|
-
this._trustedOrigin = origin;
|
|
4872
|
-
return true;
|
|
4873
|
-
}
|
|
4874
|
-
return this._trustedOrigin === origin;
|
|
4875
|
-
}
|
|
4876
|
-
// ============================================
|
|
4877
|
-
// Private: Events
|
|
4878
|
-
// ============================================
|
|
4879
|
-
/**
|
|
4880
|
-
* Emit a bridge event via CustomEvent.
|
|
4881
|
-
*/
|
|
4882
|
-
_emitBridgeEvent(type, detail) {
|
|
4883
|
-
if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
|
|
4884
|
-
try {
|
|
4885
|
-
const event = new CustomEvent(type, { detail });
|
|
4886
|
-
window.dispatchEvent(event);
|
|
4887
|
-
} catch {
|
|
4888
|
-
}
|
|
4889
|
-
}
|
|
4890
|
-
}
|
|
4891
|
-
};
|
|
4892
|
-
function createExtAppsAdapter(config) {
|
|
4893
|
-
return new ExtAppsAdapter(config);
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
|
-
// libs/ui/src/bridge/adapters/claude.adapter.ts
|
|
4897
|
-
var ClaudeAdapter = class extends BaseAdapter {
|
|
4898
|
-
id = "claude";
|
|
4899
|
-
name = "Claude (Anthropic)";
|
|
4900
|
-
priority = 60;
|
|
4901
|
-
constructor() {
|
|
4902
|
-
super();
|
|
4903
|
-
this._capabilities = {
|
|
4904
|
-
...DEFAULT_CAPABILITIES,
|
|
4905
|
-
canCallTools: false,
|
|
4906
|
-
// Claude artifacts can't call tools
|
|
4907
|
-
canSendMessages: false,
|
|
4908
|
-
// Can't send messages back to conversation
|
|
4909
|
-
canOpenLinks: true,
|
|
4910
|
-
// Can open links via window.open
|
|
4911
|
-
canPersistState: true,
|
|
4912
|
-
// localStorage works
|
|
4913
|
-
hasNetworkAccess: false,
|
|
4914
|
-
// Network is blocked
|
|
4915
|
-
supportsDisplayModes: false,
|
|
4916
|
-
// No display mode control
|
|
4917
|
-
supportsTheme: true
|
|
4918
|
-
// Can detect system theme
|
|
4919
|
-
};
|
|
4920
|
-
}
|
|
4921
|
-
/**
|
|
4922
|
-
* Check if we're running in a Claude artifact/widget context.
|
|
4923
|
-
*/
|
|
4924
|
-
canHandle() {
|
|
4925
|
-
if (typeof window === "undefined") return false;
|
|
4926
|
-
const win = window;
|
|
4927
|
-
if (win.__mcpPlatform === "claude") return true;
|
|
4928
|
-
if (win.claude) return true;
|
|
4929
|
-
if (win.__claudeArtifact) return true;
|
|
4930
|
-
if (typeof location !== "undefined") {
|
|
4931
|
-
const href = location.href;
|
|
4932
|
-
if (href.includes("claude.ai") || href.includes("anthropic.com")) {
|
|
4933
|
-
return true;
|
|
4934
|
-
}
|
|
4935
|
-
}
|
|
4936
|
-
return false;
|
|
4937
|
-
}
|
|
4938
|
-
/**
|
|
4939
|
-
* Initialize the Claude adapter.
|
|
4940
|
-
*/
|
|
4941
|
-
async initialize() {
|
|
4942
|
-
if (this._initialized) return;
|
|
4943
|
-
await super.initialize();
|
|
4944
|
-
this._setupThemeListener();
|
|
4945
|
-
}
|
|
4946
|
-
/**
|
|
4947
|
-
* Open a link in a new tab.
|
|
4948
|
-
* This is one of the few actions available in Claude artifacts.
|
|
4949
|
-
*/
|
|
4950
|
-
async openLink(url) {
|
|
4951
|
-
if (typeof window !== "undefined") {
|
|
4952
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
4953
|
-
}
|
|
4954
|
-
}
|
|
4955
|
-
/**
|
|
4956
|
-
* Request display mode change (no-op for Claude).
|
|
4957
|
-
*/
|
|
4958
|
-
async requestDisplayMode(_mode) {
|
|
4959
|
-
}
|
|
4960
|
-
/**
|
|
4961
|
-
* Request close (no-op for Claude).
|
|
4962
|
-
*/
|
|
4963
|
-
async requestClose() {
|
|
4964
|
-
}
|
|
4965
|
-
// ============================================
|
|
4966
|
-
// Private Helpers
|
|
4967
|
-
// ============================================
|
|
4968
|
-
/**
|
|
4969
|
-
* Setup listener for system theme changes.
|
|
4970
|
-
*/
|
|
4971
|
-
_setupThemeListener() {
|
|
4972
|
-
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
4973
|
-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
4974
|
-
const handleChange = (e) => {
|
|
4975
|
-
const newTheme = e.matches ? "dark" : "light";
|
|
4976
|
-
if (newTheme !== this._hostContext.theme) {
|
|
4977
|
-
this._notifyContextChange({ theme: newTheme });
|
|
4978
|
-
}
|
|
4979
|
-
};
|
|
4980
|
-
if (mediaQuery.addEventListener) {
|
|
4981
|
-
mediaQuery.addEventListener("change", handleChange);
|
|
4982
|
-
} else if (mediaQuery.addListener) {
|
|
4983
|
-
mediaQuery.addListener(handleChange);
|
|
4984
|
-
}
|
|
4985
|
-
}
|
|
4986
|
-
};
|
|
4987
|
-
function createClaudeAdapter() {
|
|
4988
|
-
return new ClaudeAdapter();
|
|
4989
|
-
}
|
|
4990
|
-
|
|
4991
|
-
// libs/ui/src/bridge/adapters/gemini.adapter.ts
|
|
4992
|
-
var GeminiAdapter = class extends BaseAdapter {
|
|
4993
|
-
id = "gemini";
|
|
4994
|
-
name = "Google Gemini";
|
|
4995
|
-
priority = 40;
|
|
4996
|
-
_gemini;
|
|
4997
|
-
constructor() {
|
|
4998
|
-
super();
|
|
4999
|
-
this._capabilities = {
|
|
5000
|
-
...DEFAULT_CAPABILITIES,
|
|
5001
|
-
canCallTools: false,
|
|
5002
|
-
// May be enabled if SDK supports it
|
|
5003
|
-
canSendMessages: false,
|
|
5004
|
-
// May be enabled if SDK supports it
|
|
5005
|
-
canOpenLinks: true,
|
|
5006
|
-
canPersistState: true,
|
|
5007
|
-
hasNetworkAccess: true,
|
|
5008
|
-
supportsDisplayModes: false,
|
|
5009
|
-
supportsTheme: true
|
|
5010
|
-
};
|
|
5011
|
-
}
|
|
5012
|
-
/**
|
|
5013
|
-
* Check if we're running in a Gemini context.
|
|
5014
|
-
*/
|
|
5015
|
-
canHandle() {
|
|
5016
|
-
if (typeof window === "undefined") return false;
|
|
5017
|
-
const win = window;
|
|
5018
|
-
if (win.__mcpPlatform === "gemini") return true;
|
|
5019
|
-
if (win.gemini) return true;
|
|
5020
|
-
if (typeof location !== "undefined") {
|
|
5021
|
-
const href = location.href;
|
|
5022
|
-
if (href.includes("gemini.google.com") || href.includes("bard.google.com")) {
|
|
5023
|
-
return true;
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
return false;
|
|
5027
|
-
}
|
|
5028
|
-
/**
|
|
5029
|
-
* Initialize the Gemini adapter.
|
|
5030
|
-
*/
|
|
5031
|
-
async initialize() {
|
|
5032
|
-
if (this._initialized) return;
|
|
5033
|
-
const win = window;
|
|
5034
|
-
this._gemini = win.gemini;
|
|
5035
|
-
if (this._gemini?.ui) {
|
|
5036
|
-
if (this._gemini.ui.sendMessage) {
|
|
5037
|
-
this._capabilities = { ...this._capabilities, canSendMessages: true };
|
|
5038
|
-
}
|
|
5039
|
-
}
|
|
5040
|
-
await super.initialize();
|
|
5041
|
-
this._setupThemeListener();
|
|
5042
|
-
}
|
|
5043
|
-
/**
|
|
5044
|
-
* Get current theme.
|
|
5045
|
-
*/
|
|
5046
|
-
getTheme() {
|
|
5047
|
-
if (this._gemini?.ui?.getTheme) {
|
|
5048
|
-
const theme = this._gemini.ui.getTheme();
|
|
5049
|
-
return theme === "dark" ? "dark" : "light";
|
|
5050
|
-
}
|
|
5051
|
-
return super.getTheme();
|
|
5052
|
-
}
|
|
5053
|
-
/**
|
|
5054
|
-
* Send a message (if supported by SDK).
|
|
5055
|
-
*/
|
|
5056
|
-
async sendMessage(content) {
|
|
5057
|
-
if (this._gemini?.ui?.sendMessage) {
|
|
5058
|
-
await this._gemini.ui.sendMessage(content);
|
|
5059
|
-
return;
|
|
5060
|
-
}
|
|
5061
|
-
throw new Error("Sending messages is not supported by Gemini adapter");
|
|
5062
|
-
}
|
|
5063
|
-
/**
|
|
5064
|
-
* Open a link.
|
|
5065
|
-
*/
|
|
5066
|
-
async openLink(url) {
|
|
5067
|
-
if (this._gemini?.ui?.openLink) {
|
|
5068
|
-
await this._gemini.ui.openLink(url);
|
|
5069
|
-
return;
|
|
5070
|
-
}
|
|
5071
|
-
return super.openLink(url);
|
|
5072
|
-
}
|
|
5073
|
-
// ============================================
|
|
5074
|
-
// Private Helpers
|
|
5075
|
-
// ============================================
|
|
5076
|
-
/**
|
|
5077
|
-
* Setup listener for system theme changes.
|
|
5078
|
-
*/
|
|
5079
|
-
_setupThemeListener() {
|
|
5080
|
-
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
5081
|
-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
5082
|
-
const handleChange = (e) => {
|
|
5083
|
-
if (!this._gemini?.ui?.getTheme) {
|
|
5084
|
-
const newTheme = e.matches ? "dark" : "light";
|
|
5085
|
-
if (newTheme !== this._hostContext.theme) {
|
|
5086
|
-
this._notifyContextChange({ theme: newTheme });
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
};
|
|
5090
|
-
if (mediaQuery.addEventListener) {
|
|
5091
|
-
mediaQuery.addEventListener("change", handleChange);
|
|
5092
|
-
} else if (mediaQuery.addListener) {
|
|
5093
|
-
mediaQuery.addListener(handleChange);
|
|
5094
|
-
}
|
|
5095
|
-
}
|
|
5096
|
-
};
|
|
5097
|
-
function createGeminiAdapter() {
|
|
5098
|
-
return new GeminiAdapter();
|
|
5099
|
-
}
|
|
5100
|
-
|
|
5101
|
-
// libs/ui/src/bridge/adapters/generic.adapter.ts
|
|
5102
|
-
var GenericAdapter = class extends BaseAdapter {
|
|
5103
|
-
id = "generic";
|
|
5104
|
-
name = "Generic Web";
|
|
5105
|
-
priority = 0;
|
|
5106
|
-
// Lowest priority - fallback only
|
|
5107
|
-
constructor() {
|
|
5108
|
-
super();
|
|
5109
|
-
this._capabilities = {
|
|
5110
|
-
...DEFAULT_CAPABILITIES,
|
|
5111
|
-
canCallTools: false,
|
|
5112
|
-
canSendMessages: false,
|
|
5113
|
-
canOpenLinks: true,
|
|
5114
|
-
// window.open works
|
|
5115
|
-
canPersistState: true,
|
|
5116
|
-
// localStorage works
|
|
5117
|
-
hasNetworkAccess: true,
|
|
5118
|
-
// Assume network available
|
|
5119
|
-
supportsDisplayModes: false,
|
|
5120
|
-
supportsTheme: true
|
|
5121
|
-
// System theme detection
|
|
5122
|
-
};
|
|
5123
|
-
}
|
|
5124
|
-
/**
|
|
5125
|
-
* Generic adapter can always handle the environment.
|
|
5126
|
-
* It serves as the fallback when no other adapter matches.
|
|
5127
|
-
*/
|
|
5128
|
-
canHandle() {
|
|
5129
|
-
return typeof window !== "undefined";
|
|
5130
|
-
}
|
|
5131
|
-
/**
|
|
5132
|
-
* Initialize the generic adapter.
|
|
5133
|
-
*/
|
|
5134
|
-
async initialize() {
|
|
5135
|
-
if (this._initialized) return;
|
|
5136
|
-
await super.initialize();
|
|
5137
|
-
this._setupThemeListener();
|
|
5138
|
-
}
|
|
5139
|
-
/**
|
|
5140
|
-
* Open a link using window.open.
|
|
5141
|
-
*/
|
|
5142
|
-
async openLink(url) {
|
|
5143
|
-
if (typeof window !== "undefined") {
|
|
5144
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
5145
|
-
}
|
|
5146
|
-
}
|
|
5147
|
-
// ============================================
|
|
5148
|
-
// Private Helpers
|
|
5149
|
-
// ============================================
|
|
5150
|
-
/**
|
|
5151
|
-
* Setup listener for system theme changes.
|
|
5152
|
-
*/
|
|
5153
|
-
_setupThemeListener() {
|
|
5154
|
-
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
5155
|
-
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
5156
|
-
const handleChange = (e) => {
|
|
5157
|
-
const newTheme = e.matches ? "dark" : "light";
|
|
5158
|
-
if (newTheme !== this._hostContext.theme) {
|
|
5159
|
-
this._notifyContextChange({ theme: newTheme });
|
|
5160
|
-
}
|
|
5161
|
-
};
|
|
5162
|
-
if (mediaQuery.addEventListener) {
|
|
5163
|
-
mediaQuery.addEventListener("change", handleChange);
|
|
5164
|
-
} else if (mediaQuery.addListener) {
|
|
5165
|
-
mediaQuery.addListener(handleChange);
|
|
5166
|
-
}
|
|
5167
|
-
}
|
|
5168
|
-
};
|
|
5169
|
-
function createGenericAdapter() {
|
|
5170
|
-
return new GenericAdapter();
|
|
5171
|
-
}
|
|
5172
|
-
|
|
5173
|
-
// libs/ui/src/bridge/adapters/index.ts
|
|
5174
|
-
function registerBuiltInAdapters() {
|
|
5175
|
-
defaultRegistry.register("openai", createOpenAIAdapter);
|
|
5176
|
-
defaultRegistry.register("ext-apps", createExtAppsAdapter);
|
|
5177
|
-
defaultRegistry.register("claude", createClaudeAdapter);
|
|
5178
|
-
defaultRegistry.register("gemini", createGeminiAdapter);
|
|
5179
|
-
defaultRegistry.register("generic", createGenericAdapter);
|
|
5180
|
-
}
|
|
5181
|
-
registerBuiltInAdapters();
|
|
5182
|
-
|
|
5183
|
-
// libs/ui/src/bridge/runtime/iife-generator.ts
|
|
5184
|
-
function generateBridgeIIFE(options = {}) {
|
|
5185
|
-
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
5186
|
-
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
5187
|
-
const parts = [];
|
|
5188
|
-
parts.push("(function() {");
|
|
5189
|
-
parts.push('"use strict";');
|
|
5190
|
-
parts.push("");
|
|
5191
|
-
if (debug) {
|
|
5192
|
-
parts.push('function log(msg) { console.log("[FrontMcpBridge] " + msg); }');
|
|
5193
|
-
} else {
|
|
5194
|
-
parts.push("function log() {}");
|
|
5195
|
-
}
|
|
5196
|
-
parts.push("");
|
|
5197
|
-
parts.push("var DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };");
|
|
5198
|
-
parts.push("");
|
|
5199
|
-
parts.push(generateContextDetection());
|
|
5200
|
-
parts.push("");
|
|
5201
|
-
parts.push(generateBaseCapabilities());
|
|
5202
|
-
parts.push("");
|
|
5203
|
-
if (adapters.includes("openai")) {
|
|
5204
|
-
parts.push(generateOpenAIAdapter());
|
|
5205
|
-
parts.push("");
|
|
5206
|
-
}
|
|
5207
|
-
if (adapters.includes("ext-apps")) {
|
|
5208
|
-
parts.push(generateExtAppsAdapter(trustedOrigins));
|
|
5209
|
-
parts.push("");
|
|
5210
|
-
}
|
|
5211
|
-
if (adapters.includes("claude")) {
|
|
5212
|
-
parts.push(generateClaudeAdapter());
|
|
5213
|
-
parts.push("");
|
|
5214
|
-
}
|
|
5215
|
-
if (adapters.includes("gemini")) {
|
|
5216
|
-
parts.push(generateGeminiAdapter());
|
|
5217
|
-
parts.push("");
|
|
5218
|
-
}
|
|
5219
|
-
if (adapters.includes("generic")) {
|
|
5220
|
-
parts.push(generateGenericAdapter());
|
|
5221
|
-
parts.push("");
|
|
5222
|
-
}
|
|
5223
|
-
parts.push(generatePlatformDetection(adapters));
|
|
5224
|
-
parts.push("");
|
|
5225
|
-
parts.push(generateBridgeClass());
|
|
5226
|
-
parts.push("");
|
|
5227
|
-
parts.push("var bridge = new FrontMcpBridge();");
|
|
5228
|
-
parts.push("bridge.initialize().then(function() {");
|
|
5229
|
-
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
5230
|
-
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
5231
|
-
parts.push("}).catch(function(err) {");
|
|
5232
|
-
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
5233
|
-
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
5234
|
-
parts.push("});");
|
|
5235
|
-
parts.push("");
|
|
5236
|
-
parts.push("window.FrontMcpBridge = bridge;");
|
|
5237
|
-
parts.push("})();");
|
|
5238
|
-
const code = parts.join("\n");
|
|
5239
|
-
if (minify) {
|
|
5240
|
-
return minifyJS(code);
|
|
5241
|
-
}
|
|
5242
|
-
return code;
|
|
5243
|
-
}
|
|
5244
|
-
function generateContextDetection() {
|
|
5245
|
-
return `
|
|
5246
|
-
function detectTheme() {
|
|
5247
|
-
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
5248
|
-
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
5249
|
-
}
|
|
5250
|
-
return 'light';
|
|
5251
|
-
}
|
|
5252
|
-
|
|
5253
|
-
function detectLocale() {
|
|
5254
|
-
if (typeof navigator !== 'undefined') {
|
|
5255
|
-
return navigator.language || 'en-US';
|
|
5256
|
-
}
|
|
5257
|
-
return 'en-US';
|
|
5258
|
-
}
|
|
5259
|
-
|
|
5260
|
-
function detectUserAgent() {
|
|
5261
|
-
if (typeof navigator === 'undefined') {
|
|
5262
|
-
return { type: 'web', hover: true, touch: false };
|
|
5263
|
-
}
|
|
5264
|
-
var ua = navigator.userAgent || '';
|
|
5265
|
-
var isMobile = /iPhone|iPad|iPod|Android/i.test(ua);
|
|
5266
|
-
var hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
5267
|
-
var hasHover = window.matchMedia && window.matchMedia('(hover: hover)').matches;
|
|
5268
|
-
return { type: isMobile ? 'mobile' : 'web', hover: hasHover !== false, touch: hasTouch };
|
|
5269
|
-
}
|
|
5270
|
-
|
|
5271
|
-
function detectViewport() {
|
|
5272
|
-
if (typeof window !== 'undefined') {
|
|
5273
|
-
return { width: window.innerWidth, height: window.innerHeight };
|
|
5274
|
-
}
|
|
5275
|
-
return undefined;
|
|
5276
|
-
}
|
|
5277
|
-
|
|
5278
|
-
function readInjectedData() {
|
|
5279
|
-
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
5280
|
-
if (typeof window !== 'undefined') {
|
|
5281
|
-
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
5282
|
-
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
5283
|
-
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
5284
|
-
}
|
|
5285
|
-
return data;
|
|
5286
|
-
}
|
|
5287
|
-
`.trim();
|
|
5288
|
-
}
|
|
5289
|
-
function generateBaseCapabilities() {
|
|
5290
|
-
return `
|
|
5291
|
-
var DEFAULT_CAPABILITIES = {
|
|
5292
|
-
canCallTools: false,
|
|
5293
|
-
canSendMessages: false,
|
|
5294
|
-
canOpenLinks: false,
|
|
5295
|
-
canPersistState: true,
|
|
5296
|
-
hasNetworkAccess: true,
|
|
5297
|
-
supportsDisplayModes: false,
|
|
5298
|
-
supportsTheme: true
|
|
5299
|
-
};
|
|
5300
|
-
`.trim();
|
|
5301
|
-
}
|
|
5302
|
-
function generateOpenAIAdapter() {
|
|
5303
|
-
return `
|
|
5304
|
-
var OpenAIAdapter = {
|
|
5305
|
-
id: 'openai',
|
|
5306
|
-
name: 'OpenAI ChatGPT',
|
|
5307
|
-
priority: 100,
|
|
5308
|
-
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
5309
|
-
canCallTools: true,
|
|
5310
|
-
canSendMessages: true,
|
|
5311
|
-
canOpenLinks: true,
|
|
5312
|
-
supportsDisplayModes: true
|
|
5313
|
-
}),
|
|
5314
|
-
canHandle: function() {
|
|
5315
|
-
if (typeof window === 'undefined') return false;
|
|
5316
|
-
// Check for window.openai.callTool (the actual OpenAI SDK API)
|
|
5317
|
-
if (window.openai && typeof window.openai.callTool === 'function') return true;
|
|
5318
|
-
// Also check if we're being injected with tool metadata (OpenAI injects toolOutput)
|
|
5319
|
-
if (window.openai && (window.openai.toolOutput !== undefined || window.openai.toolInput !== undefined)) return true;
|
|
5320
|
-
return false;
|
|
5321
|
-
},
|
|
5322
|
-
initialize: function(context) {
|
|
5323
|
-
var sdk = window.openai;
|
|
5324
|
-
context.sdk = sdk;
|
|
5325
|
-
// OpenAI SDK exposes theme and displayMode directly as properties
|
|
5326
|
-
if (sdk.theme) {
|
|
5327
|
-
context.hostContext.theme = sdk.theme;
|
|
5328
|
-
}
|
|
5329
|
-
if (sdk.displayMode) {
|
|
5330
|
-
context.hostContext.displayMode = sdk.displayMode;
|
|
5331
|
-
}
|
|
5332
|
-
// Note: OpenAI SDK does not have an onContextChange equivalent
|
|
5333
|
-
return Promise.resolve();
|
|
5334
|
-
},
|
|
5335
|
-
callTool: function(context, name, args) {
|
|
5336
|
-
return context.sdk.callTool(name, args);
|
|
5337
|
-
},
|
|
5338
|
-
sendMessage: function(context, content) {
|
|
5339
|
-
if (typeof context.sdk.sendFollowUpMessage === 'function') {
|
|
5340
|
-
return context.sdk.sendFollowUpMessage(content);
|
|
5341
|
-
}
|
|
5342
|
-
return Promise.reject(new Error('Messages not supported'));
|
|
5343
|
-
},
|
|
5344
|
-
openLink: function(context, url) {
|
|
5345
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
5346
|
-
return Promise.resolve();
|
|
5347
|
-
},
|
|
5348
|
-
requestDisplayMode: function(context, mode) {
|
|
5349
|
-
return Promise.resolve();
|
|
5350
|
-
},
|
|
5351
|
-
requestClose: function(context) {
|
|
5352
|
-
return Promise.resolve();
|
|
5353
|
-
}
|
|
5354
|
-
};
|
|
5355
|
-
`.trim();
|
|
5356
|
-
}
|
|
5357
|
-
function generateExtAppsAdapter(trustedOrigins) {
|
|
5358
|
-
const originsArray = trustedOrigins.length > 0 ? JSON.stringify(trustedOrigins) : "[]";
|
|
5359
|
-
return `
|
|
5360
|
-
var ExtAppsAdapter = {
|
|
5361
|
-
id: 'ext-apps',
|
|
5362
|
-
name: 'ext-apps (SEP-1865)',
|
|
5363
|
-
priority: 80,
|
|
5364
|
-
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
|
|
5365
|
-
trustedOrigins: ${originsArray},
|
|
5366
|
-
trustedOrigin: null,
|
|
5367
|
-
pendingRequests: {},
|
|
5368
|
-
requestId: 0,
|
|
5369
|
-
hostCapabilities: {},
|
|
5370
|
-
canHandle: function() {
|
|
5371
|
-
if (typeof window === 'undefined') return false;
|
|
5372
|
-
if (window.parent === window) return false;
|
|
5373
|
-
// Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
|
|
5374
|
-
if (window.openai && typeof window.openai.callTool === 'function') return false;
|
|
5375
|
-
if (window.__mcpPlatform === 'ext-apps') return true;
|
|
5376
|
-
return true;
|
|
5377
|
-
},
|
|
5378
|
-
initialize: function(context) {
|
|
5379
|
-
var self = this;
|
|
5380
|
-
context.extApps = this;
|
|
5381
|
-
|
|
5382
|
-
window.addEventListener('message', function(event) {
|
|
5383
|
-
self.handleMessage(context, event);
|
|
5384
|
-
});
|
|
5385
|
-
|
|
5386
|
-
return self.performHandshake(context);
|
|
5387
|
-
},
|
|
5388
|
-
handleMessage: function(context, event) {
|
|
5389
|
-
if (!this.isOriginTrusted(event.origin)) return;
|
|
5390
|
-
var data = event.data;
|
|
5391
|
-
if (!data || typeof data !== 'object' || data.jsonrpc !== '2.0') return;
|
|
5392
|
-
|
|
5393
|
-
if ('id' in data && (data.result !== undefined || data.error !== undefined)) {
|
|
5394
|
-
var pending = this.pendingRequests[data.id];
|
|
5395
|
-
if (pending) {
|
|
5396
|
-
clearTimeout(pending.timeout);
|
|
5397
|
-
delete this.pendingRequests[data.id];
|
|
5398
|
-
if (data.error) {
|
|
5399
|
-
pending.reject(new Error(data.error.message + ' (code: ' + data.error.code + ')'));
|
|
5400
|
-
} else {
|
|
5401
|
-
pending.resolve(data.result);
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5404
|
-
return;
|
|
5405
|
-
}
|
|
5406
|
-
|
|
5407
|
-
if ('method' in data && !('id' in data)) {
|
|
5408
|
-
this.handleNotification(context, data);
|
|
5409
|
-
}
|
|
5410
|
-
},
|
|
5411
|
-
handleNotification: function(context, notification) {
|
|
5412
|
-
var params = notification.params || {};
|
|
5413
|
-
switch (notification.method) {
|
|
5414
|
-
case 'ui/notifications/tool-input':
|
|
5415
|
-
context.toolInput = params.arguments || {};
|
|
5416
|
-
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
5417
|
-
break;
|
|
5418
|
-
case 'ui/notifications/tool-result':
|
|
5419
|
-
context.toolOutput = params.content;
|
|
5420
|
-
context.structuredContent = params.structuredContent;
|
|
5421
|
-
context.notifyToolResult(params.content);
|
|
5422
|
-
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
5423
|
-
break;
|
|
5424
|
-
case 'ui/notifications/host-context-changed':
|
|
5425
|
-
Object.assign(context.hostContext, params);
|
|
5426
|
-
context.notifyContextChange(params);
|
|
5427
|
-
break;
|
|
5428
|
-
}
|
|
5429
|
-
},
|
|
5430
|
-
isOriginTrusted: function(origin) {
|
|
5431
|
-
if (this.trustedOrigins.length > 0) {
|
|
5432
|
-
return this.trustedOrigins.indexOf(origin) !== -1;
|
|
5433
|
-
}
|
|
5434
|
-
// When no trusted origins configured, only trust first message in iframe context
|
|
5435
|
-
// This helps mitigate race conditions where a malicious iframe could establish trust
|
|
5436
|
-
if (!this.trustedOrigin) {
|
|
5437
|
-
if (window.parent !== window && origin) {
|
|
5438
|
-
this.trustedOrigin = origin;
|
|
5439
|
-
return true;
|
|
5440
|
-
}
|
|
5441
|
-
return false;
|
|
5442
|
-
}
|
|
5443
|
-
return this.trustedOrigin === origin;
|
|
5444
|
-
},
|
|
5445
|
-
sendRequest: function(method, params) {
|
|
5446
|
-
var self = this;
|
|
5447
|
-
return new Promise(function(resolve, reject) {
|
|
5448
|
-
// Security: Require trusted origin before sending requests to prevent message leaks
|
|
5449
|
-
if (!self.trustedOrigin && self.trustedOrigins.length === 0) {
|
|
5450
|
-
reject(new Error('Cannot send request: no trusted origin established'));
|
|
5451
|
-
return;
|
|
5452
|
-
}
|
|
5453
|
-
|
|
5454
|
-
var id = ++self.requestId;
|
|
5455
|
-
var timeout = setTimeout(function() {
|
|
5456
|
-
delete self.pendingRequests[id];
|
|
5457
|
-
reject(new Error('Request ' + method + ' timed out'));
|
|
5458
|
-
}, 10000);
|
|
5459
|
-
|
|
5460
|
-
self.pendingRequests[id] = { resolve: resolve, reject: reject, timeout: timeout };
|
|
5461
|
-
|
|
5462
|
-
var targetOrigin = self.trustedOrigin || self.trustedOrigins[0];
|
|
5463
|
-
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
5464
|
-
});
|
|
5465
|
-
},
|
|
5466
|
-
performHandshake: function(context) {
|
|
5467
|
-
var self = this;
|
|
5468
|
-
var params = {
|
|
5469
|
-
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
5470
|
-
appCapabilities: { tools: { listChanged: false } },
|
|
5471
|
-
protocolVersion: '2024-11-05'
|
|
5472
|
-
};
|
|
5473
|
-
|
|
5474
|
-
return this.sendRequest('ui/initialize', params).then(function(result) {
|
|
5475
|
-
self.hostCapabilities = result.hostCapabilities || {};
|
|
5476
|
-
self.capabilities = Object.assign({}, self.capabilities, {
|
|
5477
|
-
canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
|
|
5478
|
-
canSendMessages: true,
|
|
5479
|
-
canOpenLinks: Boolean(self.hostCapabilities.openLink),
|
|
5480
|
-
supportsDisplayModes: true
|
|
5481
|
-
});
|
|
5482
|
-
if (result.hostContext) {
|
|
5483
|
-
Object.assign(context.hostContext, result.hostContext);
|
|
5484
|
-
}
|
|
5485
|
-
});
|
|
5486
|
-
},
|
|
5487
|
-
callTool: function(context, name, args) {
|
|
5488
|
-
if (!this.hostCapabilities.serverToolProxy) {
|
|
5489
|
-
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
5490
|
-
}
|
|
5491
|
-
return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
|
|
5492
|
-
},
|
|
5493
|
-
sendMessage: function(context, content) {
|
|
5494
|
-
return this.sendRequest('ui/message', { content: content });
|
|
5495
|
-
},
|
|
5496
|
-
openLink: function(context, url) {
|
|
5497
|
-
if (!this.hostCapabilities.openLink) {
|
|
5498
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
5499
|
-
return Promise.resolve();
|
|
5500
|
-
}
|
|
5501
|
-
return this.sendRequest('ui/openLink', { url: url });
|
|
5502
|
-
},
|
|
5503
|
-
requestDisplayMode: function(context, mode) {
|
|
5504
|
-
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
5505
|
-
},
|
|
5506
|
-
requestClose: function(context) {
|
|
5507
|
-
return this.sendRequest('ui/close', {});
|
|
5508
|
-
}
|
|
5509
|
-
};
|
|
5510
|
-
`.trim();
|
|
5511
|
-
}
|
|
5512
|
-
function generateClaudeAdapter() {
|
|
5513
|
-
return `
|
|
5514
|
-
var ClaudeAdapter = {
|
|
5515
|
-
id: 'claude',
|
|
5516
|
-
name: 'Claude (Anthropic)',
|
|
5517
|
-
priority: 60,
|
|
5518
|
-
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
5519
|
-
canCallTools: false,
|
|
5520
|
-
canSendMessages: false,
|
|
5521
|
-
canOpenLinks: true,
|
|
5522
|
-
hasNetworkAccess: false,
|
|
5523
|
-
supportsDisplayModes: false
|
|
5524
|
-
}),
|
|
5525
|
-
canHandle: function() {
|
|
5526
|
-
if (typeof window === 'undefined') return false;
|
|
5527
|
-
if (window.__mcpPlatform === 'claude') return true;
|
|
5528
|
-
if (window.claude) return true;
|
|
5529
|
-
if (window.__claudeArtifact) return true;
|
|
5530
|
-
if (typeof location !== 'undefined') {
|
|
5531
|
-
var href = location.href;
|
|
5532
|
-
if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
|
|
5533
|
-
}
|
|
5534
|
-
return false;
|
|
5535
|
-
},
|
|
5536
|
-
initialize: function(context) {
|
|
5537
|
-
return Promise.resolve();
|
|
5538
|
-
},
|
|
5539
|
-
callTool: function() {
|
|
5540
|
-
return Promise.reject(new Error('Tool calls not supported in Claude'));
|
|
5541
|
-
},
|
|
5542
|
-
sendMessage: function() {
|
|
5543
|
-
return Promise.reject(new Error('Messages not supported in Claude'));
|
|
5544
|
-
},
|
|
5545
|
-
openLink: function(context, url) {
|
|
5546
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
5547
|
-
return Promise.resolve();
|
|
5548
|
-
},
|
|
5549
|
-
requestDisplayMode: function() {
|
|
5550
|
-
return Promise.resolve();
|
|
5551
|
-
},
|
|
5552
|
-
requestClose: function() {
|
|
5553
|
-
return Promise.resolve();
|
|
5554
|
-
}
|
|
5555
|
-
};
|
|
5556
|
-
`.trim();
|
|
5557
|
-
}
|
|
5558
|
-
function generateGeminiAdapter() {
|
|
5559
|
-
return `
|
|
5560
|
-
var GeminiAdapter = {
|
|
5561
|
-
id: 'gemini',
|
|
5562
|
-
name: 'Google Gemini',
|
|
5563
|
-
priority: 40,
|
|
5564
|
-
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
5565
|
-
canOpenLinks: true,
|
|
5566
|
-
hasNetworkAccess: true
|
|
5567
|
-
}),
|
|
5568
|
-
canHandle: function() {
|
|
5569
|
-
if (typeof window === 'undefined') return false;
|
|
5570
|
-
if (window.__mcpPlatform === 'gemini') return true;
|
|
5571
|
-
if (window.gemini) return true;
|
|
5572
|
-
if (typeof location !== 'undefined') {
|
|
5573
|
-
var href = location.href;
|
|
5574
|
-
if (href.indexOf('gemini.google.com') !== -1 || href.indexOf('bard.google.com') !== -1) return true;
|
|
5575
|
-
}
|
|
5576
|
-
return false;
|
|
5577
|
-
},
|
|
5578
|
-
initialize: function(context) {
|
|
5579
|
-
if (window.gemini && window.gemini.ui && window.gemini.ui.getTheme) {
|
|
5580
|
-
context.hostContext.theme = window.gemini.ui.getTheme() === 'dark' ? 'dark' : 'light';
|
|
5581
|
-
}
|
|
5582
|
-
return Promise.resolve();
|
|
5583
|
-
},
|
|
5584
|
-
callTool: function() {
|
|
5585
|
-
return Promise.reject(new Error('Tool calls not supported in Gemini'));
|
|
5586
|
-
},
|
|
5587
|
-
sendMessage: function(context, content) {
|
|
5588
|
-
if (window.gemini && window.gemini.ui && window.gemini.ui.sendMessage) {
|
|
5589
|
-
return window.gemini.ui.sendMessage(content);
|
|
5590
|
-
}
|
|
5591
|
-
return Promise.reject(new Error('Messages not supported in Gemini'));
|
|
5592
|
-
},
|
|
5593
|
-
openLink: function(context, url) {
|
|
5594
|
-
if (window.gemini && window.gemini.ui && window.gemini.ui.openLink) {
|
|
5595
|
-
return window.gemini.ui.openLink(url);
|
|
5596
|
-
}
|
|
5597
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
5598
|
-
return Promise.resolve();
|
|
5599
|
-
},
|
|
5600
|
-
requestDisplayMode: function() {
|
|
5601
|
-
return Promise.resolve();
|
|
5602
|
-
},
|
|
5603
|
-
requestClose: function() {
|
|
5604
|
-
return Promise.resolve();
|
|
3138
|
+
// Events (delegate to adapter)
|
|
3139
|
+
// ============================================
|
|
3140
|
+
/**
|
|
3141
|
+
* Subscribe to host context changes.
|
|
3142
|
+
* @param callback - Called when context changes
|
|
3143
|
+
* @returns Unsubscribe function
|
|
3144
|
+
*/
|
|
3145
|
+
onContextChange(callback) {
|
|
3146
|
+
const adapter = this._ensureInitialized();
|
|
3147
|
+
return adapter.onContextChange(callback);
|
|
5605
3148
|
}
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
priority: 0,
|
|
5615
|
-
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, {
|
|
5616
|
-
canOpenLinks: true,
|
|
5617
|
-
hasNetworkAccess: true
|
|
5618
|
-
}),
|
|
5619
|
-
canHandle: function() {
|
|
5620
|
-
return typeof window !== 'undefined';
|
|
5621
|
-
},
|
|
5622
|
-
initialize: function(context) {
|
|
5623
|
-
return Promise.resolve();
|
|
5624
|
-
},
|
|
5625
|
-
callTool: function() {
|
|
5626
|
-
return Promise.reject(new Error('Tool calls not supported'));
|
|
5627
|
-
},
|
|
5628
|
-
sendMessage: function() {
|
|
5629
|
-
return Promise.reject(new Error('Messages not supported'));
|
|
5630
|
-
},
|
|
5631
|
-
openLink: function(context, url) {
|
|
5632
|
-
window.open(url, '_blank', 'noopener,noreferrer');
|
|
5633
|
-
return Promise.resolve();
|
|
5634
|
-
},
|
|
5635
|
-
requestDisplayMode: function() {
|
|
5636
|
-
return Promise.resolve();
|
|
5637
|
-
},
|
|
5638
|
-
requestClose: function() {
|
|
5639
|
-
return Promise.resolve();
|
|
3149
|
+
/**
|
|
3150
|
+
* Subscribe to tool result updates.
|
|
3151
|
+
* @param callback - Called when tool result is received
|
|
3152
|
+
* @returns Unsubscribe function
|
|
3153
|
+
*/
|
|
3154
|
+
onToolResult(callback) {
|
|
3155
|
+
const adapter = this._ensureInitialized();
|
|
3156
|
+
return adapter.onToolResult(callback);
|
|
5640
3157
|
}
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
case "claude":
|
|
5652
|
-
return "ClaudeAdapter";
|
|
5653
|
-
case "gemini":
|
|
5654
|
-
return "GeminiAdapter";
|
|
5655
|
-
case "generic":
|
|
5656
|
-
return "GenericAdapter";
|
|
5657
|
-
default:
|
|
5658
|
-
return "";
|
|
5659
|
-
}
|
|
5660
|
-
}).filter(Boolean);
|
|
5661
|
-
return `
|
|
5662
|
-
var ADAPTERS = [${adapterVars.join(", ")}].sort(function(a, b) { return b.priority - a.priority; });
|
|
5663
|
-
|
|
5664
|
-
function detectPlatform() {
|
|
5665
|
-
for (var i = 0; i < ADAPTERS.length; i++) {
|
|
5666
|
-
if (ADAPTERS[i].canHandle()) {
|
|
5667
|
-
log('Detected platform: ' + ADAPTERS[i].id);
|
|
5668
|
-
return ADAPTERS[i];
|
|
3158
|
+
// ============================================
|
|
3159
|
+
// Private Helpers
|
|
3160
|
+
// ============================================
|
|
3161
|
+
/**
|
|
3162
|
+
* Ensure the bridge is initialized before operations.
|
|
3163
|
+
* Returns the adapter for type-safe access.
|
|
3164
|
+
*/
|
|
3165
|
+
_ensureInitialized() {
|
|
3166
|
+
if (!this._initialized || !this._adapter) {
|
|
3167
|
+
throw new Error("FrontMcpBridge is not initialized. Call initialize() first.");
|
|
5669
3168
|
}
|
|
3169
|
+
return this._adapter;
|
|
5670
3170
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
Object.assign(this.hostContext, changes);
|
|
5698
|
-
for (var i = 0; i < this.contextListeners.length; i++) {
|
|
5699
|
-
try { this.contextListeners[i](changes); } catch(e) {}
|
|
5700
|
-
}
|
|
5701
|
-
},
|
|
5702
|
-
notifyToolResult: function(result) {
|
|
5703
|
-
this.toolOutput = result;
|
|
5704
|
-
for (var i = 0; i < this.toolResultListeners.length; i++) {
|
|
5705
|
-
try { this.toolResultListeners[i](result); } catch(e) {}
|
|
3171
|
+
/**
|
|
3172
|
+
* Wrap a promise with a timeout.
|
|
3173
|
+
*/
|
|
3174
|
+
_withTimeout(promise, ms) {
|
|
3175
|
+
return new Promise((resolve, reject) => {
|
|
3176
|
+
const timer = setTimeout(() => {
|
|
3177
|
+
reject(new Error(`Operation timed out after ${ms}ms`));
|
|
3178
|
+
}, ms);
|
|
3179
|
+
promise.then((result) => {
|
|
3180
|
+
clearTimeout(timer);
|
|
3181
|
+
resolve(result);
|
|
3182
|
+
}).catch((error) => {
|
|
3183
|
+
clearTimeout(timer);
|
|
3184
|
+
reject(error);
|
|
3185
|
+
});
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
/**
|
|
3189
|
+
* Emit a bridge event via CustomEvent.
|
|
3190
|
+
*/
|
|
3191
|
+
_emitEvent(type, payload) {
|
|
3192
|
+
if (typeof window !== "undefined" && typeof CustomEvent !== "undefined") {
|
|
3193
|
+
try {
|
|
3194
|
+
const event = new CustomEvent(type, { detail: payload });
|
|
3195
|
+
window.dispatchEvent(event);
|
|
3196
|
+
} catch {
|
|
5706
3197
|
}
|
|
5707
3198
|
}
|
|
5708
|
-
};
|
|
5709
|
-
|
|
5710
|
-
var injected = readInjectedData();
|
|
5711
|
-
this._context.toolInput = injected.toolInput;
|
|
5712
|
-
this._context.toolOutput = injected.toolOutput;
|
|
5713
|
-
this._context.structuredContent = injected.structuredContent;
|
|
5714
|
-
|
|
5715
|
-
this._loadWidgetState();
|
|
5716
|
-
}
|
|
5717
|
-
|
|
5718
|
-
FrontMcpBridge.prototype._loadWidgetState = function() {
|
|
5719
|
-
try {
|
|
5720
|
-
var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
|
|
5721
|
-
var stored = localStorage.getItem(key);
|
|
5722
|
-
if (stored) this._context.widgetState = JSON.parse(stored);
|
|
5723
|
-
} catch(e) {}
|
|
5724
|
-
};
|
|
5725
|
-
|
|
5726
|
-
FrontMcpBridge.prototype._saveWidgetState = function() {
|
|
5727
|
-
try {
|
|
5728
|
-
var key = 'frontmcp:widget:' + (window.__mcpToolName || 'unknown');
|
|
5729
|
-
localStorage.setItem(key, JSON.stringify(this._context.widgetState));
|
|
5730
|
-
} catch(e) {}
|
|
5731
|
-
};
|
|
5732
|
-
|
|
5733
|
-
FrontMcpBridge.prototype.initialize = function() {
|
|
5734
|
-
if (this._initialized) return Promise.resolve();
|
|
5735
|
-
var self = this;
|
|
5736
|
-
this._adapter = detectPlatform();
|
|
5737
|
-
return this._adapter.initialize(this._context).then(function() {
|
|
5738
|
-
self._initialized = true;
|
|
5739
|
-
// Set up the data-tool-call click handler after initialization
|
|
5740
|
-
self._setupDataToolCallHandler();
|
|
5741
|
-
});
|
|
5742
|
-
};
|
|
5743
|
-
|
|
5744
|
-
Object.defineProperty(FrontMcpBridge.prototype, 'initialized', { get: function() { return this._initialized; } });
|
|
5745
|
-
Object.defineProperty(FrontMcpBridge.prototype, 'adapterId', { get: function() { return this._adapter ? this._adapter.id : undefined; } });
|
|
5746
|
-
Object.defineProperty(FrontMcpBridge.prototype, 'capabilities', { get: function() { return this._adapter ? this._adapter.capabilities : DEFAULT_CAPABILITIES; } });
|
|
5747
|
-
|
|
5748
|
-
FrontMcpBridge.prototype.getTheme = function() { return this._context.hostContext.theme; };
|
|
5749
|
-
FrontMcpBridge.prototype.getDisplayMode = function() { return this._context.hostContext.displayMode; };
|
|
5750
|
-
FrontMcpBridge.prototype.getToolInput = function() { return this._context.toolInput; };
|
|
5751
|
-
FrontMcpBridge.prototype.getToolOutput = function() { return this._context.toolOutput; };
|
|
5752
|
-
FrontMcpBridge.prototype.getStructuredContent = function() { return this._context.structuredContent; };
|
|
5753
|
-
FrontMcpBridge.prototype.getWidgetState = function() { return this._context.widgetState; };
|
|
5754
|
-
FrontMcpBridge.prototype.getHostContext = function() { return Object.assign({}, this._context.hostContext); };
|
|
5755
|
-
FrontMcpBridge.prototype.hasCapability = function(cap) { return this._adapter && this._adapter.capabilities[cap] === true; };
|
|
5756
|
-
|
|
5757
|
-
// Get tool response metadata (platform-agnostic)
|
|
5758
|
-
// Used by inline mode widgets to detect when ui/html arrives
|
|
5759
|
-
FrontMcpBridge.prototype.getToolResponseMetadata = function() {
|
|
5760
|
-
// OpenAI injects toolResponseMetadata for widget-producing tools
|
|
5761
|
-
if (typeof window !== 'undefined' && window.openai && window.openai.toolResponseMetadata) {
|
|
5762
|
-
return window.openai.toolResponseMetadata;
|
|
5763
|
-
}
|
|
5764
|
-
// Claude (future support)
|
|
5765
|
-
if (typeof window !== 'undefined' && window.claude && window.claude.toolResponseMetadata) {
|
|
5766
|
-
return window.claude.toolResponseMetadata;
|
|
5767
|
-
}
|
|
5768
|
-
// FrontMCP direct injection (for testing/ext-apps)
|
|
5769
|
-
if (typeof window !== 'undefined' && window.__mcpToolResponseMetadata) {
|
|
5770
|
-
return window.__mcpToolResponseMetadata;
|
|
5771
|
-
}
|
|
5772
|
-
return null;
|
|
5773
|
-
};
|
|
5774
|
-
|
|
5775
|
-
// Subscribe to tool response metadata changes (for inline mode injection)
|
|
5776
|
-
FrontMcpBridge.prototype.onToolResponseMetadata = function(callback) {
|
|
5777
|
-
var self = this;
|
|
5778
|
-
var called = false;
|
|
5779
|
-
|
|
5780
|
-
// Check if already available
|
|
5781
|
-
var existing = self.getToolResponseMetadata();
|
|
5782
|
-
if (existing) {
|
|
5783
|
-
called = true;
|
|
5784
|
-
callback(existing);
|
|
5785
3199
|
}
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
if (
|
|
5791
|
-
|
|
5792
|
-
window.__frontmcpMetadataCallbacks = [];
|
|
5793
|
-
|
|
5794
|
-
// Create openai object if it doesn't exist
|
|
5795
|
-
if (!window.openai) window.openai = {};
|
|
5796
|
-
|
|
5797
|
-
var originalMetadata = window.openai.toolResponseMetadata;
|
|
5798
|
-
Object.defineProperty(window.openai, 'toolResponseMetadata', {
|
|
5799
|
-
get: function() { return originalMetadata; },
|
|
5800
|
-
set: function(val) {
|
|
5801
|
-
originalMetadata = val;
|
|
5802
|
-
log('toolResponseMetadata set, notifying ' + window.__frontmcpMetadataCallbacks.length + ' listeners');
|
|
5803
|
-
for (var i = 0; i < window.__frontmcpMetadataCallbacks.length; i++) {
|
|
5804
|
-
try { window.__frontmcpMetadataCallbacks[i](val); } catch(e) {}
|
|
5805
|
-
}
|
|
5806
|
-
},
|
|
5807
|
-
configurable: true
|
|
5808
|
-
});
|
|
3200
|
+
/**
|
|
3201
|
+
* Log debug message if debugging is enabled.
|
|
3202
|
+
*/
|
|
3203
|
+
_log(message) {
|
|
3204
|
+
if (this._config.debug) {
|
|
3205
|
+
console.log(`[FrontMcpBridge] ${message}`);
|
|
5809
3206
|
}
|
|
5810
|
-
|
|
5811
|
-
// Register callback wrapper (store reference for unsubscribe)
|
|
5812
|
-
var wrapper = function(metadata) {
|
|
5813
|
-
if (!called) {
|
|
5814
|
-
called = true;
|
|
5815
|
-
callback(metadata);
|
|
5816
|
-
}
|
|
5817
|
-
};
|
|
5818
|
-
window.__frontmcpMetadataCallbacks.push(wrapper);
|
|
5819
|
-
|
|
5820
|
-
// Return unsubscribe function that removes the wrapper (not the original callback)
|
|
5821
|
-
return function() {
|
|
5822
|
-
if (window.__frontmcpMetadataCallbacks) {
|
|
5823
|
-
var idx = window.__frontmcpMetadataCallbacks.indexOf(wrapper);
|
|
5824
|
-
if (idx !== -1) window.__frontmcpMetadataCallbacks.splice(idx, 1);
|
|
5825
|
-
}
|
|
5826
|
-
};
|
|
5827
|
-
}
|
|
5828
|
-
|
|
5829
|
-
// Return no-op unsubscribe for non-window environments
|
|
5830
|
-
return function() {};
|
|
5831
|
-
};
|
|
5832
|
-
|
|
5833
|
-
FrontMcpBridge.prototype.callTool = function(name, args) {
|
|
5834
|
-
// Priority 1: Direct OpenAI SDK call (most reliable in OpenAI iframe)
|
|
5835
|
-
// This bypasses adapter abstraction for maximum compatibility
|
|
5836
|
-
if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
|
|
5837
|
-
log('callTool: Using OpenAI SDK directly');
|
|
5838
|
-
return window.openai.callTool(name, args);
|
|
5839
3207
|
}
|
|
5840
|
-
|
|
5841
|
-
// Priority 2: Use adapter (if initialized and supports tool calls)
|
|
5842
|
-
if (this._adapter && this._adapter.capabilities && this._adapter.capabilities.canCallTools) {
|
|
5843
|
-
log('callTool: Using adapter ' + this._adapter.id);
|
|
5844
|
-
return this._adapter.callTool(this._context, name, args);
|
|
5845
|
-
}
|
|
5846
|
-
|
|
5847
|
-
// Not initialized or no tool support
|
|
5848
|
-
if (!this._adapter) {
|
|
5849
|
-
return Promise.reject(new Error('Bridge not initialized. Wait for bridge:ready event.'));
|
|
5850
|
-
}
|
|
5851
|
-
return Promise.reject(new Error('Tool calls not supported on this platform (' + this._adapter.id + ')'));
|
|
5852
|
-
};
|
|
5853
|
-
|
|
5854
|
-
FrontMcpBridge.prototype.sendMessage = function(content) {
|
|
5855
|
-
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
5856
|
-
return this._adapter.sendMessage(this._context, content);
|
|
5857
|
-
};
|
|
5858
|
-
|
|
5859
|
-
FrontMcpBridge.prototype.openLink = function(url) {
|
|
5860
|
-
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
5861
|
-
return this._adapter.openLink(this._context, url);
|
|
5862
|
-
};
|
|
5863
|
-
|
|
5864
|
-
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
5865
|
-
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
5866
|
-
var self = this;
|
|
5867
|
-
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
5868
|
-
self._context.hostContext.displayMode = mode;
|
|
5869
|
-
});
|
|
5870
|
-
};
|
|
5871
|
-
|
|
5872
|
-
FrontMcpBridge.prototype.requestClose = function() {
|
|
5873
|
-
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
5874
|
-
return this._adapter.requestClose(this._context);
|
|
5875
|
-
};
|
|
5876
|
-
|
|
5877
|
-
FrontMcpBridge.prototype.setWidgetState = function(state) {
|
|
5878
|
-
Object.assign(this._context.widgetState, state);
|
|
5879
|
-
this._saveWidgetState();
|
|
5880
|
-
};
|
|
5881
|
-
|
|
5882
|
-
FrontMcpBridge.prototype.onContextChange = function(callback) {
|
|
5883
|
-
var listeners = this._context.contextListeners;
|
|
5884
|
-
listeners.push(callback);
|
|
5885
|
-
return function() {
|
|
5886
|
-
var idx = listeners.indexOf(callback);
|
|
5887
|
-
if (idx !== -1) listeners.splice(idx, 1);
|
|
5888
|
-
};
|
|
5889
|
-
};
|
|
5890
|
-
|
|
5891
|
-
FrontMcpBridge.prototype.onToolResult = function(callback) {
|
|
5892
|
-
var listeners = this._context.toolResultListeners;
|
|
5893
|
-
listeners.push(callback);
|
|
5894
|
-
return function() {
|
|
5895
|
-
var idx = listeners.indexOf(callback);
|
|
5896
|
-
if (idx !== -1) listeners.splice(idx, 1);
|
|
5897
|
-
};
|
|
5898
|
-
};
|
|
5899
|
-
|
|
5900
|
-
// ==================== data-tool-call Click Handler ====================
|
|
5901
|
-
|
|
5902
|
-
FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
5903
|
-
var self = this;
|
|
5904
|
-
|
|
5905
|
-
document.addEventListener('click', function(e) {
|
|
5906
|
-
// Find the closest element with data-tool-call attribute
|
|
5907
|
-
var target = e.target;
|
|
5908
|
-
while (target && target !== document) {
|
|
5909
|
-
if (target.hasAttribute && target.hasAttribute('data-tool-call')) {
|
|
5910
|
-
var toolName = target.getAttribute('data-tool-call');
|
|
5911
|
-
var argsAttr = target.getAttribute('data-tool-args');
|
|
5912
|
-
var args = {};
|
|
5913
|
-
|
|
5914
|
-
try {
|
|
5915
|
-
if (argsAttr) {
|
|
5916
|
-
args = JSON.parse(argsAttr);
|
|
5917
|
-
}
|
|
5918
|
-
} catch (parseErr) {
|
|
5919
|
-
console.error('[frontmcp] Failed to parse data-tool-args:', parseErr);
|
|
5920
|
-
}
|
|
5921
|
-
|
|
5922
|
-
log('data-tool-call clicked: ' + toolName);
|
|
5923
|
-
|
|
5924
|
-
// Show loading state - save original content first
|
|
5925
|
-
var originalContent = target.innerHTML;
|
|
5926
|
-
var originalDisabled = target.disabled;
|
|
5927
|
-
target.disabled = true;
|
|
5928
|
-
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
5929
|
-
|
|
5930
|
-
// Add spinner for buttons
|
|
5931
|
-
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
5932
|
-
if (target.tagName === 'BUTTON') {
|
|
5933
|
-
target.innerHTML = spinner + 'Loading...';
|
|
5934
|
-
}
|
|
5935
|
-
|
|
5936
|
-
// Helper to reset button state
|
|
5937
|
-
function resetButton() {
|
|
5938
|
-
target.innerHTML = originalContent;
|
|
5939
|
-
target.disabled = originalDisabled;
|
|
5940
|
-
target.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
5941
|
-
}
|
|
5942
|
-
|
|
5943
|
-
// Determine how to call the tool
|
|
5944
|
-
var toolCallPromise;
|
|
5945
|
-
|
|
5946
|
-
// Priority 1: Direct OpenAI SDK call (bypasses adapter abstraction)
|
|
5947
|
-
if (typeof window !== 'undefined' && window.openai && typeof window.openai.callTool === 'function') {
|
|
5948
|
-
log('Using OpenAI SDK directly for tool call');
|
|
5949
|
-
toolCallPromise = window.openai.callTool(toolName, args);
|
|
5950
|
-
}
|
|
5951
|
-
// Priority 2: Use adapter (if it supports tool calls)
|
|
5952
|
-
else if (self.hasCapability('canCallTools')) {
|
|
5953
|
-
log('Using adapter for tool call');
|
|
5954
|
-
toolCallPromise = self.callTool(toolName, args);
|
|
5955
|
-
}
|
|
5956
|
-
// No tool call capability
|
|
5957
|
-
else {
|
|
5958
|
-
console.error('[frontmcp] Tool calls not supported on this platform (' + self.adapterId + ')');
|
|
5959
|
-
resetButton();
|
|
5960
|
-
target.dispatchEvent(new CustomEvent('tool:error', {
|
|
5961
|
-
detail: { name: toolName, args: args, error: 'Tool calls not supported on this platform' },
|
|
5962
|
-
bubbles: true
|
|
5963
|
-
}));
|
|
5964
|
-
e.preventDefault();
|
|
5965
|
-
return;
|
|
5966
|
-
}
|
|
5967
|
-
|
|
5968
|
-
// Handle the tool call result
|
|
5969
|
-
toolCallPromise.then(function(result) {
|
|
5970
|
-
log('Tool call succeeded: ' + toolName);
|
|
5971
|
-
resetButton();
|
|
5972
|
-
|
|
5973
|
-
// Update bridge state to trigger widget re-render
|
|
5974
|
-
// React isn't hydrated in OpenAI iframe, so useState doesn't work
|
|
5975
|
-
// Instead, we use the bridge's reactive state system
|
|
5976
|
-
if (result && window.__frontmcp && window.__frontmcp.bridge && typeof window.__frontmcp.bridge.setWidgetState === 'function') {
|
|
5977
|
-
var newData = result.structuredContent || result;
|
|
5978
|
-
log('Updating bridge state with new data');
|
|
5979
|
-
window.__frontmcp.bridge.setWidgetState(newData);
|
|
5980
|
-
}
|
|
5981
|
-
|
|
5982
|
-
// Dispatch success event
|
|
5983
|
-
target.dispatchEvent(new CustomEvent('tool:success', {
|
|
5984
|
-
detail: { name: toolName, args: args, result: result },
|
|
5985
|
-
bubbles: true
|
|
5986
|
-
}));
|
|
5987
|
-
}).catch(function(err) {
|
|
5988
|
-
console.error('[frontmcp] Tool call failed: ' + toolName, err);
|
|
5989
|
-
resetButton();
|
|
5990
|
-
// Dispatch error event
|
|
5991
|
-
target.dispatchEvent(new CustomEvent('tool:error', {
|
|
5992
|
-
detail: { name: toolName, args: args, error: err.message || err },
|
|
5993
|
-
bubbles: true
|
|
5994
|
-
}));
|
|
5995
|
-
});
|
|
5996
|
-
|
|
5997
|
-
// Prevent default behavior (e.g., form submission)
|
|
5998
|
-
e.preventDefault();
|
|
5999
|
-
return;
|
|
6000
|
-
}
|
|
6001
|
-
target = target.parentElement;
|
|
6002
|
-
}
|
|
6003
|
-
}, true); // Use capture phase to handle before React handlers
|
|
6004
3208
|
};
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
return
|
|
6009
|
-
}
|
|
6010
|
-
function generatePlatformBundle(platform, options = {}) {
|
|
6011
|
-
const platformAdapters = {
|
|
6012
|
-
chatgpt: ["openai", "generic"],
|
|
6013
|
-
claude: ["claude", "generic"],
|
|
6014
|
-
gemini: ["gemini", "generic"],
|
|
6015
|
-
universal: ["openai", "ext-apps", "claude", "gemini", "generic"]
|
|
6016
|
-
};
|
|
6017
|
-
return generateBridgeIIFE({
|
|
6018
|
-
...options,
|
|
6019
|
-
adapters: platformAdapters[platform]
|
|
6020
|
-
});
|
|
3209
|
+
async function createBridge(config, registry) {
|
|
3210
|
+
const bridge = new FrontMcpBridge(config, registry);
|
|
3211
|
+
await bridge.initialize();
|
|
3212
|
+
return bridge;
|
|
6021
3213
|
}
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
3214
|
+
|
|
3215
|
+
// libs/ui/src/bridge/runtime/index.ts
|
|
3216
|
+
import {
|
|
3217
|
+
generateBridgeIIFE,
|
|
3218
|
+
generatePlatformBundle,
|
|
3219
|
+
UNIVERSAL_BRIDGE_SCRIPT,
|
|
3220
|
+
BRIDGE_SCRIPT_TAGS
|
|
3221
|
+
} from "@frontmcp/uipack";
|
|
6029
3222
|
|
|
6030
3223
|
// libs/ui/src/web-components/core/base-element.ts
|
|
6031
3224
|
import { validationErrorBox } from "@frontmcp/uipack/validation";
|
|
@@ -7107,15 +4300,6 @@ function renderToStringSync(element) {
|
|
|
7107
4300
|
const ReactDOMServer = __require("react-dom/server");
|
|
7108
4301
|
return ReactDOMServer.renderToStaticMarkup(element);
|
|
7109
4302
|
}
|
|
7110
|
-
function isReactAvailable() {
|
|
7111
|
-
try {
|
|
7112
|
-
__require("react");
|
|
7113
|
-
__require("react-dom/server");
|
|
7114
|
-
return true;
|
|
7115
|
-
} catch {
|
|
7116
|
-
return false;
|
|
7117
|
-
}
|
|
7118
|
-
}
|
|
7119
4303
|
|
|
7120
4304
|
// libs/ui/src/react/Card.tsx
|
|
7121
4305
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -7297,7 +4481,7 @@ import {
|
|
|
7297
4481
|
getAlertVariantClasses,
|
|
7298
4482
|
ALERT_BASE_CLASSES,
|
|
7299
4483
|
ALERT_ICONS,
|
|
7300
|
-
CLOSE_ICON
|
|
4484
|
+
CLOSE_ICON,
|
|
7301
4485
|
cn as cn4
|
|
7302
4486
|
} from "@frontmcp/uipack/styles";
|
|
7303
4487
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
@@ -7333,7 +4517,7 @@ function Alert({
|
|
|
7333
4517
|
className: "flex-shrink-0 ml-3 hover:opacity-70 transition-opacity",
|
|
7334
4518
|
"aria-label": "Dismiss",
|
|
7335
4519
|
onClick: onDismiss,
|
|
7336
|
-
children: /* @__PURE__ */ jsx4("span", { dangerouslySetInnerHTML: { __html:
|
|
4520
|
+
children: /* @__PURE__ */ jsx4("span", { dangerouslySetInnerHTML: { __html: CLOSE_ICON } })
|
|
7337
4521
|
}
|
|
7338
4522
|
)
|
|
7339
4523
|
] }) });
|
|
@@ -7470,7 +4654,7 @@ function useCapability(cap) {
|
|
|
7470
4654
|
}
|
|
7471
4655
|
|
|
7472
4656
|
// libs/ui/src/react/hooks/tools.tsx
|
|
7473
|
-
import { useState as useState2, useCallback
|
|
4657
|
+
import { useState as useState2, useCallback, useEffect as useEffect2 } from "react";
|
|
7474
4658
|
function useToolInput() {
|
|
7475
4659
|
const { bridge, ready } = useMcpBridgeContext();
|
|
7476
4660
|
if (!ready || !bridge) {
|
|
@@ -7536,7 +4720,7 @@ function useCallTool(toolName, options = {}) {
|
|
|
7536
4720
|
});
|
|
7537
4721
|
}
|
|
7538
4722
|
}, [toolName, resetOnToolChange]);
|
|
7539
|
-
const reset =
|
|
4723
|
+
const reset = useCallback(() => {
|
|
7540
4724
|
setState({
|
|
7541
4725
|
data: null,
|
|
7542
4726
|
loading: false,
|
|
@@ -7544,7 +4728,7 @@ function useCallTool(toolName, options = {}) {
|
|
|
7544
4728
|
called: false
|
|
7545
4729
|
});
|
|
7546
4730
|
}, []);
|
|
7547
|
-
const callTool =
|
|
4731
|
+
const callTool = useCallback(
|
|
7548
4732
|
async (args) => {
|
|
7549
4733
|
if (!ready || !bridge) {
|
|
7550
4734
|
const error = new Error("Bridge not initialized");
|
|
@@ -7594,7 +4778,7 @@ function useToolCalls(toolMap) {
|
|
|
7594
4778
|
}
|
|
7595
4779
|
return initial;
|
|
7596
4780
|
});
|
|
7597
|
-
const createCallFn =
|
|
4781
|
+
const createCallFn = useCallback(
|
|
7598
4782
|
(key, toolName) => async (args) => {
|
|
7599
4783
|
if (!bridge) {
|
|
7600
4784
|
setStates((prev) => ({
|
|
@@ -7629,7 +4813,7 @@ function useToolCalls(toolMap) {
|
|
|
7629
4813
|
},
|
|
7630
4814
|
[bridge]
|
|
7631
4815
|
);
|
|
7632
|
-
const createResetFn =
|
|
4816
|
+
const createResetFn = useCallback(
|
|
7633
4817
|
(key) => () => {
|
|
7634
4818
|
setStates((prev) => ({
|
|
7635
4819
|
...prev,
|
|
@@ -7663,7 +4847,7 @@ function useSendMessage() {
|
|
|
7663
4847
|
error: null,
|
|
7664
4848
|
sent: false
|
|
7665
4849
|
});
|
|
7666
|
-
const sendMessage =
|
|
4850
|
+
const sendMessage = useCallback(
|
|
7667
4851
|
async (content) => {
|
|
7668
4852
|
if (!bridge) {
|
|
7669
4853
|
setState({ loading: false, error: new Error("Bridge not initialized"), sent: false });
|
|
@@ -7684,7 +4868,7 @@ function useSendMessage() {
|
|
|
7684
4868
|
}
|
|
7685
4869
|
function useOpenLink() {
|
|
7686
4870
|
const bridge = useMcpBridge();
|
|
7687
|
-
return
|
|
4871
|
+
return useCallback(
|
|
7688
4872
|
async (url) => {
|
|
7689
4873
|
if (!bridge) {
|
|
7690
4874
|
console.warn("Bridge not initialized, cannot open link");
|
|
@@ -7696,58 +4880,19 @@ function useOpenLink() {
|
|
|
7696
4880
|
);
|
|
7697
4881
|
}
|
|
7698
4882
|
|
|
7699
|
-
// libs/ui/src/react
|
|
7700
|
-
import {
|
|
7701
|
-
var
|
|
7702
|
-
function
|
|
7703
|
-
|
|
7704
|
-
try {
|
|
7705
|
-
cachedReactDOMServer = __require("react-dom/server");
|
|
7706
|
-
} catch {
|
|
7707
|
-
return null;
|
|
7708
|
-
}
|
|
7709
|
-
}
|
|
7710
|
-
return cachedReactDOMServer;
|
|
4883
|
+
// libs/ui/src/renderers/react.renderer.ts
|
|
4884
|
+
import { isReactComponent, containsJsx, hashString, transpileJsx } from "@frontmcp/uipack/renderers";
|
|
4885
|
+
var VALID_JS_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
4886
|
+
function isValidComponentName(name) {
|
|
4887
|
+
return VALID_JS_IDENTIFIER.test(name);
|
|
7711
4888
|
}
|
|
7712
|
-
function
|
|
7713
|
-
if (
|
|
7714
|
-
return
|
|
7715
|
-
}
|
|
7716
|
-
if (typeof children === "string") {
|
|
7717
|
-
return escapeHtml3(children);
|
|
7718
|
-
}
|
|
7719
|
-
if (typeof children === "number") {
|
|
7720
|
-
return String(children);
|
|
7721
|
-
}
|
|
7722
|
-
if (typeof children === "boolean") {
|
|
7723
|
-
return "";
|
|
7724
|
-
}
|
|
7725
|
-
try {
|
|
7726
|
-
const server = getReactDOMServer();
|
|
7727
|
-
if (server) {
|
|
7728
|
-
return server.renderToStaticMarkup(children);
|
|
7729
|
-
}
|
|
7730
|
-
return String(children);
|
|
7731
|
-
} catch {
|
|
7732
|
-
return String(children);
|
|
4889
|
+
function sanitizeComponentName(name) {
|
|
4890
|
+
if (isValidComponentName(name)) {
|
|
4891
|
+
return name;
|
|
7733
4892
|
}
|
|
4893
|
+
const sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_").replace(/^[0-9]/, "_$&");
|
|
4894
|
+
return sanitized || "Component";
|
|
7734
4895
|
}
|
|
7735
|
-
function isBrowser() {
|
|
7736
|
-
return typeof window !== "undefined";
|
|
7737
|
-
}
|
|
7738
|
-
function isServer() {
|
|
7739
|
-
return typeof window === "undefined";
|
|
7740
|
-
}
|
|
7741
|
-
|
|
7742
|
-
// libs/ui/src/renderers/react.renderer.ts
|
|
7743
|
-
import {
|
|
7744
|
-
isReactComponent,
|
|
7745
|
-
containsJsx,
|
|
7746
|
-
hashString,
|
|
7747
|
-
transpileJsx,
|
|
7748
|
-
executeTranspiledCode,
|
|
7749
|
-
transpileCache
|
|
7750
|
-
} from "@frontmcp/uipack/renderers";
|
|
7751
4896
|
var REACT_CDN = {
|
|
7752
4897
|
react: "https://esm.sh/react@19",
|
|
7753
4898
|
reactDom: "https://esm.sh/react-dom@19/client"
|
|
@@ -7761,13 +4906,6 @@ var ReactRenderer = class {
|
|
|
7761
4906
|
type = "react";
|
|
7762
4907
|
priority = 20;
|
|
7763
4908
|
// Higher priority than HTML
|
|
7764
|
-
/**
|
|
7765
|
-
* Lazy-loaded React modules.
|
|
7766
|
-
*/
|
|
7767
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7768
|
-
React = null;
|
|
7769
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7770
|
-
ReactDOMServer = null;
|
|
7771
4909
|
/**
|
|
7772
4910
|
* Check if this renderer can handle the given template.
|
|
7773
4911
|
*
|
|
@@ -7808,46 +4946,109 @@ var ReactRenderer = class {
|
|
|
7808
4946
|
throw new Error("Invalid template type for ReactRenderer");
|
|
7809
4947
|
}
|
|
7810
4948
|
/**
|
|
7811
|
-
* Render the template to HTML
|
|
4949
|
+
* Render the template to HTML for client-side rendering.
|
|
4950
|
+
*
|
|
4951
|
+
* Unlike SSR, this method generates HTML that will be rendered
|
|
4952
|
+
* client-side by React in the browser. No server-side React required.
|
|
4953
|
+
*
|
|
4954
|
+
* The generated HTML includes:
|
|
4955
|
+
* - A container div for the React root
|
|
4956
|
+
* - The component code (transpiled if needed)
|
|
4957
|
+
* - Props embedded as a data attribute
|
|
4958
|
+
* - A render script that initializes the component
|
|
7812
4959
|
*/
|
|
7813
|
-
async render(template, context,
|
|
7814
|
-
|
|
7815
|
-
|
|
4960
|
+
async render(template, context, _options) {
|
|
4961
|
+
const props = {
|
|
4962
|
+
input: context.input,
|
|
4963
|
+
output: context.output,
|
|
4964
|
+
structuredContent: context.structuredContent,
|
|
4965
|
+
helpers: context.helpers
|
|
4966
|
+
};
|
|
4967
|
+
const escapedProps = JSON.stringify(props).replace(/&/g, "&").replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
4968
|
+
const rootId = `frontmcp-react-${hashString(Date.now().toString()).slice(0, 8)}`;
|
|
4969
|
+
let componentCode;
|
|
4970
|
+
let componentName;
|
|
7816
4971
|
if (typeof template === "function") {
|
|
7817
|
-
|
|
4972
|
+
const rawName = template.name || "Component";
|
|
4973
|
+
componentName = sanitizeComponentName(rawName);
|
|
4974
|
+
componentCode = `
|
|
4975
|
+
// Component should be registered via window.__frontmcp_components['${componentName}']
|
|
4976
|
+
(function() {
|
|
4977
|
+
if (!window.__frontmcp_components || !window.__frontmcp_components['${componentName}']) {
|
|
4978
|
+
console.error('[FrontMCP] Component "${componentName}" not registered. Use buildHydrationScript() to register components.');
|
|
4979
|
+
}
|
|
4980
|
+
})();
|
|
4981
|
+
`;
|
|
7818
4982
|
} else if (typeof template === "string") {
|
|
7819
4983
|
const transpiled = await this.transpile(template);
|
|
7820
|
-
const
|
|
7821
|
-
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
Component = await executeTranspiledCode(transpiled.code, {
|
|
7825
|
-
// Provide any additional MDX components if specified
|
|
7826
|
-
...options?.mdxComponents
|
|
7827
|
-
});
|
|
7828
|
-
transpileCache.setByKey(`exec:${transpiled.hash}`, {
|
|
7829
|
-
code: Component,
|
|
7830
|
-
hash: transpiled.hash,
|
|
7831
|
-
cached: false
|
|
7832
|
-
});
|
|
7833
|
-
}
|
|
4984
|
+
const match = transpiled.code.match(/function\s+(\w+)/);
|
|
4985
|
+
const rawName = match?.[1] || "Widget";
|
|
4986
|
+
componentName = sanitizeComponentName(rawName);
|
|
4987
|
+
componentCode = transpiled.code;
|
|
7834
4988
|
} else {
|
|
7835
4989
|
throw new Error("Invalid template type for ReactRenderer");
|
|
7836
4990
|
}
|
|
7837
|
-
const
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
4991
|
+
const html = `
|
|
4992
|
+
<div id="${rootId}" data-frontmcp-react data-component="${componentName}" data-props='${escapedProps}'>
|
|
4993
|
+
<div class="flex items-center justify-center p-4 text-gray-500">
|
|
4994
|
+
<svg class="animate-spin h-5 w-5 mr-2" viewBox="0 0 24 24">
|
|
4995
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
|
|
4996
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
4997
|
+
</svg>
|
|
4998
|
+
Loading...
|
|
4999
|
+
</div>
|
|
5000
|
+
</div>
|
|
5001
|
+
<script type="module">
|
|
5002
|
+
(function() {
|
|
5003
|
+
${componentCode}
|
|
5004
|
+
|
|
5005
|
+
// Wait for React to be available
|
|
5006
|
+
function waitForReact(callback, maxAttempts) {
|
|
5007
|
+
var attempts = 0;
|
|
5008
|
+
var check = function() {
|
|
5009
|
+
if (typeof React !== 'undefined' && typeof ReactDOM !== 'undefined') {
|
|
5010
|
+
callback();
|
|
5011
|
+
} else if (attempts < maxAttempts) {
|
|
5012
|
+
attempts++;
|
|
5013
|
+
setTimeout(check, 50);
|
|
5014
|
+
} else {
|
|
5015
|
+
console.error('[FrontMCP] React not loaded after ' + maxAttempts + ' attempts');
|
|
5016
|
+
}
|
|
7842
5017
|
};
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
5018
|
+
check();
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
waitForReact(function() {
|
|
5022
|
+
try {
|
|
5023
|
+
var root = document.getElementById('${rootId}');
|
|
5024
|
+
if (!root) return;
|
|
5025
|
+
|
|
5026
|
+
var propsJson = root.getAttribute('data-props');
|
|
5027
|
+
var props = propsJson ? JSON.parse(propsJson.replace(/&/g, '&').replace(/'/g, "'").replace(/</g, '<').replace(/>/g, '>')) : {};
|
|
5028
|
+
|
|
5029
|
+
// Get the component
|
|
5030
|
+
var Component = ${componentName};
|
|
5031
|
+
|
|
5032
|
+
// Check if it's registered globally
|
|
5033
|
+
if (typeof Component === 'undefined' && window.__frontmcp_components) {
|
|
5034
|
+
Component = window.__frontmcp_components['${componentName}'];
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5037
|
+
if (typeof Component === 'function') {
|
|
5038
|
+
var element = React.createElement(Component, props);
|
|
5039
|
+
var reactRoot = ReactDOM.createRoot(root);
|
|
5040
|
+
reactRoot.render(element);
|
|
5041
|
+
} else {
|
|
5042
|
+
console.error('[FrontMCP] Component "${componentName}" not found');
|
|
5043
|
+
}
|
|
5044
|
+
} catch (err) {
|
|
5045
|
+
console.error('[FrontMCP] React render error:', err);
|
|
7849
5046
|
}
|
|
7850
|
-
|
|
5047
|
+
}, 100);
|
|
5048
|
+
})();
|
|
5049
|
+
</script>
|
|
5050
|
+
`;
|
|
5051
|
+
return html.trim();
|
|
7851
5052
|
}
|
|
7852
5053
|
/**
|
|
7853
5054
|
* Get runtime scripts for client-side functionality.
|
|
@@ -7868,54 +5069,8 @@ var ReactRenderer = class {
|
|
|
7868
5069
|
isInline: false
|
|
7869
5070
|
};
|
|
7870
5071
|
}
|
|
7871
|
-
/**
|
|
7872
|
-
* Load React and ReactDOMServer modules.
|
|
7873
|
-
*/
|
|
7874
|
-
async loadReact() {
|
|
7875
|
-
if (this.React && this.ReactDOMServer) {
|
|
7876
|
-
return;
|
|
7877
|
-
}
|
|
7878
|
-
try {
|
|
7879
|
-
this.React = await import("react");
|
|
7880
|
-
this.ReactDOMServer = await import("react-dom/server");
|
|
7881
|
-
} catch {
|
|
7882
|
-
throw new Error("React is required for ReactRenderer. Install react and react-dom: npm install react react-dom");
|
|
7883
|
-
}
|
|
7884
|
-
}
|
|
7885
5072
|
};
|
|
7886
5073
|
var reactRenderer = new ReactRenderer();
|
|
7887
|
-
function buildHydrationScript() {
|
|
7888
|
-
return `
|
|
7889
|
-
<script>
|
|
7890
|
-
(function() {
|
|
7891
|
-
// Wait for React to be available
|
|
7892
|
-
if (typeof React === 'undefined' || typeof ReactDOM === 'undefined') {
|
|
7893
|
-
console.warn('[FrontMCP] React not available for hydration');
|
|
7894
|
-
return;
|
|
7895
|
-
}
|
|
7896
|
-
|
|
7897
|
-
// Find all elements marked for hydration
|
|
7898
|
-
document.querySelectorAll('[data-hydrate]').forEach(function(root) {
|
|
7899
|
-
var componentName = root.getAttribute('data-hydrate');
|
|
7900
|
-
var propsJson = root.getAttribute('data-props');
|
|
7901
|
-
var props = propsJson ? JSON.parse(propsJson) : {};
|
|
7902
|
-
|
|
7903
|
-
// Look for the component in the global scope
|
|
7904
|
-
if (window.__frontmcp_components && window.__frontmcp_components[componentName]) {
|
|
7905
|
-
try {
|
|
7906
|
-
ReactDOM.hydrateRoot(root, React.createElement(
|
|
7907
|
-
window.__frontmcp_components[componentName],
|
|
7908
|
-
props
|
|
7909
|
-
));
|
|
7910
|
-
} catch (e) {
|
|
7911
|
-
console.error('[FrontMCP] Hydration failed for', componentName, e);
|
|
7912
|
-
}
|
|
7913
|
-
}
|
|
7914
|
-
});
|
|
7915
|
-
})();
|
|
7916
|
-
</script>
|
|
7917
|
-
`;
|
|
7918
|
-
}
|
|
7919
5074
|
|
|
7920
5075
|
// libs/ui/src/renderers/react.adapter.ts
|
|
7921
5076
|
var mountedRoots = /* @__PURE__ */ new WeakMap();
|
|
@@ -8204,22 +5359,15 @@ export {
|
|
|
8204
5359
|
badgeGroup,
|
|
8205
5360
|
baseLayout,
|
|
8206
5361
|
betaBadge,
|
|
8207
|
-
buildHydrationScript,
|
|
8208
5362
|
busyDot,
|
|
8209
5363
|
button,
|
|
8210
5364
|
buttonGroup,
|
|
8211
5365
|
card,
|
|
8212
5366
|
cardGroup,
|
|
8213
5367
|
checkbox,
|
|
8214
|
-
circularProgress,
|
|
8215
|
-
codePreview,
|
|
8216
5368
|
confirmModal,
|
|
8217
|
-
consentDeniedPage,
|
|
8218
5369
|
consentLayout,
|
|
8219
5370
|
consentLayoutBuilder,
|
|
8220
|
-
consentPage,
|
|
8221
|
-
consentSuccessPage,
|
|
8222
|
-
contentSkeleton,
|
|
8223
5371
|
createBridge,
|
|
8224
5372
|
createLayoutBuilder,
|
|
8225
5373
|
createReactAdapter,
|
|
@@ -8232,10 +5380,8 @@ export {
|
|
|
8232
5380
|
errorBadge,
|
|
8233
5381
|
errorLayout,
|
|
8234
5382
|
errorLayoutBuilder,
|
|
8235
|
-
errorPage,
|
|
8236
5383
|
escapeHtml2 as escapeHtml,
|
|
8237
5384
|
featureList,
|
|
8238
|
-
forbiddenPage,
|
|
8239
5385
|
form,
|
|
8240
5386
|
formActions,
|
|
8241
5387
|
formRow,
|
|
@@ -8244,33 +5390,23 @@ export {
|
|
|
8244
5390
|
generatePlatformBundle,
|
|
8245
5391
|
ghostButton,
|
|
8246
5392
|
hiddenInput,
|
|
8247
|
-
imagePreview,
|
|
8248
5393
|
inactiveBadge,
|
|
8249
5394
|
infoAlert,
|
|
8250
5395
|
input,
|
|
8251
|
-
isBrowser,
|
|
8252
|
-
isReactAvailable,
|
|
8253
|
-
isServer,
|
|
8254
5396
|
linkButton,
|
|
8255
5397
|
loadReactAdapter,
|
|
8256
5398
|
loadingLayout,
|
|
8257
|
-
maintenancePage,
|
|
8258
5399
|
modal,
|
|
8259
5400
|
modalTrigger,
|
|
8260
5401
|
newBadge,
|
|
8261
|
-
notFoundPage,
|
|
8262
|
-
oauthErrorPage,
|
|
8263
5402
|
offlineDot,
|
|
8264
|
-
offlinePage,
|
|
8265
5403
|
onlineDot,
|
|
8266
5404
|
outlineButton,
|
|
8267
5405
|
pagination,
|
|
8268
5406
|
pendingBadge,
|
|
8269
5407
|
permissionList,
|
|
8270
5408
|
primaryButton,
|
|
8271
|
-
progressBar,
|
|
8272
5409
|
radioGroup,
|
|
8273
|
-
rateLimitPage,
|
|
8274
5410
|
reactRenderer,
|
|
8275
5411
|
registerAdapter,
|
|
8276
5412
|
registerAllComponents,
|
|
@@ -8288,26 +5424,14 @@ export {
|
|
|
8288
5424
|
renderButtonSync,
|
|
8289
5425
|
renderCard,
|
|
8290
5426
|
renderCardSync,
|
|
8291
|
-
renderChildrenToString,
|
|
8292
|
-
renderToString,
|
|
8293
|
-
renderToStringSync,
|
|
8294
|
-
resourceItem,
|
|
8295
|
-
resourceList,
|
|
8296
|
-
resourceWidget,
|
|
8297
5427
|
secondaryButton,
|
|
8298
5428
|
select,
|
|
8299
|
-
serverErrorPage,
|
|
8300
|
-
sessionExpiredPage,
|
|
8301
|
-
skeleton,
|
|
8302
|
-
statusIndicator,
|
|
8303
|
-
stepProgress,
|
|
8304
5429
|
successAlert,
|
|
8305
5430
|
successLayout,
|
|
8306
5431
|
table,
|
|
8307
5432
|
textarea,
|
|
8308
5433
|
toast,
|
|
8309
5434
|
toastContainer,
|
|
8310
|
-
unauthorizedPage,
|
|
8311
5435
|
useCallTool,
|
|
8312
5436
|
useCapability,
|
|
8313
5437
|
useDisplayMode,
|