@runtypelabs/persona 3.3.0 → 3.5.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/index.cjs +31 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +47 -47
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +31 -31
- package/dist/index.js.map +1 -1
- package/dist/theme-reference.cjs +1 -0
- package/dist/theme-reference.d.cts +969 -0
- package/dist/theme-reference.d.ts +969 -0
- package/dist/theme-reference.js +1 -0
- package/package.json +8 -2
- package/src/components/panel.ts +3 -3
- package/src/theme-reference.ts +414 -0
- package/src/ui.ts +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e={colors:{primary:{50:"#eff6ff",100:"#dbeafe",200:"#bfdbfe",300:"#93c5fd",400:"#60a5fa",500:"#3b82f6",600:"#2563eb",700:"#1d4ed8",800:"#1e40af",900:"#1e3a8a",950:"#172554"},secondary:{50:"#f5f3ff",100:"#ede9fe",200:"#ddd6fe",300:"#c4b5fd",400:"#a78bfa",500:"#8b5cf6",600:"#7c3aed",700:"#6d28d9",800:"#5b21b6",900:"#4c1d95",950:"#2e1065"},accent:{50:"#ecfeff",100:"#cffafe",200:"#a5f3fc",300:"#67e8f9",400:"#22d3ee",500:"#06b6d4",600:"#0891b2",700:"#0e7490",800:"#155e75",900:"#164e63",950:"#083344"},gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827",950:"#030712"},success:{50:"#f0fdf4",100:"#dcfce7",200:"#bbf7d0",300:"#86efac",400:"#4ade80",500:"#22c55e",600:"#16a34a",700:"#15803d",800:"#166534",900:"#14532d"},warning:{50:"#fefce8",100:"#fef9c3",200:"#fef08a",300:"#fde047",400:"#facc15",500:"#eab308",600:"#ca8a04",700:"#a16207",800:"#854d0e",900:"#713f12"},error:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626",700:"#b91c1c",800:"#991b1b",900:"#7f1d1d"}},spacing:{0:"0px",1:"0.25rem",2:"0.5rem",3:"0.75rem",4:"1rem",5:"1.25rem",6:"1.5rem",8:"2rem",10:"2.5rem",12:"3rem",16:"4rem",20:"5rem",24:"6rem",32:"8rem",40:"10rem",48:"12rem",56:"14rem",64:"16rem"},typography:{fontFamily:{sans:'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',serif:'Georgia, Cambria, "Times New Roman", Times, serif',mono:"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"},fontSize:{xs:"0.75rem",sm:"0.875rem",base:"1rem",lg:"1.125rem",xl:"1.25rem","2xl":"1.5rem","3xl":"1.875rem","4xl":"2.25rem"},fontWeight:{normal:"400",medium:"500",semibold:"600",bold:"700"},lineHeight:{tight:"1.25",normal:"1.5",relaxed:"1.625"}},shadows:{none:"none",sm:"0 1px 2px 0 rgb(0 0 0 / 0.05)",md:"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",lg:"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",xl:"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)","2xl":"0 25px 50px -12px rgb(0 0 0 / 0.25)"},borders:{none:"none",sm:"1px solid",md:"2px solid",lg:"4px solid"},radius:{none:"0px",sm:"0.125rem",md:"0.375rem",lg:"0.5rem",xl:"0.75rem","2xl":"1rem",full:"9999px"}};var o={overview:"Persona uses a three-layer design token system: palette \u2192 semantic \u2192 components. Most themes only need palette.colors overrides \u2014 semantic and component layers auto-derive from palette values. Config also accepts non-theme appearance options (launcher, sendButton, toolCall, etc.).",layers:{palette:{description:"Primitive design tokens. Override specific shades to change the entire widget feel.",colors:{description:"7 color scales, each with shades 50 (lightest) to 950 (darkest). Override only the shades you need.",scales:{gray:"Neutrals \u2014 backgrounds, text, borders. Key shades: 50 (lightest bg), 100 (secondary bg), 200 (borders), 500 (muted text), 900 (primary text).",primary:"Brand color \u2014 buttons, links, interactive elements. Key shades: 500 (default), 600 (hover).",accent:"Secondary highlight. Key shades: 500 (default), 600 (hover).",secondary:"Tertiary color scale.",success:"Positive feedback (default: green).",warning:"Caution feedback (default: yellow).",error:"Error/danger feedback (default: red)."}},radius:{description:"Border radius scale. Add custom keys like launcher, button.",defaults:{none:"0px",sm:"0.125rem",md:"0.375rem",lg:"0.5rem",xl:"0.75rem","2xl":"1rem",full:"9999px"}},typography:{fontFamily:"Three stacks: sans (system-ui), serif (Georgia), mono (ui-monospace). Override individual stack values.",fontSize:"Scale: xs (0.75rem), sm (0.875rem), base (1rem), lg (1.125rem), xl (1.25rem), 2xl (1.5rem), 3xl (1.875rem), 4xl (2.25rem).",fontWeight:"normal (400), medium (500), semibold (600), bold (700).",lineHeight:"tight (1.25), normal (1.5), relaxed (1.625)."},shadows:"Scale: none, sm, md, lg, xl, 2xl. Values are CSS box-shadow strings.",borders:"Scale: none, sm (1px solid), md (2px solid), lg (4px solid).",spacing:"Scale: 0 (0px), 1 (0.25rem), 2 (0.5rem), 3 (0.75rem), 4 (1rem), 5 (1.25rem), 6 (1.5rem), 8 (2rem), 10 (2.5rem), 12 (3rem), 16-64."},semantic:{description:'Design intent tokens. Auto-derived from palette by default. Override to redirect token resolution. Values are token reference strings like "palette.colors.primary.500".',colors:{primary:"palette.colors.primary.500 \u2014 Primary brand color.",secondary:"palette.colors.gray.500 \u2014 Secondary color.",accent:"palette.colors.primary.600 \u2014 Accent/interactive color.",surface:"palette.colors.gray.50 \u2014 Panel/card backgrounds.",background:"palette.colors.gray.50 \u2014 Page background.",container:"palette.colors.gray.100 \u2014 Container backgrounds.",text:"palette.colors.gray.900 \u2014 Primary text.",textMuted:"palette.colors.gray.500 \u2014 Muted/secondary text.",textInverse:"palette.colors.gray.50 \u2014 Text on dark backgrounds.",border:"palette.colors.gray.200 \u2014 Default border color.",divider:"palette.colors.gray.200 \u2014 Divider lines.",interactive:{default:"palette.colors.primary.500",hover:"palette.colors.primary.600",focus:"palette.colors.primary.700",active:"palette.colors.primary.800",disabled:"palette.colors.gray.300"},feedback:{success:"palette.colors.success.500",warning:"palette.colors.warning.500",error:"palette.colors.error.500",info:"palette.colors.primary.500"}},spacing:"xs (0.25rem), sm (0.5rem), md (1rem), lg (1.5rem), xl (2rem), 2xl (2.5rem).",typography:"fontFamily, fontSize, fontWeight, lineHeight \u2014 reference palette typography tokens."},components:{description:"UI element tokens. Rarely needed for basic theming. Override for fine-grained control. All values are token references or raw CSS strings.",button:{description:"Three variants: primary, secondary, ghost.",properties:"background, foreground, borderRadius, padding."},input:{description:"Message input field.",properties:"background, placeholder, borderRadius, padding, focus.border, focus.ring."},launcher:{description:"Floating launcher button.",properties:"size (60px), iconSize (28px), borderRadius, shadow."},panel:{description:"Chat panel container.",properties:"width, maxWidth (400px), height (600px), maxHeight, borderRadius, shadow."},header:{description:"Chat panel header.",properties:"background, border, borderRadius, padding, iconBackground, iconForeground, titleForeground, subtitleForeground, actionIconForeground, shadow, borderBottom."},message:{description:"Chat message bubbles.",user:"background, text, borderRadius, shadow.",assistant:"background, text, borderRadius, border (optional), shadow (optional)."},markdown:{description:"Markdown rendering in messages and artifact pane.",properties:"inlineCode (background, foreground), link.foreground, prose.fontFamily, heading.h1/h2 (fontSize, fontWeight)."},voice:"recording (indicator, background, border), processing (icon, background), speaking (icon).",approval:"requested (background, border, text), approve (background, foreground), deny (background, foreground).",attachment:"image (background, border).",toolBubble:"shadow \u2014 tool call row box-shadow.",reasoningBubble:"shadow \u2014 reasoning/thinking row box-shadow.",composer:"shadow \u2014 message input form box-shadow.",artifact:"toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground)."}},colorScheme:'"dark" merges darkTheme overrides on top of theme. "auto" detects system preference or <html class="dark">. "light" is default. colorScheme does NOT auto-invert colors \u2014 provide dark palette and semantic overrides yourself.',plugins:{description:"Plugins transform theme tokens before resolution. Use with createTheme().",available:{brandPlugin:'Auto-generates full color scales from a single brand hex: brandPlugin({ colors: { primary: "#7c3aed" } }).',accessibilityPlugin:"Enhanced focus indicators and disabled states.",highContrastPlugin:"Increased contrast for visual accessibility.",reducedMotionPlugin:"Disables all animations (sets transitions to 0ms).",animationsPlugin:"Adds transition and easing tokens."},usage:'createTheme(themeOverrides, { plugins: [brandPlugin({ colors: { primary: "#7c3aed" } })] })'},widgetConfig:{description:'Non-theme config options on the widget config object that affect appearance. These are siblings of "theme" in the config, not nested inside it.',launcher:{description:"Floating launcher button and panel positioning.",properties:{enabled:"Show/hide the launcher button.",title:"Header title text.",subtitle:"Header subtitle text.",position:'"bottom-right" | "bottom-left" | "top-right" | "top-left".',width:"Chat panel width (CSS value).",fullHeight:"Fill full height of container.",mountMode:'"floating" | "docked".',agentIconText:"Emoji/text for agent icon.",border:"Border style for launcher button.",shadow:"Box shadow for launcher button.",collapsedMaxWidth:"Max-width for launcher pill when panel closed."}},sendButton:{description:"Send button appearance.",properties:"backgroundColor, textColor, borderWidth, borderColor, paddingX, paddingY, iconText, iconName, size."},closeButton:{description:"Close button (on launcher config).",properties:"closeButtonSize, closeButtonColor, closeButtonBackgroundColor, closeButtonBorderWidth, closeButtonBorderColor, closeButtonBorderRadius."},clearChat:{description:"Clear chat button (on launcher.clearChat config).",properties:"enabled, iconColor, backgroundColor, borderWidth, borderColor, borderRadius, size."},toolCall:{description:"Tool call display styling.",properties:"shadow, backgroundColor, borderColor, borderWidth, borderRadius, headerBackgroundColor, headerTextColor, headerPaddingX, headerPaddingY, contentBackgroundColor, contentTextColor, contentPaddingX, contentPaddingY, codeBlockBackgroundColor, codeBlockBorderColor, codeBlockTextColor, toggleTextColor, labelTextColor."},approval:{description:"Tool approval bubble styling and behavior. Set to false to disable.",properties:"backgroundColor, borderColor, titleColor, descriptionColor, approveButtonColor, approveButtonTextColor, denyButtonColor, denyButtonTextColor, parameterBackgroundColor, parameterTextColor, title, approveLabel, denyLabel."},copy:{description:"Widget text content.",properties:"showWelcomeCard (boolean), welcomeTitle, welcomeSubtitle, inputPlaceholder, sendButtonLabel."},voiceRecognition:{description:"Voice input configuration.",properties:"enabled, pauseDuration, iconColor, backgroundColor."},textToSpeech:{description:"Text-to-speech for assistant messages.",properties:'enabled, provider ("browser" | "runtype"), browserFallback, voice, rate, pitch.'},suggestionChips:"string[] \u2014 Suggested prompts shown to the user.",messageActions:{description:"Message action buttons (copy, upvote, downvote).",properties:'enabled, showCopy, showUpvote, showDownvote, visibility ("hover" | "always"), align ("left" | "center" | "right"), layout ("pill-inside" | "row-inside").'},attachments:{description:"File attachment configuration.",properties:"enabled, allowedTypes (string[]), maxFileSize (bytes), maxFiles, buttonIconName, buttonTooltipText."},markdown:{description:"Markdown rendering configuration.",properties:"options (gfm, breaks, headerIds, headerPrefix, pedantic, mangle, silent), disableDefaultStyles."},layout:{description:"Layout configuration.",showHeader:"boolean \u2014 show/hide the header section entirely.",showFooter:"boolean \u2014 show/hide the footer/composer section entirely.",contentMaxWidth:'CSS width value for centering content (e.g. "720px", "90ch").',header:'"default" | "minimal". Options: showIcon, showTitle, showSubtitle, showCloseButton, showClearChat.',messages:'"bubble" | "flat" | "minimal". Options: groupConsecutive, avatar (show, position, userAvatar, assistantAvatar), timestamp (show, position).'},statusIndicator:{description:"Status text shown below the composer.",properties:{visible:"Show/hide the status indicator.",align:'"left" | "center" | "right" \u2014 text alignment (default: "right").',idleText:'Text shown when idle (default: "Online").',idleLink:"URL to open when idle text is clicked (wraps text in a link).",connectingText:'Text shown while connecting (default: "Connecting\u2026").',connectedText:'Text shown while streaming (default: "Streaming\u2026").',errorText:'Text shown on error (default: "Offline").'}},features:{description:"Feature flags.",properties:"showReasoning (AI thinking steps), showToolCalls (tool invocations), artifacts (sidebar config)."}}},r={darkIndigo:{description:"Dark mode with indigo accent \u2014 override grays for dark backgrounds and semantic tokens for inverted text/surface",theme:{palette:{colors:{primary:{500:"#6366f1",600:"#4f46e5"},gray:{50:"#f1f5f9",100:"#1e293b",200:"#334155",500:"#94a3b8",900:"#0f172a",950:"#020617"}}},semantic:{colors:{surface:"palette.colors.gray.900",background:"palette.colors.gray.900",container:"palette.colors.gray.100",text:"palette.colors.gray.50",textMuted:"palette.colors.gray.500",textInverse:"palette.colors.gray.900",border:"palette.colors.gray.200"}}}},warmVintage:{description:"Warm sepia tones with serif font and subtle radius",theme:{palette:{colors:{primary:{500:"#b45309",600:"#92400e"},gray:{50:"#fef3c7",100:"#fef9c3",200:"#d6d3d1",500:"#78716c",900:"#44403c"}},radius:{sm:"0.125rem",md:"0.25rem",lg:"0.375rem"},typography:{fontFamily:{sans:'Georgia, Cambria, "Times New Roman", Times, serif'}}}}},neonCyberpunk:{description:"Neon on dark with monospace font \u2014 full semantic override for dark background",theme:{palette:{colors:{primary:{500:"#f0abfc",600:"#e879f9"},accent:{500:"#22d3ee",600:"#06b6d4"},gray:{50:"#f0abfc",100:"#1e0a3c",200:"#3b0764",500:"#c084fc",900:"#0c0a1a",950:"#050412"}},radius:{sm:"0",md:"0.25rem",lg:"0.375rem"},typography:{fontFamily:{sans:"ui-monospace, SFMono-Regular, Menlo, monospace"}}},semantic:{colors:{surface:"palette.colors.gray.900",background:"palette.colors.gray.950",container:"palette.colors.gray.100",text:"palette.colors.gray.50",textMuted:"palette.colors.gray.500",border:"palette.colors.gray.200"}}}},cleanRounded:{description:"Clean light theme with large radius and panel styling",theme:{palette:{radius:{sm:"6px",md:"8px",lg:"12px",launcher:"50px",button:"8px"}},components:{panel:{borderRadius:"16px",shadow:"palette.shadows.2xl"}}}}};function n(){return{tokenDocs:o,defaultColorPalette:e.colors,defaultRadius:e.radius,examples:r,sdkPresets:["shop","minimal","fullscreen"]}}export{r as THEME_EXAMPLES,o as THEME_TOKEN_DOCS,n as getThemeReference};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
14
|
},
|
|
15
|
+
"./theme-reference": {
|
|
16
|
+
"types": "./dist/theme-reference.d.ts",
|
|
17
|
+
"import": "./dist/theme-reference.js",
|
|
18
|
+
"require": "./dist/theme-reference.cjs"
|
|
19
|
+
},
|
|
15
20
|
"./widget.css": {
|
|
16
21
|
"import": "./widget.css",
|
|
17
22
|
"default": "./dist/widget.css"
|
|
@@ -70,7 +75,8 @@
|
|
|
70
75
|
"access": "public"
|
|
71
76
|
},
|
|
72
77
|
"scripts": {
|
|
73
|
-
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer",
|
|
78
|
+
"build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:theme-ref",
|
|
79
|
+
"build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
|
|
74
80
|
"build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
|
|
75
81
|
"build:client": "tsup src/index.ts --format esm,cjs,iife --global-name AgentWidget --minify --sourcemap --splitting false --dts --loader \".css=text\"",
|
|
76
82
|
"build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
|
package/src/components/panel.ts
CHANGED
|
@@ -164,14 +164,14 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
164
164
|
messagesWrapper.style.width = "100%";
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
introCard.setAttribute("data-persona-intro-card", "");
|
|
167
168
|
const showWelcomeCard = config?.copy?.showWelcomeCard !== false;
|
|
168
169
|
if (!showWelcomeCard) {
|
|
170
|
+
introCard.style.display = "none";
|
|
169
171
|
body.classList.remove("persona-gap-6");
|
|
170
172
|
body.classList.add("persona-gap-3");
|
|
171
|
-
body.append(messagesWrapper);
|
|
172
|
-
} else {
|
|
173
|
-
body.append(introCard, messagesWrapper);
|
|
174
173
|
}
|
|
174
|
+
body.append(introCard, messagesWrapper);
|
|
175
175
|
|
|
176
176
|
// Build composer/footer using extracted builder
|
|
177
177
|
const composerElements: ComposerElements = buildComposer({ config });
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Reference — Structured documentation and examples for the Persona v2 theme system.
|
|
3
|
+
*
|
|
4
|
+
* Exported via the `@runtypelabs/persona/theme-reference` entry point so it stays
|
|
5
|
+
* out of the IIFE widget bundle. Intended for AI/MCP tool consumption.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DEFAULT_PALETTE } from './utils/tokens'
|
|
9
|
+
import type { DeepPartial, PersonaTheme } from './types/theme'
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Token System Documentation
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export const THEME_TOKEN_DOCS = {
|
|
16
|
+
overview:
|
|
17
|
+
'Persona uses a three-layer design token system: palette → semantic → components. Most themes only need palette.colors overrides — semantic and component layers auto-derive from palette values. Config also accepts non-theme appearance options (launcher, sendButton, toolCall, etc.).',
|
|
18
|
+
|
|
19
|
+
layers: {
|
|
20
|
+
palette: {
|
|
21
|
+
description:
|
|
22
|
+
'Primitive design tokens. Override specific shades to change the entire widget feel.',
|
|
23
|
+
colors: {
|
|
24
|
+
description:
|
|
25
|
+
'7 color scales, each with shades 50 (lightest) to 950 (darkest). Override only the shades you need.',
|
|
26
|
+
scales: {
|
|
27
|
+
gray: 'Neutrals — backgrounds, text, borders. Key shades: 50 (lightest bg), 100 (secondary bg), 200 (borders), 500 (muted text), 900 (primary text).',
|
|
28
|
+
primary:
|
|
29
|
+
'Brand color — buttons, links, interactive elements. Key shades: 500 (default), 600 (hover).',
|
|
30
|
+
accent: 'Secondary highlight. Key shades: 500 (default), 600 (hover).',
|
|
31
|
+
secondary: 'Tertiary color scale.',
|
|
32
|
+
success: 'Positive feedback (default: green).',
|
|
33
|
+
warning: 'Caution feedback (default: yellow).',
|
|
34
|
+
error: 'Error/danger feedback (default: red).',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
radius: {
|
|
38
|
+
description: 'Border radius scale. Add custom keys like launcher, button.',
|
|
39
|
+
defaults: {
|
|
40
|
+
none: '0px',
|
|
41
|
+
sm: '0.125rem',
|
|
42
|
+
md: '0.375rem',
|
|
43
|
+
lg: '0.5rem',
|
|
44
|
+
xl: '0.75rem',
|
|
45
|
+
'2xl': '1rem',
|
|
46
|
+
full: '9999px',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
typography: {
|
|
50
|
+
fontFamily:
|
|
51
|
+
'Three stacks: sans (system-ui), serif (Georgia), mono (ui-monospace). Override individual stack values.',
|
|
52
|
+
fontSize:
|
|
53
|
+
'Scale: xs (0.75rem), sm (0.875rem), base (1rem), lg (1.125rem), xl (1.25rem), 2xl (1.5rem), 3xl (1.875rem), 4xl (2.25rem).',
|
|
54
|
+
fontWeight: 'normal (400), medium (500), semibold (600), bold (700).',
|
|
55
|
+
lineHeight: 'tight (1.25), normal (1.5), relaxed (1.625).',
|
|
56
|
+
},
|
|
57
|
+
shadows:
|
|
58
|
+
'Scale: none, sm, md, lg, xl, 2xl. Values are CSS box-shadow strings.',
|
|
59
|
+
borders: 'Scale: none, sm (1px solid), md (2px solid), lg (4px solid).',
|
|
60
|
+
spacing:
|
|
61
|
+
'Scale: 0 (0px), 1 (0.25rem), 2 (0.5rem), 3 (0.75rem), 4 (1rem), 5 (1.25rem), 6 (1.5rem), 8 (2rem), 10 (2.5rem), 12 (3rem), 16-64.',
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
semantic: {
|
|
65
|
+
description:
|
|
66
|
+
'Design intent tokens. Auto-derived from palette by default. Override to redirect token resolution. Values are token reference strings like "palette.colors.primary.500".',
|
|
67
|
+
colors: {
|
|
68
|
+
primary: 'palette.colors.primary.500 — Primary brand color.',
|
|
69
|
+
secondary: 'palette.colors.gray.500 — Secondary color.',
|
|
70
|
+
accent: 'palette.colors.primary.600 — Accent/interactive color.',
|
|
71
|
+
surface: 'palette.colors.gray.50 — Panel/card backgrounds.',
|
|
72
|
+
background: 'palette.colors.gray.50 — Page background.',
|
|
73
|
+
container: 'palette.colors.gray.100 — Container backgrounds.',
|
|
74
|
+
text: 'palette.colors.gray.900 — Primary text.',
|
|
75
|
+
textMuted: 'palette.colors.gray.500 — Muted/secondary text.',
|
|
76
|
+
textInverse: 'palette.colors.gray.50 — Text on dark backgrounds.',
|
|
77
|
+
border: 'palette.colors.gray.200 — Default border color.',
|
|
78
|
+
divider: 'palette.colors.gray.200 — Divider lines.',
|
|
79
|
+
interactive: {
|
|
80
|
+
default: 'palette.colors.primary.500',
|
|
81
|
+
hover: 'palette.colors.primary.600',
|
|
82
|
+
focus: 'palette.colors.primary.700',
|
|
83
|
+
active: 'palette.colors.primary.800',
|
|
84
|
+
disabled: 'palette.colors.gray.300',
|
|
85
|
+
},
|
|
86
|
+
feedback: {
|
|
87
|
+
success: 'palette.colors.success.500',
|
|
88
|
+
warning: 'palette.colors.warning.500',
|
|
89
|
+
error: 'palette.colors.error.500',
|
|
90
|
+
info: 'palette.colors.primary.500',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
spacing:
|
|
94
|
+
'xs (0.25rem), sm (0.5rem), md (1rem), lg (1.5rem), xl (2rem), 2xl (2.5rem).',
|
|
95
|
+
typography:
|
|
96
|
+
'fontFamily, fontSize, fontWeight, lineHeight — reference palette typography tokens.',
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
components: {
|
|
100
|
+
description:
|
|
101
|
+
'UI element tokens. Rarely needed for basic theming. Override for fine-grained control. All values are token references or raw CSS strings.',
|
|
102
|
+
button: {
|
|
103
|
+
description: 'Three variants: primary, secondary, ghost.',
|
|
104
|
+
properties: 'background, foreground, borderRadius, padding.',
|
|
105
|
+
},
|
|
106
|
+
input: {
|
|
107
|
+
description: 'Message input field.',
|
|
108
|
+
properties:
|
|
109
|
+
'background, placeholder, borderRadius, padding, focus.border, focus.ring.',
|
|
110
|
+
},
|
|
111
|
+
launcher: {
|
|
112
|
+
description: 'Floating launcher button.',
|
|
113
|
+
properties: 'size (60px), iconSize (28px), borderRadius, shadow.',
|
|
114
|
+
},
|
|
115
|
+
panel: {
|
|
116
|
+
description: 'Chat panel container.',
|
|
117
|
+
properties:
|
|
118
|
+
'width, maxWidth (400px), height (600px), maxHeight, borderRadius, shadow.',
|
|
119
|
+
},
|
|
120
|
+
header: {
|
|
121
|
+
description: 'Chat panel header.',
|
|
122
|
+
properties:
|
|
123
|
+
'background, border, borderRadius, padding, iconBackground, iconForeground, titleForeground, subtitleForeground, actionIconForeground, shadow, borderBottom.',
|
|
124
|
+
},
|
|
125
|
+
message: {
|
|
126
|
+
description: 'Chat message bubbles.',
|
|
127
|
+
user: 'background, text, borderRadius, shadow.',
|
|
128
|
+
assistant:
|
|
129
|
+
'background, text, borderRadius, border (optional), shadow (optional).',
|
|
130
|
+
},
|
|
131
|
+
markdown: {
|
|
132
|
+
description: 'Markdown rendering in messages and artifact pane.',
|
|
133
|
+
properties:
|
|
134
|
+
'inlineCode (background, foreground), link.foreground, prose.fontFamily, heading.h1/h2 (fontSize, fontWeight).',
|
|
135
|
+
},
|
|
136
|
+
voice:
|
|
137
|
+
'recording (indicator, background, border), processing (icon, background), speaking (icon).',
|
|
138
|
+
approval:
|
|
139
|
+
'requested (background, border, text), approve (background, foreground), deny (background, foreground).',
|
|
140
|
+
attachment: 'image (background, border).',
|
|
141
|
+
toolBubble: 'shadow — tool call row box-shadow.',
|
|
142
|
+
reasoningBubble: 'shadow — reasoning/thinking row box-shadow.',
|
|
143
|
+
composer: 'shadow — message input form box-shadow.',
|
|
144
|
+
artifact:
|
|
145
|
+
'toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground).',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
colorScheme:
|
|
150
|
+
'"dark" merges darkTheme overrides on top of theme. "auto" detects system preference or <html class="dark">. "light" is default. colorScheme does NOT auto-invert colors — provide dark palette and semantic overrides yourself.',
|
|
151
|
+
|
|
152
|
+
plugins: {
|
|
153
|
+
description:
|
|
154
|
+
'Plugins transform theme tokens before resolution. Use with createTheme().',
|
|
155
|
+
available: {
|
|
156
|
+
brandPlugin:
|
|
157
|
+
'Auto-generates full color scales from a single brand hex: brandPlugin({ colors: { primary: "#7c3aed" } }).',
|
|
158
|
+
accessibilityPlugin:
|
|
159
|
+
'Enhanced focus indicators and disabled states.',
|
|
160
|
+
highContrastPlugin: 'Increased contrast for visual accessibility.',
|
|
161
|
+
reducedMotionPlugin:
|
|
162
|
+
'Disables all animations (sets transitions to 0ms).',
|
|
163
|
+
animationsPlugin: 'Adds transition and easing tokens.',
|
|
164
|
+
},
|
|
165
|
+
usage:
|
|
166
|
+
'createTheme(themeOverrides, { plugins: [brandPlugin({ colors: { primary: "#7c3aed" } })] })',
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
widgetConfig: {
|
|
170
|
+
description:
|
|
171
|
+
'Non-theme config options on the widget config object that affect appearance. These are siblings of "theme" in the config, not nested inside it.',
|
|
172
|
+
launcher: {
|
|
173
|
+
description: 'Floating launcher button and panel positioning.',
|
|
174
|
+
properties: {
|
|
175
|
+
enabled: 'Show/hide the launcher button.',
|
|
176
|
+
title: 'Header title text.',
|
|
177
|
+
subtitle: 'Header subtitle text.',
|
|
178
|
+
position:
|
|
179
|
+
'"bottom-right" | "bottom-left" | "top-right" | "top-left".',
|
|
180
|
+
width: 'Chat panel width (CSS value).',
|
|
181
|
+
fullHeight: 'Fill full height of container.',
|
|
182
|
+
mountMode: '"floating" | "docked".',
|
|
183
|
+
agentIconText: 'Emoji/text for agent icon.',
|
|
184
|
+
border: 'Border style for launcher button.',
|
|
185
|
+
shadow: 'Box shadow for launcher button.',
|
|
186
|
+
collapsedMaxWidth: 'Max-width for launcher pill when panel closed.',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
sendButton: {
|
|
190
|
+
description: 'Send button appearance.',
|
|
191
|
+
properties:
|
|
192
|
+
'backgroundColor, textColor, borderWidth, borderColor, paddingX, paddingY, iconText, iconName, size.',
|
|
193
|
+
},
|
|
194
|
+
closeButton: {
|
|
195
|
+
description: 'Close button (on launcher config).',
|
|
196
|
+
properties:
|
|
197
|
+
'closeButtonSize, closeButtonColor, closeButtonBackgroundColor, closeButtonBorderWidth, closeButtonBorderColor, closeButtonBorderRadius.',
|
|
198
|
+
},
|
|
199
|
+
clearChat: {
|
|
200
|
+
description: 'Clear chat button (on launcher.clearChat config).',
|
|
201
|
+
properties:
|
|
202
|
+
'enabled, iconColor, backgroundColor, borderWidth, borderColor, borderRadius, size.',
|
|
203
|
+
},
|
|
204
|
+
toolCall: {
|
|
205
|
+
description: 'Tool call display styling.',
|
|
206
|
+
properties:
|
|
207
|
+
'shadow, backgroundColor, borderColor, borderWidth, borderRadius, headerBackgroundColor, headerTextColor, headerPaddingX, headerPaddingY, contentBackgroundColor, contentTextColor, contentPaddingX, contentPaddingY, codeBlockBackgroundColor, codeBlockBorderColor, codeBlockTextColor, toggleTextColor, labelTextColor.',
|
|
208
|
+
},
|
|
209
|
+
approval: {
|
|
210
|
+
description:
|
|
211
|
+
'Tool approval bubble styling and behavior. Set to false to disable.',
|
|
212
|
+
properties:
|
|
213
|
+
'backgroundColor, borderColor, titleColor, descriptionColor, approveButtonColor, approveButtonTextColor, denyButtonColor, denyButtonTextColor, parameterBackgroundColor, parameterTextColor, title, approveLabel, denyLabel.',
|
|
214
|
+
},
|
|
215
|
+
copy: {
|
|
216
|
+
description: 'Widget text content.',
|
|
217
|
+
properties:
|
|
218
|
+
'showWelcomeCard (boolean), welcomeTitle, welcomeSubtitle, inputPlaceholder, sendButtonLabel.',
|
|
219
|
+
},
|
|
220
|
+
voiceRecognition: {
|
|
221
|
+
description: 'Voice input configuration.',
|
|
222
|
+
properties: 'enabled, pauseDuration, iconColor, backgroundColor.',
|
|
223
|
+
},
|
|
224
|
+
textToSpeech: {
|
|
225
|
+
description: 'Text-to-speech for assistant messages.',
|
|
226
|
+
properties:
|
|
227
|
+
'enabled, provider ("browser" | "runtype"), browserFallback, voice, rate, pitch.',
|
|
228
|
+
},
|
|
229
|
+
suggestionChips:
|
|
230
|
+
'string[] — Suggested prompts shown to the user.',
|
|
231
|
+
messageActions: {
|
|
232
|
+
description: 'Message action buttons (copy, upvote, downvote).',
|
|
233
|
+
properties:
|
|
234
|
+
'enabled, showCopy, showUpvote, showDownvote, visibility ("hover" | "always"), align ("left" | "center" | "right"), layout ("pill-inside" | "row-inside").',
|
|
235
|
+
},
|
|
236
|
+
attachments: {
|
|
237
|
+
description: 'File attachment configuration.',
|
|
238
|
+
properties:
|
|
239
|
+
'enabled, allowedTypes (string[]), maxFileSize (bytes), maxFiles, buttonIconName, buttonTooltipText.',
|
|
240
|
+
},
|
|
241
|
+
markdown: {
|
|
242
|
+
description: 'Markdown rendering configuration.',
|
|
243
|
+
properties:
|
|
244
|
+
'options (gfm, breaks, headerIds, headerPrefix, pedantic, mangle, silent), disableDefaultStyles.',
|
|
245
|
+
},
|
|
246
|
+
layout: {
|
|
247
|
+
description: 'Layout configuration.',
|
|
248
|
+
showHeader: 'boolean — show/hide the header section entirely.',
|
|
249
|
+
showFooter: 'boolean — show/hide the footer/composer section entirely.',
|
|
250
|
+
contentMaxWidth:
|
|
251
|
+
'CSS width value for centering content (e.g. "720px", "90ch").',
|
|
252
|
+
header:
|
|
253
|
+
'"default" | "minimal". Options: showIcon, showTitle, showSubtitle, showCloseButton, showClearChat.',
|
|
254
|
+
messages:
|
|
255
|
+
'"bubble" | "flat" | "minimal". Options: groupConsecutive, avatar (show, position, userAvatar, assistantAvatar), timestamp (show, position).',
|
|
256
|
+
},
|
|
257
|
+
statusIndicator: {
|
|
258
|
+
description: 'Status text shown below the composer.',
|
|
259
|
+
properties: {
|
|
260
|
+
visible: 'Show/hide the status indicator.',
|
|
261
|
+
align: '"left" | "center" | "right" — text alignment (default: "right").',
|
|
262
|
+
idleText: 'Text shown when idle (default: "Online").',
|
|
263
|
+
idleLink: 'URL to open when idle text is clicked (wraps text in a link).',
|
|
264
|
+
connectingText: 'Text shown while connecting (default: "Connecting…").',
|
|
265
|
+
connectedText: 'Text shown while streaming (default: "Streaming…").',
|
|
266
|
+
errorText: 'Text shown on error (default: "Offline").',
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
features: {
|
|
270
|
+
description: 'Feature flags.',
|
|
271
|
+
properties:
|
|
272
|
+
'showReasoning (AI thinking steps), showToolCalls (tool invocations), artifacts (sidebar config).',
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Example Themes
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
export interface ThemeExample {
|
|
282
|
+
description: string
|
|
283
|
+
colorScheme?: 'light' | 'dark' | 'auto'
|
|
284
|
+
theme: DeepPartial<PersonaTheme>
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export const THEME_EXAMPLES: Record<string, ThemeExample> = {
|
|
288
|
+
darkIndigo: {
|
|
289
|
+
description:
|
|
290
|
+
'Dark mode with indigo accent — override grays for dark backgrounds and semantic tokens for inverted text/surface',
|
|
291
|
+
theme: {
|
|
292
|
+
palette: {
|
|
293
|
+
colors: {
|
|
294
|
+
primary: { 500: '#6366f1', 600: '#4f46e5' },
|
|
295
|
+
gray: {
|
|
296
|
+
50: '#f1f5f9',
|
|
297
|
+
100: '#1e293b',
|
|
298
|
+
200: '#334155',
|
|
299
|
+
500: '#94a3b8',
|
|
300
|
+
900: '#0f172a',
|
|
301
|
+
950: '#020617',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
semantic: {
|
|
306
|
+
colors: {
|
|
307
|
+
surface: 'palette.colors.gray.900',
|
|
308
|
+
background: 'palette.colors.gray.900',
|
|
309
|
+
container: 'palette.colors.gray.100',
|
|
310
|
+
text: 'palette.colors.gray.50',
|
|
311
|
+
textMuted: 'palette.colors.gray.500',
|
|
312
|
+
textInverse: 'palette.colors.gray.900',
|
|
313
|
+
border: 'palette.colors.gray.200',
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
warmVintage: {
|
|
319
|
+
description: 'Warm sepia tones with serif font and subtle radius',
|
|
320
|
+
theme: {
|
|
321
|
+
palette: {
|
|
322
|
+
colors: {
|
|
323
|
+
primary: { 500: '#b45309', 600: '#92400e' },
|
|
324
|
+
gray: {
|
|
325
|
+
50: '#fef3c7',
|
|
326
|
+
100: '#fef9c3',
|
|
327
|
+
200: '#d6d3d1',
|
|
328
|
+
500: '#78716c',
|
|
329
|
+
900: '#44403c',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
radius: { sm: '0.125rem', md: '0.25rem', lg: '0.375rem' },
|
|
333
|
+
typography: {
|
|
334
|
+
fontFamily: {
|
|
335
|
+
sans: 'Georgia, Cambria, "Times New Roman", Times, serif',
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
neonCyberpunk: {
|
|
342
|
+
description:
|
|
343
|
+
'Neon on dark with monospace font — full semantic override for dark background',
|
|
344
|
+
theme: {
|
|
345
|
+
palette: {
|
|
346
|
+
colors: {
|
|
347
|
+
primary: { 500: '#f0abfc', 600: '#e879f9' },
|
|
348
|
+
accent: { 500: '#22d3ee', 600: '#06b6d4' },
|
|
349
|
+
gray: {
|
|
350
|
+
50: '#f0abfc',
|
|
351
|
+
100: '#1e0a3c',
|
|
352
|
+
200: '#3b0764',
|
|
353
|
+
500: '#c084fc',
|
|
354
|
+
900: '#0c0a1a',
|
|
355
|
+
950: '#050412',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
radius: { sm: '0', md: '0.25rem', lg: '0.375rem' },
|
|
359
|
+
typography: {
|
|
360
|
+
fontFamily: {
|
|
361
|
+
sans: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
semantic: {
|
|
366
|
+
colors: {
|
|
367
|
+
surface: 'palette.colors.gray.900',
|
|
368
|
+
background: 'palette.colors.gray.950',
|
|
369
|
+
container: 'palette.colors.gray.100',
|
|
370
|
+
text: 'palette.colors.gray.50',
|
|
371
|
+
textMuted: 'palette.colors.gray.500',
|
|
372
|
+
border: 'palette.colors.gray.200',
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
cleanRounded: {
|
|
378
|
+
description: 'Clean light theme with large radius and panel styling',
|
|
379
|
+
theme: {
|
|
380
|
+
palette: {
|
|
381
|
+
radius: {
|
|
382
|
+
sm: '6px',
|
|
383
|
+
md: '8px',
|
|
384
|
+
lg: '12px',
|
|
385
|
+
launcher: '50px',
|
|
386
|
+
button: '8px',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
components: {
|
|
390
|
+
panel: { borderRadius: '16px', shadow: 'palette.shadows.2xl' },
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Reference Payload
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Complete theme reference payload for AI / MCP tool consumption.
|
|
402
|
+
*
|
|
403
|
+
* Returns token system docs, the default color palette and radius scale,
|
|
404
|
+
* example themes, and a list of SDK-bundled presets.
|
|
405
|
+
*/
|
|
406
|
+
export function getThemeReference() {
|
|
407
|
+
return {
|
|
408
|
+
tokenDocs: THEME_TOKEN_DOCS,
|
|
409
|
+
defaultColorPalette: DEFAULT_PALETTE.colors,
|
|
410
|
+
defaultRadius: DEFAULT_PALETTE.radius,
|
|
411
|
+
examples: THEME_EXAMPLES,
|
|
412
|
+
sdkPresets: ['shop', 'minimal', 'fullscreen'],
|
|
413
|
+
}
|
|
414
|
+
}
|
package/src/ui.ts
CHANGED
|
@@ -2624,6 +2624,20 @@ export const createAgentExperience = (
|
|
|
2624
2624
|
"Ask anything about your account or products.";
|
|
2625
2625
|
textarea.placeholder = config.copy?.inputPlaceholder ?? "How can I help...";
|
|
2626
2626
|
|
|
2627
|
+
// Toggle welcome card visibility
|
|
2628
|
+
const introCard = body.querySelector("[data-persona-intro-card]") as HTMLElement | null;
|
|
2629
|
+
if (introCard) {
|
|
2630
|
+
const showCard = config.copy?.showWelcomeCard !== false;
|
|
2631
|
+
introCard.style.display = showCard ? "" : "none";
|
|
2632
|
+
if (showCard) {
|
|
2633
|
+
body.classList.remove("persona-gap-3");
|
|
2634
|
+
body.classList.add("persona-gap-6");
|
|
2635
|
+
} else {
|
|
2636
|
+
body.classList.remove("persona-gap-6");
|
|
2637
|
+
body.classList.add("persona-gap-3");
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2627
2641
|
// Only update send button text if NOT using icon mode
|
|
2628
2642
|
const useIcon = config.sendButton?.useIcon ?? false;
|
|
2629
2643
|
if (!useIcon) {
|
|
@@ -4824,6 +4838,30 @@ export const createAgentExperience = (
|
|
|
4824
4838
|
tooltip.style.display = "none";
|
|
4825
4839
|
}
|
|
4826
4840
|
|
|
4841
|
+
// Update contentMaxWidth on messages wrapper and composer
|
|
4842
|
+
const updatedContentMaxWidth = config.layout?.contentMaxWidth;
|
|
4843
|
+
if (updatedContentMaxWidth) {
|
|
4844
|
+
messagesWrapper.style.maxWidth = updatedContentMaxWidth;
|
|
4845
|
+
messagesWrapper.style.marginLeft = "auto";
|
|
4846
|
+
messagesWrapper.style.marginRight = "auto";
|
|
4847
|
+
messagesWrapper.style.width = "100%";
|
|
4848
|
+
if (composerForm) {
|
|
4849
|
+
composerForm.style.maxWidth = updatedContentMaxWidth;
|
|
4850
|
+
composerForm.style.marginLeft = "auto";
|
|
4851
|
+
composerForm.style.marginRight = "auto";
|
|
4852
|
+
}
|
|
4853
|
+
} else {
|
|
4854
|
+
messagesWrapper.style.maxWidth = "";
|
|
4855
|
+
messagesWrapper.style.marginLeft = "";
|
|
4856
|
+
messagesWrapper.style.marginRight = "";
|
|
4857
|
+
messagesWrapper.style.width = "";
|
|
4858
|
+
if (composerForm) {
|
|
4859
|
+
composerForm.style.maxWidth = "";
|
|
4860
|
+
composerForm.style.marginLeft = "";
|
|
4861
|
+
composerForm.style.marginRight = "";
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4827
4865
|
// Update status indicator visibility and text
|
|
4828
4866
|
const statusIndicatorConfig = config.statusIndicator ?? {};
|
|
4829
4867
|
const isVisible = statusIndicatorConfig.visible ?? true;
|
|
@@ -4841,6 +4879,13 @@ export const createAgentExperience = (
|
|
|
4841
4879
|
};
|
|
4842
4880
|
applyStatusToElement(statusText, getCurrentStatusText(currentStatus), statusIndicatorConfig, currentStatus);
|
|
4843
4881
|
}
|
|
4882
|
+
|
|
4883
|
+
// Update status text alignment
|
|
4884
|
+
statusText.classList.remove("persona-text-left", "persona-text-center", "persona-text-right");
|
|
4885
|
+
const alignClass = statusIndicatorConfig.align === "left" ? "persona-text-left"
|
|
4886
|
+
: statusIndicatorConfig.align === "center" ? "persona-text-center"
|
|
4887
|
+
: "persona-text-right";
|
|
4888
|
+
statusText.classList.add(alignClass);
|
|
4844
4889
|
},
|
|
4845
4890
|
open() {
|
|
4846
4891
|
if (!launcherEnabled) return;
|