@eka-care/medassist-widget-embed

中文文档
0.2.122发布于 2026/11/13MITTypeScriptESM + CJS零依赖近 30 天下载 3099

Embeddable MedAssist widget loader built with Web Components.

原始使用文档
文档目录22 节
  1. Overview
  2. Installation
  3. Usage
  4. Configuration
  5. Required attributes
  6. Optional attributes
  7. Programmatic config with EkaMedAssist.init()
  8. Custom icon example
  9. Custom launcher styling
  10. Side panel mode
  11. Host analytics events
  12. Client tools
  13. Environments
  14. How it works
  15. File structure
  16. Browser support
  17. Development
  18. Integration examples
  19. React
  20. Vue
  21. Troubleshooting
  22. License

@eka-care/medassist-widget-embed

Lightweight, embeddable widget loader for MedAssist using Web Components. Add the script and the <eka-medassist-widget> custom element to any page for a framework-agnostic, lazy-loaded chat widget.

Overview

  • Custom Web Component – <eka-medassist-widget> for easy embedding
  • Lazy loading – Widget assets load when the user opens the widget
  • Framework agnostic – No React or build step required
  • Isolated styling – Shadow DOM avoids conflicts with host styles
  • Simple integration – Script tag + custom element

Installation

npm install @eka-care/medassist-widget-embed

Or load from a CDN (replace the version as needed):

<script
  src="https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js"
  async></script>

For self-hosting, ensure the built widget assets (medassist-widget.js, medassist-widget.css) are served alongside index.js, or use the package’s default CDN for widget assets (see How it works).

Usage

<!DOCTYPE html>
<html>
  <head>
    <title>My Website</title>
  </head>
  <body>
    <h1>Welcome</h1>

    <eka-medassist-widget agent-id="your-agent-id-here"></eka-medassist-widget>

    <script src="https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js" async></script>
  </body>
</html>

The widget appears as a floating button; clicking it loads and opens the chat.

Configuration

Required attributes

  • agent-id – Your MedAssist agent identifier

Optional attributes

  • icon-url – Custom icon URL for the launcher button. When omitted, the launcher uses the agent's configured icon (theme.icon_img from agent config) and is only shown once that config has loaded — there is no built-in default icon
  • title – Widget title
  • base-url – Base URL for API/agent config
  • display-mode – "widget" (floating button), "full" (inline full view) or "side_panel" (docked panel, see Side panel mode)
  • side-panel-mode – "push" (default) or "overlay"; only used with display-mode="side_panel"
  • side-panel-width – panel width in px, clamped to 320–560 (default 400); side panel only
  • side – "right" (default) or "left"; defaults to "left" on RTL documents; side panel only
  • override-widget-width / override-widget-height – size of the floating window in px (positive numbers; anything else is ignored). Floating mode on desktop only: full page, side panel and the mobile layout keep their own sizing. Clamped to 320–800 wide and 480–900 high, and never larger than the viewport minus 20px on each side. They override the agent config's theme.widget_width / theme.widget_height; precedence per dimension is host override → agent config → default 384×600
  • context – JSON string of context key-value pairs

Programmatic config with EkaMedAssist.init()

After the script loads, you can set or override config before the user opens the widget:

window.EkaMedAssist.init({
  agentId: "your-agent-id",
  title: "MedAssist",
  iconUrl: "https://example.com/icon.svg",
  baseUrl: "https://api.example.com",
  context: { key: "value" },
  theme: {
    background: "#1a1738",
    primary: "#09FBD3",
    textColor: "white",
    overrideWidgetWidth: 480,  // floating window size in px (same as the override-widget-* attributes)
    overrideWidgetHeight: 720,
  },
});

Custom icon example

<eka-medassist-widget
  agent-id="your-agent-id"
  icon-url="https://example.com/custom-icon.svg"></eka-medassist-widget>

Custom launcher styling

If you need to reposition the floating launcher button (different breakpoints, custom z-index, etc.), pass CSS via customLauncherStyles in init() — or set it directly with the custom-launcher-styles attribute. The CSS is injected into the launcher's Shadow DOM, so you can use media queries, hover states, and anything else CSS allows.

Two selectors are supported as a stable contract:

  • :host — the launcher wrapper. Use this to override position, bottom, right, left, top, transform, z-index, etc.
  • #medassist-open-btn — the button element itself. Use this for size, background, box-shadow, border-radius, etc.
window.EkaMedAssist.init({
  agentId: "your-agent-id",
  customLauncherStyles: `
    @media (max-width: 991px) {
      :host {
        right: auto;
        left: 50%;
        bottom: 47px;
        transform: translateX(-50%);
      }
    }
    @media (max-width: 767px) {
      :host { bottom: 34px; }
    }
  `,
});

This only affects the launcher button — the chat panel that opens on click is unaffected.

Side panel mode

display-mode="side_panel" docks the chat to a viewport edge at a fixed width instead of floating it. On mobile-width viewports the widget falls back to its mobile layout, as with every other mode.

<eka-medassist-widget
  agent-id="your-agent-id"
  display-mode="side_panel"
  side-panel-mode="push"
  side-panel-width="400"
  side="right"></eka-medassist-widget>

side-panel-mode picks how the panel relates to your page:

  • overlay – the panel floats on top of the page. Nothing on your page moves.
  • push (default) – the page is shifted aside so the panel does not cover it. On open the loader injects a stylesheet into your document <head> and adds the eka-medassist-pushed class to <body>; on close both are removed again, leaving the page exactly as it was.

The injected stylesheet does two things:

body.eka-medassist-pushed { margin-right: 400px; transition: margin 300ms ease; } /* margin-left when side="left" */
body.eka-medassist-pushed > * { max-width: 100%; }

The margin reflows normal in-flow content. The max-width cap makes viewport-sized app roots (width: 100vw, Tailwind w-screen) that are direct children of <body> shrink with <body> instead of sliding under the panel. It does not touch position: fixed elements, and a min-width you set still wins.

If your viewport-sized root sits inside a wrapper element, the cap does not reach it. Either size it relative to its parent (w-full instead of w-screen), or subtract the panel width explicitly:

/* An app shell that must stay 100vw when the panel is closed */
.app-shell {
  width: calc(100vw - var(--eka-medassist-panel-width, 0px));
  transition: width 300ms ease;
}

Host analytics events

The widget never talks to GTM / gtag / dataLayer itself. The host page opts in by defining a global window.trackWidgetEvent(name, detail); the widget calls it and the host forwards whatever it wants to its own analytics stack. Without that function the widget emits nothing. detail is { sessionId, ...extra } and carries only non-sensitive identifiers — never message text, user data or phone numbers. sessionId is omitted only while a brand-new session is still being created.

Define the function before the loader script runs: events fired before it exists are dropped, not queued.

Startup

nameExtra detailWhen
session_error{ reason, status?, closeCode? }Something kept the user from the chat. Per failure. reason is "<category>:<code>", e.g. connection_failed:network_error — see the categories below.
connection_established{ resumed }Session established and transport connected — the user can send a message. Once per open of the widget per session id. resumed is true when an existing session was picked up again (a reload, or a past chat opened from history), false for a freshly created one.

session_error categories (the part of reason before the colon):

CategoryCode after the colonWhen
auth_failedThe SDK recovery reason (AUTH_EXPIRED, SESSION_FORBIDDEN) or the server's error code. status is 401/403.A credential was rejected: token exchange or refresh failed, or the server answered 401/403.
session_failedThe server's error code, the recovery reason (SESSION_NOT_FOUND, RETRY_LATER), an in-band code (session_not_found, token mismatch, not owned) or the SDK classification.The session could not be created, resumed or kept.
connection_failedNETWORK_ERROR (fetch failed), timeout (request timed out), CONNECTION_ERROR, or a socket/stream disconnect reason (network_error, server_closed, authentication_error, timeout, max_reconnect_attempts) with closeCode.The transport could not be reached or dropped.

SDK errors are reported only while the chat is not connected; an error after that (feedback, history, recording) is not a startup failure.

Engagement

These answer "did the visitor interact with the bot on this visit". Like connection_established, each counts like a page view: once per open of the widget per session id, so closing the bot and opening it again fires them once more. They fire again for a resumed session — a returning user who continues an old chat counts as an interaction — and every one carries resumed (same meaning as on connection_established) so the two can be split in a funnel. No event is sent for subsequent messages.

nameExtra detailWhen
first_message_sent{ resumed }The first message sent after the widget is opened (Enter, the send button, or a greeting suggestion), even if the resumed transcript already holds earlier messages.
first_bot_reply{ resumed, messageId }The first assistant reply that starts rendering after the widget is opened. The static greeting does not count.

Starting a new chat creates a new session id, so all three milestones fire again for it with resumed: false.

Funnels: connection_established → first_message_sent → first_bot_reply measures interaction per visit regardless of whether the session was resumed; filter on resumed to compare new and returning visitors.

Event names are snake_case; the host adds its own prefix when forwarding (e.g. eka_session_error).

window.trackWidgetEvent = (name, detail) => {
  window.dataLayer?.push({ event: `eka_${name}`, ...detail });
};

What push cannot move, and the hooks to handle it

position: fixed elements (sticky headers, banners, inset: 0 overlays) are positioned against the viewport, so no outside stylesheet can move them. While the panel is open the loader publishes what it did so your own CSS or JS can follow:

  • body.eka-medassist-pushed – class on <body> while pushed.
  • --eka-medassist-panel-width – custom property on <html>, e.g. 400px. Removed on close.
  • --eka-medassist-panel-side – custom property on <html>, right or left. Removed on close.
  • eka-medassist:side-panel-push – window event, detail: { pushed: boolean, width: number, side: "left" | "right" }. Fired on open (pushed: true) and close (pushed: false).
  • window.EkaMedAssist.onSidePanelPushChange(pushed, widthPx, side) – same signal as a callback, alongside EkaMedAssist.onClose.

Give the custom property a 0px fallback so the same rule is correct while the panel is closed:

/* A fixed header that should stop at the panel edge instead of running under it */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: var(--eka-medassist-panel-width, 0px);
  transition: right 300ms ease;
}
window.addEventListener("eka-medassist:side-panel-push", (e) => {
  const { pushed, width, side } = e.detail;
  // e.g. store in app state and offset fixed chrome / recompute a canvas
});

Client tools

Give the agent functions that run in your page (read or edit your app's state). The page declares them; the backend tells the LLM they exist; when the LLM calls one, the SDK runs your run and sends the result back. Declarations follow MCP's Tool shape; only name / description / inputSchema / outputSchema / annotations leave the page — run never does.

EkaMedAssist.init({
  agentId: "<agent-id>",
  tools: [
    {
      name: "read_note",
      description: "Read the note the user has open.",
      inputSchema: { type: "object", properties: { section_keys: { type: "array", items: { type: "string" } } } },
      annotations: { title: "Reading the note", readOnlyHint: true },
      run: async (input, ctx) => {
        ctx.progress({ message: "Collecting sections…" });
        return { sections: getSections(input.section_keys) }; // plain object → structured result
      },
    },
  ],
});

// Replace the list at any time (before or after the widget opens):
EkaMedAssist.registerTools([...]);

Rules:

  • run must read live page state at call time — tools are registered once and reused.
  • Return a plain object (becomes structuredContent) or a full MCP CallToolResult.
  • Report a failure the model should handle by throwing an Error with code (string) and optional data (object); it becomes { isError: true, structuredContent: { code, message, ...data } }.
  • annotations.title is the label shown on the reply bubble while the tool runs; ctx.progress({ message }) updates it live.
  • ctx.signal aborts on timeout (timeoutMs, default 30 s), server cancel, or session end.
  • Not supported in the iframe embed (functions cannot cross frames).
  • Needs widget 0.2.x (the React-SDK build). The legacy 0.1.x widget ignores tools.

Protocol details: docs/client-tools-plan.md in the repo. Demo: test-client-tools.html.

Environments

The SDK ships with the production backend only. The environment attribute / init({ environment }) is kept for backward compatibility but any value other than production is ignored; point at another backend with base-url / init({ baseUrl }).

How it works

  1. Initial load – The script registers the custom element <eka-medassist-widget>.
  2. Button – The element shows a floating button (or full view if display-mode="full").
  3. Lazy load – On first open, the script fetches the widget JS and CSS (from the same origin or from https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget@latest/dist/ by default).
  4. Isolation – The widget runs inside Shadow DOM to avoid style and script conflicts.

To use your own widget assets (e.g. after building @eka-care/medassist-widget), host medassist-widget.js and medassist-widget.css and point the script via the data-widget-assets attribute:

<script
  src="/path/to/widget-embed/index.js"
  data-widget-assets="/path/to/widget-assets/"
  async></script>

File structure

widget-embed/
├── dist/
│   ├── index.js        # Loader and custom element
│   ├── iframe.js       # Optional iframe helper
│   └── iframe.html     # Optional iframe template
├── assets/
│   └── bot-icon.svg    # Sample icon for the test pages (not a runtime default)
└── README.md

Browser support

  • Chrome, Firefox, Safari, Edge (latest)
  • Requires Custom Elements, Shadow DOM, and ES6+

Development

Build the package:

npm run build

To build and bundle the widget assets locally:

npm run build:with-widget

(Requires the widget package to be built first.)

Integration examples

React

import { useEffect } from "react";

function App() {
  useEffect(() => {
    const script = document.createElement("script");
    script.src = "https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js";
    script.async = true;
    document.body.appendChild(script);
  }, []);

  return (
    <div>
      <h1>My App</h1>
      <eka-medassist-widget agent-id="your-agent-id" />
    </div>
  );
}

Vue

<template>
  <div>
    <eka-medassist-widget :agent-id="agentId" />
  </div>
</template>

<script>
export default {
  data: () => ({ agentId: "your-agent-id" }),
  mounted() {
    const script = document.createElement("script");
    script.src = "https://cdn.jsdelivr.net/npm/@eka-care/medassist-widget-embed@latest/dist/index.js";
    script.async = true;
    document.body.appendChild(script);
  },
};
</script>

Troubleshooting

  • Widget not appearing – Check agent-id, ensure the loader script loads (Network tab), and check the console for errors.
  • Assets 404 – If using self-hosted assets, ensure medassist-widget.js and medassist-widget.css are served at the path given in data-widget-assets (or use the default CDN).

License

MIT

版本与发布记录

@eka-care/medassist-widget-embed 共发布 232 个版本,首次发布于 2025/11/25;过去 12 个月发布了 232 个版本。

版本发布日期距今
0.2.122latest2026/11/13今天
0.2.120-dev.2dev2026/11/13今天
0.2.120-dev.12026/11/13今天
0.2.1202026/11/13今天
0.2.120-dev.02026/11/13今天
0.2.1192026/11/076 天前

关于 @eka-care/medassist-widget-embed 的常见问题

@eka-care/medassist-widget-embed 是什么?

Embeddable MedAssist widget loader built with Web Components.

如何安装 @eka-care/medassist-widget-embed?

在项目目录运行 npm install @eka-care/medassist-widget-embed,或使用 pnpm add / yarn add / bun add。

@eka-care/medassist-widget-embed 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@eka-care/medassist-widget-embed 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@eka-care/medassist-widget-embed 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@eka-care/medassist-widget-embed 可以商用吗?

@eka-care/medassist-widget-embed 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@eka-care/medassist-widget-embed 的下载量有多少?

根据 npm 官方下载统计,@eka-care/medassist-widget-embed 在 2026/10/13 – 2026/11/11 这 30 天内被下载 3,099 次,日均约 103 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@eka-care/medassist-widget-embed 还在维护吗?

最新版本 0.2.122 发布于 2026/11/13(今天);至今共发布 232 个版本,其中过去 12 个月发布了 232 个;首次发布于 2025/11/25。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。