/** * The assistant widget is a package-owned shared fragment. Its chrome ships * with soma-ai beside that fragment; Hosts only install this published asset. * * This is deliberately separate from the generated-page Tailwind sheet: * generated pages use a closed vocabulary, while the assistant owns a stable * interactive DOM contract and must remain usable when that sheet changes. */ export const SOMA_AI_ASSISTANT_WIDGET_STYLESHEET_ID = 'soma-ai-assistant-widget-stylesheet'; export const SOMA_AI_ASSISTANT_WIDGET_CSS = ` #ai-assistant-widget { position: fixed; right: 1rem; bottom: 1rem; /* Stay above the shared sticky navbar (z-index 80) while remaining below modal/toast layers owned by soma-ui. */ z-index: 90; display: flex; flex-direction: column; align-items: flex-end; gap: .75rem; color: var(--soma-text-primary, #1f2937); font-family: var(--soma-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif); /* Keep the widget on the active SOMA typography axis. A fixed 14px body follows the shared SOMA typography axis without depending on generated page CSS. */ font-size: var(--soma-font-size-md, 1rem); line-height: var(--soma-line-height, 1.5715); isolation: isolate; } #ai-assistant-widget, #ai-assistant-widget * { box-sizing: border-box; } #ai-assistant-widget:not([data-mounted="true"]) { display: none; } #ai-assistant-widget [hidden] { display: none !important; } #ai-assistant-panel { position: relative; display: flex; flex-direction: column; width: min(420px, calc(100vw - 1.5rem)); /* dvh: the panel is sized to fit what is visible right now, so it must follow the toolbar instead of the toolbar-collapsed viewport. */ height: min(720px, calc(100dvh - 6.5rem)); min-height: 0; overflow: hidden; border: 1px solid var(--soma-border, #e5e7eb); border-radius: 1rem; background: var(--soma-bg-container, #fff); color: var(--soma-text-primary, #1f2937); box-shadow: 0 12px 40px rgb(15 23 42 / 16%); } #ai-assistant-panel > header { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between; min-height: 3.25rem; padding: .75rem 1rem; border-bottom: 1px solid var(--soma-divider, #eef2f7); } #ai-assistant-panel > header > div:first-child, #ai-assistant-panel > header > div:last-child { display: flex; align-items: center; } #ai-assistant-panel > header > div:first-child { min-width: 0; gap: .5rem; } #ai-assistant-panel > header > div:last-child { gap: .125rem; } #ai-assistant-panel button { /* The host may ship a button reset with a stronger selector. Set only the family here; size/weight belong to each control below so a button's typography cannot silently win over the assistant's text scale. */ font-family: inherit; cursor: pointer; } #ai-assistant-panel button:disabled { cursor: not-allowed; opacity: .5; } #ai-assistant-panel > header button, #ai-assistant-history-close { display: inline-flex; align-items: center; justify-content: center; width: 2rem; height: 2rem; padding: 0; border: 0; border-radius: .5rem; background: transparent; color: #6b7280; } #ai-assistant-panel > header button:hover, #ai-assistant-history-close:hover { background: #f3f4f6; color: #111827; } #ai-assistant-panel > header img { display: block; width: var(--soma-ui-icon-xl, 1.5rem); height: var(--soma-ui-icon-xl, 1.5rem); object-fit: contain; } #ai-assistant-panel > header > div:first-child > span:first-child { display: flex; flex: 0 0 auto; align-items: center; justify-content: center; width: 2rem; height: 2rem; overflow: hidden; border-radius: 999px; background: #eff6ff; box-shadow: 0 0 0 1px #bfdbfe; } #ai-assistant-title { min-width: 0; overflow: hidden; color: #111827; font-size: var(--soma-font-size-md, 1rem); font-weight: var(--soma-font-weight-bold, 600); line-height: var(--soma-line-height-tight, 1.25); text-overflow: ellipsis; white-space: nowrap; } #ai-assistant-history-panel { position: absolute; inset-inline: 0; top: 3.25rem; bottom: 0; z-index: 10; display: flex; flex-direction: column; border-top: 1px solid var(--soma-divider, #eef2f7); background: var(--soma-bg-container, #fff); } #ai-assistant-history-panel > div:first-child { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between; padding: .625rem 1rem; border-bottom: 1px solid var(--soma-divider, #eef2f7); } #ai-assistant-history-panel > div:first-child > span { color: #111827; font-size: var(--soma-font-size-sm, .875rem); font-weight: var(--soma-font-weight-medium, 500); line-height: var(--soma-line-height-tight, 1.25); } #ai-assistant-history-list { min-height: 0; flex: 1 1 auto; overflow-y: auto; padding: .75rem; } #ai-assistant-history-list > div { display: flex; align-items: center; gap: .25rem; padding: .375rem .5rem; border-radius: .75rem; } #ai-assistant-history-list > div:hover { background: #f9fafb; } #ai-assistant-history-list > div > button { border: 0; background: transparent; color: #9ca3af; } #ai-assistant-history-list > div > button:first-child { min-width: 0; flex: 1 1 auto; padding: .25rem; color: var(--soma-text-primary, #374151); font-size: var(--soma-font-size-sm, .875rem); line-height: var(--soma-line-height, 1.5715); text-align: left; } #ai-assistant-history-list > div > button:last-child { display: inline-flex; align-items: center; justify-content: center; width: 1.75rem; height: 1.75rem; padding: 0; border-radius: .375rem; } #ai-assistant-history-list > div > button:last-child:hover { background: #fee2e2; color: #dc2626; } #ai-assistant-messages { min-height: 0; display: flex; flex: 1 1 auto; flex-direction: column; gap: .625rem; overflow-y: auto; padding: .75rem 1rem; background: var(--soma-bg-container, #fff); } #ai-assistant-messages [data-role="welcome"] { display: flex; flex-direction: column; gap: .75rem; padding-top: .25rem; } #ai-assistant-messages [data-role="welcome"] p { margin: 0; } #ai-assistant-messages [data-role="welcome"] > div:first-child > p:first-child { color: #111827; font-size: var(--soma-font-size-lg, 1.125rem); font-weight: var(--soma-font-weight-bold, 600); line-height: var(--soma-line-height-tight, 1.25); } #ai-assistant-messages [data-role="welcome"] > div:first-child > p:nth-child(2), #ai-assistant-messages [data-role="suggested-prompts"] > p { color: #6b7280; font-size: var(--soma-font-size-sm, .875rem); line-height: var(--soma-line-height, 1.5715); } #ai-assistant-messages [data-role="suggested-prompts"] { display: flex; flex-direction: column; gap: .5rem; } #ai-assistant-messages [data-role="quick-prompt"] { padding: .625rem .75rem; border: 1px solid #e5e7eb; border-radius: .75rem; background: #f9fafb; color: #374151; font-size: var(--soma-font-size-sm, .875rem); font-weight: var(--soma-font-weight-normal, 400); line-height: var(--soma-line-height, 1.5715); text-align: left; transition: border-color .15s ease, background-color .15s ease, color .15s ease; } #ai-assistant-messages [data-role="quick-prompt"]:hover { border-color: #bfdbfe; background: #eff6ff; color: #1d4ed8; } #ai-assistant-messages .ai-msg { flex: 0 0 auto; padding: .5rem .875rem; border-radius: 1rem; font-size: var(--soma-font-size-sm, .875rem); line-height: var(--soma-line-height, 1.5715); overflow-wrap: break-word; } #ai-assistant-messages .ai-msg[data-role="user"] { align-self: flex-end; max-width: calc(100% - 2.5rem); margin-left: 2.5rem; background: #2563eb; color: #fff; box-shadow: 0 1px 2px rgb(15 23 42 / 8%); } #ai-assistant-messages .ai-msg[data-role="assistant"] { align-self: flex-start; max-width: calc(100% - 1.5rem); margin-right: 1.5rem; border: 1px solid #eef2f7; background: #f8fafc; color: #1f2937; } #ai-assistant-messages .ai-msg-text p { margin: 0 0 .5rem; } #ai-assistant-messages .ai-msg-text p:last-child { margin-bottom: 0; } #ai-assistant-messages .ai-msg-text pre { max-width: 100%; margin: .5rem 0; overflow-x: auto; padding: .625rem .75rem; border-radius: .5rem; background: #111827; color: #f9fafb; font-size: .75rem; line-height: 1.25rem; white-space: pre-wrap; } #ai-assistant-messages .ai-msg-text code { padding: .125rem .25rem; border-radius: .25rem; background: rgb(15 23 42 / 8%); font-size: .875em; } #ai-assistant-messages .ai-msg-text pre code { padding: 0; background: transparent; } #ai-assistant-messages .ai-msg-text ul, #ai-assistant-messages .ai-msg-text ol { margin: .5rem 0; padding-inline-start: 1.25rem; } #ai-assistant-messages .ai-msg-text a { color: #1d4ed8; text-decoration: underline; } #ai-assistant-messages .ai-msg-tools { margin-top: .375rem; } #ai-assistant-messages .ai-tool { border: 1px solid #e5e7eb; border-radius: .5rem; background: #f9fafb; color: #4b5563; font-size: var(--soma-font-size-xs, .75rem); line-height: var(--soma-line-height-tight, 1.25); } #ai-assistant-messages .ai-tool summary { padding: .25rem .5rem; cursor: pointer; } #ai-assistant-status { flex: 0 0 auto; padding: .5rem 1rem; border-top: 1px solid #fff7ed; background: #fff7ed; color: #9a3412; font-size: var(--soma-font-size-xs, .75rem); line-height: var(--soma-line-height, 1.5715); } #ai-assistant-form { flex: 0 0 auto; padding: .75rem; border-top: 1px solid var(--soma-divider, #eef2f7); background: var(--soma-bg-container, #fff); } #ai-assistant-form > div { padding: .5rem .75rem; border: 1px solid #e5e7eb; border-radius: 1rem; background: #fafafa; } #ai-assistant-form > div:focus-within { border-color: #93c5fd; background: #fff; box-shadow: 0 0 0 2px #dbeafe; } #ai-assistant-input { display: block; width: 100%; min-height: 2.5rem; max-height: 7rem; padding: 0; resize: none; border: 0; outline: 0; background: transparent; color: #111827; font-size: var(--soma-font-size-sm, .875rem); line-height: var(--soma-line-height, 1.5715); } #ai-assistant-input::placeholder { color: #9ca3af; } #ai-assistant-form > div > div { display: flex; align-items: center; justify-content: space-between; gap: .5rem; margin-top: .25rem; } #ai-assistant-form > div > div > span { color: var(--soma-text-secondary, #4b5563); font-size: var(--soma-font-size-xs, .75rem); line-height: var(--soma-line-height-tight, 1.25); } #ai-assistant-form > div > div > div { display: flex; align-items: center; gap: .5rem; } #ai-assistant-panel #ai-assistant-send, #ai-assistant-panel #ai-assistant-stop { height: var(--soma-ui-control-h-sm, 2rem); min-height: var(--soma-ui-control-h-sm, 2rem); padding: 0 var(--soma-ui-control-pad-sm, .625rem); border-radius: 999px; font-size: var(--soma-ui-control-font-sm, .75rem); font-weight: var(--soma-font-weight-medium, 500); line-height: var(--soma-line-height-tight, 1.25); } #ai-assistant-send { display: inline-flex; align-items: center; gap: .25rem; border: 0; background: #2563eb; color: #fff; } #ai-assistant-send:hover { background: #1d4ed8; } #ai-assistant-stop { border: 1px solid #fecaca; background: #fff; color: #dc2626; } #ai-assistant-stop:hover { background: #fef2f2; } #ai-assistant-send svg { width: var(--soma-ui-icon-sm, .875rem); height: var(--soma-ui-icon-sm, .875rem); } #ai-assistant-fab { position: relative; display: flex; align-items: center; justify-content: center; width: 3.5rem; height: 3.5rem; padding: 0; overflow: hidden; border: 0; border-radius: 999px; background: #fff; box-shadow: 0 8px 24px rgb(37 99 235 / 28%); cursor: pointer; outline: 0; transition: filter .15s ease, transform .15s ease, box-shadow .15s ease; } #ai-assistant-fab:hover { filter: brightness(1.05); } #ai-assistant-fab:active { transform: scale(.9); box-shadow: 0 4px 12px rgb(37 99 235 / 22%); } #ai-assistant-fab:focus-visible { box-shadow: 0 0 0 2px #fff, 0 0 0 4px #93c5fd, 0 8px 24px rgb(37 99 235 / 28%); } #ai-assistant-fab > span:first-child { display: flex; align-items: center; justify-content: center; } #ai-assistant-fab img { display: block; width: 2.75rem; height: 2.75rem; object-fit: contain; } #ai-assistant-badge:not([hidden]) { position: absolute; top: -.125rem; right: -.125rem; display: block; width: .75rem; height: .75rem; border: 2px solid #fff; border-radius: 999px; background: #ef4444; } #ai-assistant-widget .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } @media (max-width: 640px) { #ai-assistant-widget { right: .75rem; bottom: .75rem; } #ai-assistant-panel { position: fixed; inset-inline: .5rem; top: 3.5rem; bottom: 4.5rem; width: auto; height: auto; border-radius: 1rem; } #ai-assistant-fab { width: 3rem; height: 3rem; } #ai-assistant-fab img { width: 2.25rem; height: 2.25rem; } } `; export function ensureSomaAiAssistantWidgetStylesheet( doc: Document = document, ): HTMLStyleElement { const existing = doc.getElementById(SOMA_AI_ASSISTANT_WIDGET_STYLESHEET_ID); if (existing && existing.tagName !== 'STYLE') { existing.remove(); } const style = existing?.tagName === 'STYLE' ? existing as HTMLStyleElement : doc.createElement('style'); style.id = SOMA_AI_ASSISTANT_WIDGET_STYLESHEET_ID; style.dataset.somaAiAssistantWidget = 'package'; style.removeAttribute('media'); const nonce = doc.querySelector('meta[name="csp-nonce"]')?.getAttribute('content')?.trim(); if (nonce && style.nonce !== nonce) { style.setAttribute('nonce', nonce); style.nonce = nonce; } if (style.textContent !== SOMA_AI_ASSISTANT_WIDGET_CSS) { style.textContent = SOMA_AI_ASSISTANT_WIDGET_CSS; } if (!style.isConnected) doc.head.appendChild(style); return style; }