@miiflow/assistant-ui 0.7.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{WelcomeScreen-XVZqMpVW.d.ts → WelcomeScreen-Cbipi2rK.d.ts} +18 -8
- package/dist/{avatar-C13xc3tA.d.ts → avatar-CD685KQV.d.ts} +1 -1
- package/dist/branding-NieTEGQf.d.ts +32 -0
- package/dist/chunk-NG4HKXYB.js +2 -0
- package/dist/chunk-NG4HKXYB.js.map +1 -0
- package/dist/chunk-NKLA5PPB.js +2 -0
- package/dist/chunk-NKLA5PPB.js.map +1 -0
- package/dist/chunk-QECTWT2H.js +2 -0
- package/dist/chunk-QECTWT2H.js.map +1 -0
- package/dist/chunk-UZ2Z7C43.js +133 -0
- package/dist/chunk-UZ2Z7C43.js.map +1 -0
- package/dist/chunk-ZP4FGAAH.js +2 -0
- package/dist/chunk-ZP4FGAAH.js.map +1 -0
- package/dist/client/index.d.ts +41 -4
- package/dist/client/index.js +7 -5
- package/dist/client/index.js.map +1 -1
- package/dist/composer/index.d.ts +187 -0
- package/dist/composer/index.js +2 -0
- package/dist/composer/index.js.map +1 -0
- package/dist/context/index.d.ts +16 -4
- package/dist/context/index.js +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{message-BrHFdwb2.d.ts → message-D0oMw3tR.d.ts} +1 -1
- package/dist/primitives/index.d.ts +3 -3
- package/dist/{streaming-DURrv9_E.d.ts → streaming-CmQo_OOA.d.ts} +81 -122
- package/dist/styled/index.d.ts +217 -91
- package/dist/styled/index.js +1 -1
- package/dist/styles-no-preflight.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/types-Du00UBst.d.ts +48 -0
- package/package.json +18 -2
- package/dist/branding-SzYU4ncD.d.ts +0 -18
- package/dist/chunk-EI3GOLHT.js +0 -128
- package/dist/chunk-EI3GOLHT.js.map +0 -1
- package/dist/chunk-NSTK5EUQ.js +0 -2
- package/dist/chunk-NSTK5EUQ.js.map +0 -1
- package/dist/chunk-O2ILSWHS.js +0 -2
- package/dist/chunk-O2ILSWHS.js.map +0 -1
- package/dist/chunk-OCKHJ4WO.js +0 -2
- package/dist/chunk-OCKHJ4WO.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
-
import { A as Attachment, M as MessageData, P as ParticipantRole, d as SuggestedAction, S as SourceReference } from './message-
|
|
4
|
-
import { h as AvatarProps$1 } from './avatar-
|
|
5
|
-
import { S as StreamingChunk, V as VisualizationChunkData, M as MediaChunkData, A as ArtifactChunkData, C as ClarificationData, T as ToolApprovalData } from './streaming-
|
|
6
|
-
import
|
|
3
|
+
import { A as Attachment, M as MessageData, P as ParticipantRole, d as SuggestedAction, S as SourceReference } from './message-D0oMw3tR.js';
|
|
4
|
+
import { h as AvatarProps$1 } from './avatar-CD685KQV.js';
|
|
5
|
+
import { S as StreamingChunk, V as VisualizationChunkData, M as MediaChunkData, A as ArtifactChunkData, C as ClarificationData, T as ToolApprovalData } from './streaming-CmQo_OOA.js';
|
|
6
|
+
import { C as CommandProvider } from './types-Du00UBst.js';
|
|
7
7
|
|
|
8
8
|
interface AttachmentPreviewProps {
|
|
9
9
|
/** The attachment to preview */
|
|
@@ -53,7 +53,7 @@ interface MarkdownContentProps {
|
|
|
53
53
|
* Styled markdown renderer with syntax highlighting, copy-to-clipboard,
|
|
54
54
|
* and heading anchor links.
|
|
55
55
|
*/
|
|
56
|
-
declare function MarkdownContent({ children, className, baselineFontSize, darkCodeTheme, }: MarkdownContentProps):
|
|
56
|
+
declare function MarkdownContent({ children, className, baselineFontSize, darkCodeTheme, }: MarkdownContentProps): react.JSX.Element;
|
|
57
57
|
|
|
58
58
|
interface MessageProps {
|
|
59
59
|
/** The message data */
|
|
@@ -150,6 +150,11 @@ interface MessageComposerProps {
|
|
|
150
150
|
isStreaming?: boolean;
|
|
151
151
|
/** Callback to stop the current streaming response */
|
|
152
152
|
onStopStreaming?: () => void;
|
|
153
|
+
/** Optional slash-command typeahead provider (e.g. for skill picker). */
|
|
154
|
+
commandProvider?: CommandProvider | null;
|
|
155
|
+
/** Multiple typeahead providers for distinct triggers (e.g. `/` skills +
|
|
156
|
+
* modes plus `@` ad accounts). Takes precedence over `commandProvider`. */
|
|
157
|
+
commandProviders?: CommandProvider[];
|
|
153
158
|
}
|
|
154
159
|
/**
|
|
155
160
|
* MessageComposer with textarea, attachment support,
|
|
@@ -242,7 +247,7 @@ interface MessageActionBarProps {
|
|
|
242
247
|
* Action bar that appears on hover below assistant message bubbles.
|
|
243
248
|
* Currently implements Copy; onRegenerate and onEdit are reserved for future use.
|
|
244
249
|
*/
|
|
245
|
-
declare function MessageActionBar({ textContent, onRegenerate, onEdit, onReportIncorrect, onConfirmCorrect, className, }: MessageActionBarProps):
|
|
250
|
+
declare function MessageActionBar({ textContent, onRegenerate, onEdit, onReportIncorrect, onConfirmCorrect, className, }: MessageActionBarProps): react.JSX.Element;
|
|
246
251
|
|
|
247
252
|
interface ScrollToBottomButtonProps {
|
|
248
253
|
/** Whether the viewport is at the bottom (hides button when true) */
|
|
@@ -257,7 +262,7 @@ interface ScrollToBottomButtonProps {
|
|
|
257
262
|
* allowing them to quickly jump back to the latest messages.
|
|
258
263
|
* Fades in/out based on scroll position.
|
|
259
264
|
*/
|
|
260
|
-
declare function ScrollToBottomButton({ isAtBottom, onScrollToBottom, className, }: ScrollToBottomButtonProps):
|
|
265
|
+
declare function ScrollToBottomButton({ isAtBottom, onScrollToBottom, className, }: ScrollToBottomButtonProps): react.JSX.Element;
|
|
261
266
|
|
|
262
267
|
type ToolStatus = "running" | "complete" | "failed" | "pending";
|
|
263
268
|
interface ToolStatusIndicatorProps {
|
|
@@ -275,7 +280,7 @@ interface ToolStatusIndicatorProps {
|
|
|
275
280
|
* Shows tool name + status icon (spinner/check/X/clock).
|
|
276
281
|
* Running state includes a subtle shimmer animation.
|
|
277
282
|
*/
|
|
278
|
-
declare function ToolStatusIndicator({ toolName, status, description, className, }: ToolStatusIndicatorProps):
|
|
283
|
+
declare function ToolStatusIndicator({ toolName, status, description, className, }: ToolStatusIndicatorProps): react.JSX.Element;
|
|
279
284
|
|
|
280
285
|
interface ChatLayoutProps {
|
|
281
286
|
/** Whether the chat is in empty state (no messages) */
|
|
@@ -317,6 +322,11 @@ interface WelcomeScreenProps {
|
|
|
317
322
|
supportsAttachments?: boolean;
|
|
318
323
|
/** Override the default plain-text input with a custom composer (e.g. chat-ui MessageComposer) */
|
|
319
324
|
composerSlot?: ReactNode;
|
|
325
|
+
/** Optional slash-command typeahead provider (e.g. for skill picker). */
|
|
326
|
+
commandProvider?: CommandProvider | null;
|
|
327
|
+
/** Multiple typeahead providers for distinct triggers (e.g. `/` skills +
|
|
328
|
+
* modes plus `@` ad accounts). Takes precedence over `commandProvider`. */
|
|
329
|
+
commandProviders?: CommandProvider[];
|
|
320
330
|
/** Additional CSS classes for the outer wrapper */
|
|
321
331
|
className?: string;
|
|
322
332
|
/** Assistant avatar image URL — when provided, welcome text renders in message format */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode, TextareaHTMLAttributes, ButtonHTMLAttributes } from 'react';
|
|
3
|
-
import { M as MessageData, P as ParticipantRole } from './message-
|
|
3
|
+
import { M as MessageData, P as ParticipantRole } from './message-D0oMw3tR.js';
|
|
4
4
|
|
|
5
5
|
interface MessageContextValue {
|
|
6
6
|
message: MessageData;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branding types for theming chat UI components.
|
|
3
|
+
*/
|
|
4
|
+
interface BrandingData {
|
|
5
|
+
customName?: string;
|
|
6
|
+
messageFontSize?: number;
|
|
7
|
+
fontFamily?: string;
|
|
8
|
+
welcomeMessage?: string;
|
|
9
|
+
chatboxPlaceholder?: string;
|
|
10
|
+
backgroundBubbleColor?: string;
|
|
11
|
+
headerBackgroundColor?: string;
|
|
12
|
+
showHeader?: boolean;
|
|
13
|
+
rotatingPlaceholders?: string[];
|
|
14
|
+
presetQuestions?: string[];
|
|
15
|
+
chatbotLogo?: string;
|
|
16
|
+
assistantAvatar?: string;
|
|
17
|
+
approvalAccentColor?: string;
|
|
18
|
+
approveButtonColor?: string;
|
|
19
|
+
approveButtonHoverColor?: string;
|
|
20
|
+
rejectButtonHoverColor?: string;
|
|
21
|
+
clarificationAccentColor?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Accent color used for in-progress / live indicators in the reasoning
|
|
24
|
+
* panel: the running-state halo, animated caret, header wave, rail-flow
|
|
25
|
+
* gradient, and active-row wash. Defaults to `backgroundBubbleColor`
|
|
26
|
+
* (i.e. `--chat-primary`); set this independently when the brand
|
|
27
|
+
* primary is neutral, so activity stays distinguishable.
|
|
28
|
+
*/
|
|
29
|
+
activityAccentColor?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type { BrandingData as B };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {prepare,layout,clearCache}from'@chenglou/pretext';var u={spacing:{messageBetween:"1rem",messagePx:"0.875rem",messagePy:"0.625rem",container:"1rem",composerPadding:"1rem"},typography:{message:{fontSize:"1rem",lineHeight:1.5,letterSpacing:"0px"},caption:{fontSize:"0.875rem",lineHeight:1.4},small:{fontSize:"0.8125rem",lineHeight:1.3}},borderRadius:{message:"0.5rem",input:"1rem",panel:"0.5rem",button:"0.5rem",chip:"0.25rem"},colors:{primary:"var(--chat-primary, #106997)",secondary:"var(--chat-secondary, #56C18A)",error:"var(--chat-error, #B1001B)",warning:"var(--chat-warning, #DD9652)",activity:"var(--chat-activity, var(--chat-primary, #106997))",messageBg:"var(--chat-message-bg, rgba(0, 0, 0, 0.03))",userMessageBg:"var(--chat-user-message-bg, var(--chat-primary, #106997))",userMessageText:"var(--chat-user-message-text, #ffffff)",panelBg:"var(--chat-panel-bg, rgba(0, 0, 0, 0.02))",panelBorder:"var(--chat-panel-border, rgba(0, 0, 0, 0.06))",border:"var(--chat-border, rgba(0, 0, 0, 0.06))",borderHover:"var(--chat-border-hover, rgba(0, 0, 0, 0.12))",text:"var(--chat-text, #1D2033)",subtle:"var(--chat-text-subtle, rgba(0, 0, 0, 0.5))",placeholder:"var(--chat-placeholder, rgba(0, 0, 0, 0.4))",status:{pending:{main:"rgba(0, 0, 0, 0.3)",bg:"rgba(0, 0, 0, 0.05)"},running:{main:"rgb(99, 102, 241)",bg:"rgba(99, 102, 241, 0.08)"},completed:{main:"rgb(16, 185, 129)",bg:"rgba(16, 185, 129, 0.08)"},failed:{main:"rgb(239, 68, 68)",bg:"rgba(239, 68, 68, 0.08)"}}},shadows:{subtle:"0 1px 2px rgba(0, 0, 0, 0.04)",input:"0 2px 8px rgba(0, 0, 0, 0.08)",inputFocus:"0 4px 16px rgba(0, 0, 0, 0.12)",button:"0 1px 3px rgba(0, 0, 0, 0.08)",buttonHover:"0 2px 6px rgba(0, 0, 0, 0.12)",primary:"0 2px 6px rgba(16, 105, 151, 0.3)",primaryHover:"0 4px 12px rgba(16, 105, 151, 0.4)"},animations:{messageEntrance:{initial:{opacity:0,y:8},animate:{opacity:1,y:0},transition:{duration:.3,ease:[.4,0,.2,1]}},fadeIn:{initial:{opacity:0},animate:{opacity:1},transition:{duration:.2}}}};var h=16,x=h*u.typography.message.lineHeight,f=`400 ${h}px "Inter", "Assistant", -apple-system, sans-serif`,y=512,t=new Map;function v(e){let r=t.get(e);return r&&(t.delete(e),t.set(e,r)),r}function T(e,r){if(t.set(e,r),t.size>y){let a=t.keys().next().value;a!==void 0&&t.delete(a);}}function P(e,r,a={}){if(!e||r<=0)return {height:0,lineCount:0};if(typeof document>"u")return null;let n=a.font??f,s=a.lineHeightPx??x,i=Math.round(r),o=`${n}|${s}|${i}|${e}`,c=v(o);if(c)return c;try{let m=prepare(e,n),g=layout(m,i,s),p={height:g.height,lineCount:g.lineCount};return T(o,p),p}catch{return null}}function A(){t.clear();try{clearCache();}catch{}}export{u as a,P as b,A as c};//# sourceMappingURL=chunk-NG4HKXYB.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-NG4HKXYB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/styles/tokens.ts","../src/utils/measureMessage.ts"],"names":["chatTokens","DEFAULT_FONT_PX","DEFAULT_LINE_HEIGHT_PX","DEFAULT_FONT","CACHE_MAX","cache","cacheGet","key","hit","cacheSet","value","oldest","measureMessageHeight","text","maxWidth","options","font","lineHeightPx","snappedWidth","cached","prepared","prepare","result","layout","measured","clearMeasurementCache","clearCache"],"mappings":"0DAKO,IAAMA,EAAa,CACxB,OAAA,CAAS,CAEP,cAAA,CAAgB,MAAA,CAEhB,UAAW,UAAA,CAEX,SAAA,CAAW,WAEX,SAAA,CAAW,MAAA,CAEX,gBAAiB,MACnB,CAAA,CAEA,WAAY,CACV,OAAA,CAAS,CACP,QAAA,CAAU,MAAA,CACV,UAAA,CAAY,GAAA,CACZ,cAAe,KACjB,CAAA,CACA,QAAS,CACP,QAAA,CAAU,WACV,UAAA,CAAY,GACd,EACA,KAAA,CAAO,CACL,SAAU,WAAA,CACV,UAAA,CAAY,GACd,CACF,CAAA,CAEA,aAAc,CACZ,OAAA,CAAS,QAAA,CACT,KAAA,CAAO,OACP,KAAA,CAAO,QAAA,CACP,OAAQ,QAAA,CACR,IAAA,CAAM,SACR,CAAA,CAEA,MAAA,CAAQ,CAEN,OAAA,CAAS,8BAAA,CACT,UAAW,gCAAA,CACX,KAAA,CAAO,6BACP,OAAA,CAAS,8BAAA,CAST,SAAU,oDAAA,CAGV,SAAA,CAAW,8CACX,aAAA,CAAe,2DAAA,CACf,gBAAiB,wCAAA,CAGjB,OAAA,CAAS,4CACT,WAAA,CAAa,+CAAA,CAGb,OAAQ,yCAAA,CACR,WAAA,CAAa,gDAGb,IAAA,CAAM,2BAAA,CACN,OAAQ,6CAAA,CACR,WAAA,CAAa,8CAGb,MAAA,CAAQ,CACN,QAAS,CACP,IAAA,CAAM,oBAAA,CACN,EAAA,CAAI,qBACN,CAAA,CACA,OAAA,CAAS,CACP,IAAA,CAAM,mBAAA,CACN,GAAI,0BACN,CAAA,CACA,UAAW,CACT,IAAA,CAAM,oBACN,EAAA,CAAI,0BACN,EACA,MAAA,CAAQ,CACN,KAAM,kBAAA,CACN,EAAA,CAAI,yBACN,CACF,CACF,EAEA,OAAA,CAAS,CACP,OAAQ,+BAAA,CACR,KAAA,CAAO,gCACP,UAAA,CAAY,gCAAA,CACZ,OAAQ,+BAAA,CACR,WAAA,CAAa,gCACb,OAAA,CAAS,mCAAA,CACT,aAAc,oCAChB,CAAA,CAEA,WAAY,CACV,eAAA,CAAiB,CACf,OAAA,CAAS,CAAE,OAAA,CAAS,CAAA,CAAG,EAAG,CAAE,CAAA,CAC5B,QAAS,CAAE,OAAA,CAAS,EAAG,CAAA,CAAG,CAAE,EAC5B,UAAA,CAAY,CAAE,SAAU,EAAA,CAAK,IAAA,CAAM,CAAC,EAAA,CAAK,CAAA,CAAG,GAAK,CAAC,CAAE,CACtD,CAAA,CACA,MAAA,CAAQ,CACN,OAAA,CAAS,CAAE,QAAS,CAAE,CAAA,CACtB,QAAS,CAAE,OAAA,CAAS,CAAE,CAAA,CACtB,UAAA,CAAY,CAAE,QAAA,CAAU,EAAI,CAC9B,CACF,CACF,EC1FA,IAAMC,CAAAA,CAAkB,GAClBC,CAAAA,CACLD,CAAAA,CAAkBD,EAAW,UAAA,CAAW,OAAA,CAAQ,WAE3CG,CAAAA,CAAe,CAAA,IAAA,EAAOF,CAAe,CAAA,kDAAA,CAAA,CAErCG,CAAAA,CAAY,IACZC,CAAAA,CAAQ,IAAI,IAElB,SAASC,CAAAA,CAASC,EAAuC,CACxD,IAAMC,EAAMH,CAAAA,CAAM,GAAA,CAAIE,CAAG,CAAA,CACzB,OAAIC,IAEHH,CAAAA,CAAM,MAAA,CAAOE,CAAG,CAAA,CAChBF,CAAAA,CAAM,IAAIE,CAAAA,CAAKC,CAAG,CAAA,CAAA,CAEZA,CACR,CAEA,SAASC,CAAAA,CAASF,EAAaG,CAAAA,CAA2B,CAEzD,GADAL,CAAAA,CAAM,GAAA,CAAIE,EAAKG,CAAK,CAAA,CAChBL,EAAM,IAAA,CAAOD,CAAAA,CAAW,CAC3B,IAAMO,CAAAA,CAASN,EAAM,IAAA,EAAK,CAAE,MAAK,CAAE,KAAA,CAC/BM,IAAW,MAAA,EAAWN,CAAAA,CAAM,OAAOM,CAAM,EAC9C,CACD,CAOO,SAASC,EACfC,CAAAA,CACAC,CAAAA,CACAC,EAA0B,EAAC,CACL,CACtB,GAAI,CAACF,GAAQC,CAAAA,EAAY,CAAA,CAAG,OAAO,CAAE,OAAQ,CAAA,CAAG,SAAA,CAAW,CAAE,CAAA,CAC7D,GAAI,OAAO,QAAA,CAAa,GAAA,CAAa,OAAO,IAAA,CAE5C,IAAME,EAAOD,CAAAA,CAAQ,IAAA,EAAQZ,EACvBc,CAAAA,CAAeF,CAAAA,CAAQ,cAAgBb,CAAAA,CAGvCgB,CAAAA,CAAe,KAAK,KAAA,CAAMJ,CAAQ,EAClCP,CAAAA,CAAM,CAAA,EAAGS,CAAI,CAAA,CAAA,EAAIC,CAAY,IAAIC,CAAY,CAAA,CAAA,EAAIL,CAAI,CAAA,CAAA,CAErDM,CAAAA,CAASb,EAASC,CAAG,CAAA,CAC3B,GAAIY,CAAAA,CAAQ,OAAOA,EAEnB,GAAI,CACH,IAAMC,CAAAA,CAAWC,QAAQR,CAAAA,CAAMG,CAAI,EAC7BM,CAAAA,CAASC,MAAAA,CAAOH,EAAUF,CAAAA,CAAcD,CAAY,EACpDO,CAAAA,CAAyB,CAC9B,OAAQF,CAAAA,CAAO,MAAA,CACf,UAAWA,CAAAA,CAAO,SACnB,EACA,OAAAb,CAAAA,CAASF,EAAKiB,CAAQ,CAAA,CACfA,CACR,CAAA,KAAQ,CACP,OAAO,IACR,CACD,CAMO,SAASC,CAAAA,EAA8B,CAC7CpB,CAAAA,CAAM,KAAA,GACN,GAAI,CACHqB,aACD,CAAA,KAAQ,CAER,CACD","file":"chunk-NG4HKXYB.js","sourcesContent":["/**\n * Design tokens for the chat UI.\n * These map to CSS custom properties for easy theming.\n */\n\nexport const chatTokens = {\n spacing: {\n /** Vertical spacing between messages (16px) */\n messageBetween: \"1rem\",\n /** Message bubble horizontal padding (14px) */\n messagePx: \"0.875rem\",\n /** Message bubble vertical padding (10px) */\n messagePy: \"0.625rem\",\n /** Container padding (16px) */\n container: \"1rem\",\n /** Composer padding (16px) */\n composerPadding: \"1rem\",\n },\n\n typography: {\n message: {\n fontSize: \"1rem\",\n lineHeight: 1.5,\n letterSpacing: \"0px\",\n },\n caption: {\n fontSize: \"0.875rem\",\n lineHeight: 1.4,\n },\n small: {\n fontSize: \"0.8125rem\",\n lineHeight: 1.3,\n },\n },\n\n borderRadius: {\n message: \"0.5rem\",\n input: \"1rem\",\n panel: \"0.5rem\",\n button: \"0.5rem\",\n chip: \"0.25rem\",\n },\n\n colors: {\n // CSS custom property references\n primary: \"var(--chat-primary, #106997)\",\n secondary: \"var(--chat-secondary, #56C18A)\",\n error: \"var(--chat-error, #B1001B)\",\n warning: \"var(--chat-warning, #DD9652)\",\n\n /**\n * Accent for in-progress / live indicators in the reasoning panel:\n * running halo, animated caret, header wave, rail-flow gradient, and\n * active-row wash. Falls back to --chat-primary so existing usage is\n * unchanged. Override independently when the brand primary is neutral\n * and would otherwise blend into completed-state visuals.\n */\n activity: \"var(--chat-activity, var(--chat-primary, #106997))\",\n\n // Message backgrounds\n messageBg: \"var(--chat-message-bg, rgba(0, 0, 0, 0.03))\",\n userMessageBg: \"var(--chat-user-message-bg, var(--chat-primary, #106997))\",\n userMessageText: \"var(--chat-user-message-text, #ffffff)\",\n\n // Panel backgrounds\n panelBg: \"var(--chat-panel-bg, rgba(0, 0, 0, 0.02))\",\n panelBorder: \"var(--chat-panel-border, rgba(0, 0, 0, 0.06))\",\n\n // Borders\n border: \"var(--chat-border, rgba(0, 0, 0, 0.06))\",\n borderHover: \"var(--chat-border-hover, rgba(0, 0, 0, 0.12))\",\n\n // Text\n text: \"var(--chat-text, #1D2033)\",\n subtle: \"var(--chat-text-subtle, rgba(0, 0, 0, 0.5))\",\n placeholder: \"var(--chat-placeholder, rgba(0, 0, 0, 0.4))\",\n\n // Status colors\n status: {\n pending: {\n main: \"rgba(0, 0, 0, 0.3)\",\n bg: \"rgba(0, 0, 0, 0.05)\",\n },\n running: {\n main: \"rgb(99, 102, 241)\",\n bg: \"rgba(99, 102, 241, 0.08)\",\n },\n completed: {\n main: \"rgb(16, 185, 129)\",\n bg: \"rgba(16, 185, 129, 0.08)\",\n },\n failed: {\n main: \"rgb(239, 68, 68)\",\n bg: \"rgba(239, 68, 68, 0.08)\",\n },\n },\n },\n\n shadows: {\n subtle: \"0 1px 2px rgba(0, 0, 0, 0.04)\",\n input: \"0 2px 8px rgba(0, 0, 0, 0.08)\",\n inputFocus: \"0 4px 16px rgba(0, 0, 0, 0.12)\",\n button: \"0 1px 3px rgba(0, 0, 0, 0.08)\",\n buttonHover: \"0 2px 6px rgba(0, 0, 0, 0.12)\",\n primary: \"0 2px 6px rgba(16, 105, 151, 0.3)\",\n primaryHover: \"0 4px 12px rgba(16, 105, 151, 0.4)\",\n },\n\n animations: {\n messageEntrance: {\n initial: { opacity: 0, y: 8 },\n animate: { opacity: 1, y: 0 },\n transition: { duration: 0.3, ease: [0.4, 0, 0.2, 1] },\n },\n fadeIn: {\n initial: { opacity: 0 },\n animate: { opacity: 1 },\n transition: { duration: 0.2 },\n },\n },\n} as const;\n\nexport type ChatTokens = typeof chatTokens;\n","/**\n * Off-DOM text measurement for chat message bodies.\n *\n * Wraps `@chenglou/pretext` (canvas-based, no layout reflow) so callers can\n * compute a paragraph's pixel height before React renders it. Used to reserve\n * vertical space for streaming assistant messages and prevent scroll jitter.\n *\n * Notes:\n * - We avoid `system-ui` in the font shorthand because pretext is inaccurate\n * with it on macOS. The font family stays in sync with `--chat-font-family`\n * minus the `system-ui` token.\n * - The cache is a small FIFO keyed by `(text, font, maxWidth, lineHeight)`.\n * Chat threads are bounded so this stays cheap.\n */\n\nimport { clearCache, layout, prepare } from \"@chenglou/pretext\";\nimport { chatTokens } from \"../styles/tokens\";\n\nexport interface MeasureOptions {\n\t/** CSS font shorthand. Defaults to the chat message body font. */\n\tfont?: string;\n\t/** Line height in pixels. Defaults to message body fontSize × lineHeight. */\n\tlineHeightPx?: number;\n}\n\nexport interface MeasuredText {\n\theight: number;\n\tlineCount: number;\n}\n\nconst DEFAULT_FONT_PX = 16; // matches chatTokens.typography.message.fontSize \"1rem\"\nconst DEFAULT_LINE_HEIGHT_PX =\n\tDEFAULT_FONT_PX * chatTokens.typography.message.lineHeight;\n\nconst DEFAULT_FONT = `400 ${DEFAULT_FONT_PX}px \"Inter\", \"Assistant\", -apple-system, sans-serif`;\n\nconst CACHE_MAX = 512;\nconst cache = new Map<string, MeasuredText>();\n\nfunction cacheGet(key: string): MeasuredText | undefined {\n\tconst hit = cache.get(key);\n\tif (hit) {\n\t\t// LRU bump\n\t\tcache.delete(key);\n\t\tcache.set(key, hit);\n\t}\n\treturn hit;\n}\n\nfunction cacheSet(key: string, value: MeasuredText): void {\n\tcache.set(key, value);\n\tif (cache.size > CACHE_MAX) {\n\t\tconst oldest = cache.keys().next().value;\n\t\tif (oldest !== undefined) cache.delete(oldest);\n\t}\n}\n\n/**\n * Measure the rendered height of a plain-text message body without touching\n * the DOM. Returns `null` if measurement is not possible in this environment\n * (e.g. SSR, no canvas).\n */\nexport function measureMessageHeight(\n\ttext: string,\n\tmaxWidth: number,\n\toptions: MeasureOptions = {},\n): MeasuredText | null {\n\tif (!text || maxWidth <= 0) return { height: 0, lineCount: 0 };\n\tif (typeof document === \"undefined\") return null;\n\n\tconst font = options.font ?? DEFAULT_FONT;\n\tconst lineHeightPx = options.lineHeightPx ?? DEFAULT_LINE_HEIGHT_PX;\n\n\t// Snap maxWidth so subpixel container resizes share cache entries.\n\tconst snappedWidth = Math.round(maxWidth);\n\tconst key = `${font}|${lineHeightPx}|${snappedWidth}|${text}`;\n\n\tconst cached = cacheGet(key);\n\tif (cached) return cached;\n\n\ttry {\n\t\tconst prepared = prepare(text, font);\n\t\tconst result = layout(prepared, snappedWidth, lineHeightPx);\n\t\tconst measured: MeasuredText = {\n\t\t\theight: result.height,\n\t\t\tlineCount: result.lineCount,\n\t\t};\n\t\tcacheSet(key, measured);\n\t\treturn measured;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Drop pretext's internal caches and our LRU. Call after the active font\n * family changes (e.g. branding swap) so measurements use the new metrics.\n */\nexport function clearMeasurementCache(): void {\n\tcache.clear();\n\ttry {\n\t\tclearCache();\n\t} catch {\n\t\t/* pretext may not be loaded in SSR */\n\t}\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {createContext,useCallback,useMemo,useContext}from'react';import {jsx}from'react/jsx-runtime';var e=createContext(null);function f({children:t,messages:n,isStreaming:o=false,streamingMessageId:a=null,viewerRole:i="user",onSendMessage:r,onStopStreaming:s,onRetryLastMessage:l,customData:d,onVisualizationAction:C,resolveCommandToken:u}){let c=useCallback(async(v,h)=>{await r(v,h);},[r]),g=useMemo(()=>({messages:n,isStreaming:o,streamingMessageId:a,viewerRole:i,sendMessage:c,stopStreaming:s,retryLastMessage:l,customData:d,onVisualizationAction:C,resolveCommandToken:u}),[n,o,a,i,c,s,l,d,C,u]);return jsx(e.Provider,{value:g,children:t})}function R(){let t=useContext(e);if(!t)throw new Error("useChatContext must be used within a ChatProvider");return t}export{e as a,f as b,R as c};//# sourceMappingURL=chunk-NKLA5PPB.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-NKLA5PPB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context/ChatProvider.tsx"],"names":["ChatContext","createContext","ChatProvider","children","messages","isStreaming","streamingMessageId","viewerRole","onSendMessage","onStopStreaming","onRetryLastMessage","customData","onVisualizationAction","resolveCommandToken","sendMessage","useCallback","content","attachments","value","useMemo","jsx","useChatContext","context","useContext"],"mappings":"qGAuCA,IAAMA,CAAAA,CAAcC,aAAAA,CAAuC,IAAI,EAmBxD,SAASC,CAAAA,CAAa,CAC3B,QAAA,CAAAC,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,WAAA,CAAAC,CAAAA,CAAc,KAAA,CACd,kBAAA,CAAAC,EAAqB,IAAA,CACrB,UAAA,CAAAC,CAAAA,CAAa,MAAA,CACb,aAAA,CAAAC,CAAAA,CACA,eAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CAAAA,CACA,mBAAA,CAAAC,CACF,EAAsB,CACpB,IAAMC,CAAAA,CAAcC,WAAAA,CAClB,MAAOC,CAAAA,CAAiBC,CAAAA,GAAyB,CAC/C,MAAMT,CAAAA,CAAcQ,CAAAA,CAASC,CAAW,EAC1C,CAAA,CACA,CAACT,CAAa,CAChB,CAAA,CAEMU,CAAAA,CAAQC,OAAAA,CACZ,KAAO,CACL,QAAA,CAAAf,CAAAA,CACA,WAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,WAAA,CAAAO,CAAAA,CACA,aAAA,CAAeL,CAAAA,CACf,gBAAA,CAAkBC,CAAAA,CAClB,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CAAAA,CACA,mBAAA,CAAAC,CACF,CAAA,CAAA,CACA,CACET,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAO,CAAAA,CACAL,EACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CACF,CACF,CAAA,CAEA,OAAOO,GAAAA,CAACpB,CAAAA,CAAY,QAAA,CAAZ,CAAqB,KAAA,CAAOkB,CAAAA,CAAQ,QAAA,CAAAf,CAAAA,CAAS,CACvD,CAEO,SAASkB,CAAAA,EAAiB,CAC/B,IAAMC,CAAAA,CAAUC,UAAAA,CAAWvB,CAAW,CAAA,CACtC,GAAI,CAACsB,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,mDAAmD,CAAA,CAErE,OAAOA,CACT","file":"chunk-NKLA5PPB.js","sourcesContent":["import {\n createContext,\n useContext,\n useCallback,\n useMemo,\n type ReactNode,\n} from \"react\";\nimport type { ChatMessage, ParticipantRole, VisualizationActionEvent } from \"../types\";\n\nexport interface ChatContextValue {\n /** List of messages in the conversation */\n messages: ChatMessage[];\n /** Whether a message is currently being streamed */\n isStreaming: boolean;\n /** ID of the message currently being streamed */\n streamingMessageId: string | null;\n /** The viewer's role (determines message alignment) */\n viewerRole: ParticipantRole;\n /** Send a new message */\n sendMessage: (content: string, attachments?: File[]) => Promise<void>;\n /** Stop the current streaming response */\n stopStreaming?: () => void;\n /** Retry the last failed message */\n retryLastMessage?: () => Promise<void>;\n /** Custom data passed through context */\n customData?: Record<string, unknown>;\n /** Callback when user interacts with a visualization (form submit, card action, etc.) */\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n /** Resolve how to render an inline command-token chip (e.g. an\n * `@<id>:ad-account` mention). Wire format only carries id + kind, so the\n * host app supplies the display info. Returning `tag` replaces the default\n * uppercase kind pill (e.g. a platform logo). Returning `label` overrides\n * the id text. */\n resolveCommandToken?: (\n id: string,\n kind: string,\n ) => { label?: string; tag?: ReactNode } | undefined;\n}\n\nconst ChatContext = createContext<ChatContextValue | null>(null);\n\nexport interface ChatProviderProps {\n children: ReactNode;\n messages: ChatMessage[];\n isStreaming?: boolean;\n streamingMessageId?: string | null;\n viewerRole?: ParticipantRole;\n onSendMessage: (content: string, attachments?: File[]) => Promise<void>;\n onStopStreaming?: () => void;\n onRetryLastMessage?: () => Promise<void>;\n customData?: Record<string, unknown>;\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n resolveCommandToken?: (\n id: string,\n kind: string,\n ) => { label?: string; tag?: ReactNode } | undefined;\n}\n\nexport function ChatProvider({\n children,\n messages,\n isStreaming = false,\n streamingMessageId = null,\n viewerRole = \"user\",\n onSendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n resolveCommandToken,\n}: ChatProviderProps) {\n const sendMessage = useCallback(\n async (content: string, attachments?: File[]) => {\n await onSendMessage(content, attachments);\n },\n [onSendMessage]\n );\n\n const value = useMemo<ChatContextValue>(\n () => ({\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n stopStreaming: onStopStreaming,\n retryLastMessage: onRetryLastMessage,\n customData,\n onVisualizationAction,\n resolveCommandToken,\n }),\n [\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n resolveCommandToken,\n ]\n );\n\n return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;\n}\n\nexport function useChatContext() {\n const context = useContext(ChatContext);\n if (!context) {\n throw new Error(\"useChatContext must be used within a ChatProvider\");\n }\n return context;\n}\n\nexport { ChatContext };\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {useMemo}from'react';function l(t){let r=t.trim();if(r.startsWith("#")){let e=r.slice(1);if(e.length===3)return [parseInt(e[0]+e[0],16),parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16)];if(e.length>=6)return [parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)]}let o=t.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/);return o?[Number(o[1]),Number(o[2]),Number(o[3])]:null}function i(t,r,o){let[e,s,n]=[t/255,r/255,o/255].map(c=>c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4));return .2126*e+.7152*s+.0722*n}function a(t){let r=l(t);return r&&i(...r)>.179?"#1D2033":"#ffffff"}function C(t,r){return useMemo(()=>{if(!t&&!r?.iconColor)return {};let o={},e=t?.backgroundBubbleColor||r?.iconColor;return e&&(o["--chat-primary"]=e,o["--chat-user-message-bg"]=e,o["--chat-user-message-text"]=a(e)),t?.headerBackgroundColor&&(o["--chat-header-bg"]=t.headerBackgroundColor),t?.messageFontSize&&(o["--chat-message-font-size"]=`${t.messageFontSize}px`),t?.fontFamily&&(o["--chat-font-family"]=t.fontFamily),t?.approvalAccentColor&&(o["--chat-approval-accent"]=t.approvalAccentColor,o["--chat-approval-accent-soft"]=t.approvalAccentColor),t?.approveButtonColor&&(o["--chat-approve-bg"]=t.approveButtonColor,o["--chat-approve-bg-hover"]=t.approveButtonHoverColor||t.approveButtonColor),t?.rejectButtonHoverColor&&(o["--chat-reject-bg-hover"]=t.rejectButtonHoverColor),t?.clarificationAccentColor&&(o["--chat-clarification-accent"]=t.clarificationAccentColor,o["--chat-clarification-accent-soft"]=t.clarificationAccentColor),t?.activityAccentColor&&(o["--chat-activity"]=t.activityAccentColor),o},[t,r?.iconColor])}export{a,C as b};//# sourceMappingURL=chunk-QECTWT2H.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-QECTWT2H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/color-contrast.ts","../src/hooks/use-branding-css-vars.ts"],"names":["parseColor","color","hex","h","rgbMatch","relativeLuminance","r","g","b","rs","gs","bs","getContrastTextColor","bgColor","rgb","useBrandingCSSVars","branding","overrides","useMemo","vars","primaryColor"],"mappings":"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","file":"chunk-QECTWT2H.js","sourcesContent":["/**\n * Parse a CSS color string (hex, rgb, rgba) into [r, g, b] values.\n * Returns null if the color cannot be parsed.\n */\nfunction parseColor(color: string): [number, number, number] | null {\n // Hex: #RGB, #RRGGBB\n const hex = color.trim();\n if (hex.startsWith(\"#\")) {\n const h = hex.slice(1);\n if (h.length === 3) {\n return [\n parseInt(h[0] + h[0], 16),\n parseInt(h[1] + h[1], 16),\n parseInt(h[2] + h[2], 16),\n ];\n }\n if (h.length >= 6) {\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n }\n }\n\n // rgb(r, g, b) or rgba(r, g, b, a)\n const rgbMatch = color.match(\n /rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})/\n );\n if (rgbMatch) {\n return [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];\n }\n\n return null;\n}\n\n/**\n * Compute the relative luminance of an sRGB color per WCAG 2.0.\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n */\nfunction relativeLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)\n );\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Given a background color string, return the best contrast text color.\n * Returns \"#ffffff\" for dark backgrounds and \"#1D2033\" for light backgrounds.\n * Falls back to \"#ffffff\" if the color cannot be parsed.\n */\nexport function getContrastTextColor(bgColor: string): string {\n const rgb = parseColor(bgColor);\n if (!rgb) return \"#ffffff\";\n\n const lum = relativeLuminance(...rgb);\n // WCAG threshold: luminance > 0.179 means the background is \"light\"\n return lum > 0.179 ? \"#1D2033\" : \"#ffffff\";\n}\n","import { useMemo } from \"react\";\nimport type { BrandingData } from \"../types/branding\";\nimport { getContrastTextColor } from \"../utils/color-contrast\";\n\n/**\n * Converts BrandingData into CSS custom properties for chat-ui theming.\n * Returns a CSSProperties object that can be spread onto a container element.\n */\nexport function useBrandingCSSVars(\n branding: BrandingData | null | undefined,\n overrides?: { iconColor?: string }\n): React.CSSProperties {\n return useMemo(() => {\n if (!branding && !overrides?.iconColor) return {};\n\n const vars: Record<string, string> = {};\n\n const primaryColor =\n branding?.backgroundBubbleColor || overrides?.iconColor;\n if (primaryColor) {\n vars[\"--chat-primary\"] = primaryColor;\n vars[\"--chat-user-message-bg\"] = primaryColor;\n vars[\"--chat-user-message-text\"] = getContrastTextColor(primaryColor);\n }\n\n if (branding?.headerBackgroundColor) {\n vars[\"--chat-header-bg\"] = branding.headerBackgroundColor;\n }\n\n if (branding?.messageFontSize) {\n vars[\"--chat-message-font-size\"] = `${branding.messageFontSize}px`;\n }\n\n if (branding?.fontFamily) {\n vars[\"--chat-font-family\"] = branding.fontFamily;\n }\n\n if (branding?.approvalAccentColor) {\n vars[\"--chat-approval-accent\"] = branding.approvalAccentColor;\n vars[\"--chat-approval-accent-soft\"] = branding.approvalAccentColor;\n }\n\n if (branding?.approveButtonColor) {\n vars[\"--chat-approve-bg\"] = branding.approveButtonColor;\n vars[\"--chat-approve-bg-hover\"] =\n branding.approveButtonHoverColor || branding.approveButtonColor;\n }\n\n if (branding?.rejectButtonHoverColor) {\n vars[\"--chat-reject-bg-hover\"] = branding.rejectButtonHoverColor;\n }\n\n if (branding?.clarificationAccentColor) {\n vars[\"--chat-clarification-accent\"] = branding.clarificationAccentColor;\n vars[\"--chat-clarification-accent-soft\"] = branding.clarificationAccentColor;\n }\n\n // Activity accent is independent of brand primary so callers whose\n // primary is neutral can still surface a vivid in-progress signal in\n // the reasoning panel. Unset → falls back to --chat-primary at usage.\n if (branding?.activityAccentColor) {\n vars[\"--chat-activity\"] = branding.activityAccentColor;\n }\n\n return vars as React.CSSProperties;\n }, [branding, overrides?.iconColor]);\n}\n"]}
|