tunio-agent-widget 0.1.0 → 0.1.1
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/README.md +0 -12
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,17 +10,6 @@ A Next.js demo app for the Tunio on‑air control widget. The widget is a compac
|
|
|
10
10
|
- `src/app/page.tsx` — demo page that mounts the widget.
|
|
11
11
|
- `src/app/fonts.ts` + `src/app/globals.css` — typography setup (Roboto / Russo One, etc.).
|
|
12
12
|
|
|
13
|
-
## Widget structure ("player")
|
|
14
|
-
|
|
15
|
-
The widget is **not** an audio player. It’s a control surface that sends natural‑language commands to the Tunio agent and shows streaming responses.
|
|
16
|
-
|
|
17
|
-
UI sections:
|
|
18
|
-
|
|
19
|
-
- Header: title/subtitle + status pill (always Online/Онлайн).
|
|
20
|
-
- Message list: system, user, and assistant bubbles (assistant supports Markdown).
|
|
21
|
-
- Composer: quick prompt pills + textarea + send button.
|
|
22
|
-
- Typing indicator: animated 3‑dot bounce while the agent streams.
|
|
23
|
-
|
|
24
13
|
## How streaming works
|
|
25
14
|
|
|
26
15
|
The widget expects the agent API to respond as **Server‑Sent Events (SSE)** over a `POST` request.
|
|
@@ -48,7 +37,6 @@ event: message
|
|
|
48
37
|
The widget accumulates `event: message` chunks and updates the last assistant bubble in real time.
|
|
49
38
|
|
|
50
39
|
## Props
|
|
51
|
-
|
|
52
40
|
```ts
|
|
53
41
|
export type TunioWidgetProps = {
|
|
54
42
|
apiUrl?: string;
|
package/dist/index.cjs
CHANGED
|
@@ -62,7 +62,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// src/components/TunioWidget.module.css
|
|
65
|
-
styleInject('
|
|
65
|
+
styleInject('.tw-widget {\n --panel: #f7f9fc;\n --ink: #1b2430;\n --muted: #6c7a90;\n --accent: #3f6df6;\n --accent-soft: rgba(63, 109, 246, 0.14);\n --teal: #21b3a8;\n --surface: #ffffff;\n --border: rgba(16, 24, 40, 0.12);\n --widget-bg:\n linear-gradient(\n 135deg,\n #f9fbff 0%,\n #eef2f8 60%,\n #e8edf6 100%);\n --header-bg:\n linear-gradient(\n \n 120deg,\n rgba(255, 255, 255, 0.92),\n rgba(243, 246, 252, 0.82) );\n --composer-bg:\n linear-gradient(\n \n 180deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(244, 246, 252, 0.92) 80% );\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\n --glow:\n radial-gradient(\n circle,\n rgba(63, 109, 246, 0.18) 0%,\n rgba(63, 109, 246, 0) 70%);\n --accent-glow: rgba(63, 109, 246, 0.2);\n --bubble-user: #1b2430;\n --bubble-user-text: #f7f9ff;\n --bubble-assistant: #ffffff;\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\n --bubble-system: rgba(27, 36, 48, 0.08);\n --bubble-system-text: #6c7a90;\n --input-focus-border: rgba(63, 109, 246, 0.6);\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\n --button-bg: #1b2430;\n --button-text: #f7f9ff;\n --button-shadow: rgba(27, 36, 48, 0.2);\n --pill-border: rgba(16, 24, 40, 0.2);\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n width: min(100%, 420px);\n height: min(80vh, 640px);\n min-height: 520px;\n border-radius: 12px;\n background: var(--widget-bg);\n border: 1px solid var(--border);\n box-shadow: var(--shadow);\n color: var(--ink);\n overflow: hidden;\n position: relative;\n}\n.tw-light {\n color-scheme: light;\n}\n.tw-dark {\n color-scheme: dark;\n --panel: #121826;\n --ink: #e6edf7;\n --muted: #a5b3c8;\n --accent: #4a7dff;\n --accent-soft: rgba(74, 125, 255, 0.18);\n --teal: #2db9c3;\n --surface: #1a2232;\n --border: rgba(230, 237, 247, 0.12);\n --widget-bg:\n linear-gradient(\n 135deg,\n #131a2a 0%,\n #101525 60%,\n #0d1220 100%);\n --header-bg:\n linear-gradient(\n \n 120deg,\n rgba(18, 24, 38, 0.95),\n rgba(23, 30, 48, 0.85) );\n --composer-bg:\n linear-gradient(\n \n 180deg,\n rgba(18, 24, 38, 0) 0%,\n rgba(18, 24, 38, 0.9) 80% );\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\n --glow:\n radial-gradient(\n circle,\n rgba(74, 125, 255, 0.2) 0%,\n rgba(74, 125, 255, 0) 70%);\n --accent-glow: rgba(74, 125, 255, 0.28);\n --bubble-user: #2f3e63;\n --bubble-user-text: #f4f7ff;\n --bubble-assistant: #1a2233;\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\n --bubble-system: rgba(230, 237, 247, 0.08);\n --bubble-system-text: #b7c3d7;\n --input-focus-border: rgba(74, 125, 255, 0.6);\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\n --button-bg: #4a7dff;\n --button-text: #f4f7ff;\n --button-shadow: rgba(74, 125, 255, 0.35);\n --pill-border: rgba(230, 237, 247, 0.24);\n}\n.tw-widget::before {\n content: "";\n position: absolute;\n inset: -120px 40% auto -60px;\n height: 220px;\n background: var(--glow);\n pointer-events: none;\n}\n.tw-header {\n padding: 22px 26px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--header-bg);\n backdrop-filter: blur(12px);\n}\n.tw-brand {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.tw-brand-title {\n font-family: var(--font-display);\n font-size: 24px;\n letter-spacing: -0.02em;\n}\n.tw-brand-subtitle {\n color: var(--muted);\n font-size: 13px;\n}\n.tw-status {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border-radius: 8px;\n background: var(--accent-soft);\n color: var(--ink);\n font-size: 12px;\n font-weight: 600;\n}\n.tw-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 4px var(--accent-glow);\n}\n.tw-messages {\n padding: 16px 22px 8px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n overflow-y: auto;\n min-height: 0;\n scroll-behavior: smooth;\n}\n.tw-message {\n max-width: 85%;\n padding: 14px 16px;\n border-radius: 8px;\n line-height: 1.45;\n font-size: 14px;\n animation: fadeUp 0.25s ease;\n}\n.tw-message p {\n margin: 0;\n}\n.tw-message p + p {\n margin-top: 8px;\n}\n.tw-message a {\n color: var(--accent);\n text-decoration: none;\n font-weight: 600;\n}\n.tw-message a:hover {\n text-decoration: underline;\n}\n.tw-message strong {\n font-weight: 700;\n}\n.tw-message ul,\n.tw-message ol {\n margin: 8px 0 0;\n padding-left: 18px;\n}\n.tw-message li {\n margin: 4px 0;\n}\n.tw-typing {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-height: 16px;\n color: var(--muted);\n}\n.tw-typing-dot {\n width: 6px;\n height: 6px;\n border-radius: 999px;\n background: currentColor;\n opacity: 0.6;\n animation: typingBounce 1.2s infinite ease-in-out;\n}\n.tw-typing-dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.tw-typing-dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n.tw-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.tw-user {\n align-self: flex-end;\n background: var(--bubble-user);\n color: var(--bubble-user-text);\n border-bottom-right-radius: 8px;\n}\n.tw-assistant {\n align-self: flex-start;\n background: var(--bubble-assistant);\n border: 1px solid var(--bubble-assistant-border);\n border-bottom-left-radius: 8px;\n}\n.tw-system {\n align-self: center;\n max-width: 92%;\n text-align: center;\n background: var(--bubble-system);\n color: var(--bubble-system-text);\n}\n.tw-composer {\n padding: 16px 22px 22px;\n display: grid;\n gap: 10px;\n background: var(--composer-bg);\n}\n.tw-input-wrap {\n display: flex;\n gap: 10px;\n align-items: flex-end;\n}\n.tw-textarea {\n flex: 1;\n min-height: 54px;\n max-height: 140px;\n padding: 14px 16px;\n border-radius: 8px;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--ink);\n resize: none;\n font-family: inherit;\n font-size: 14px;\n outline: none;\n transition: border 0.2s ease, box-shadow 0.2s ease;\n}\n.tw-textarea::placeholder {\n color: var(--muted);\n opacity: 0.85;\n}\n.tw-textarea:focus {\n border-color: var(--input-focus-border);\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\n}\n.tw-send {\n border: none;\n border-radius: 8px;\n padding: 12px 18px;\n background: var(--button-bg);\n color: var(--button-text);\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n.tw-send:hover {\n transform: translateY(-1px);\n box-shadow: 0 12px 24px var(--button-shadow);\n}\n.tw-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n.tw-meta {\n display: flex;\n justify-content: space-between;\n color: var(--muted);\n font-size: 12px;\n}\n.tw-pill-row {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n.tw-pill {\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px dashed var(--pill-border);\n color: var(--muted);\n font-size: 11px;\n}\n@keyframes fadeUp {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@keyframes typingBounce {\n 0%, 80%, 100% {\n transform: translateY(0);\n opacity: 0.4;\n }\n 40% {\n transform: translateY(-4px);\n opacity: 0.9;\n }\n}\n@media (max-width: 720px) {\n .tw-widget {\n width: 100%;\n height: 100vh;\n border-radius: 12px;\n }\n}\n');
|
|
66
66
|
|
|
67
67
|
// src/i18n/translations.ts
|
|
68
68
|
var en = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/TunioWidget.tsx","#style-inject:#style-inject","../src/components/TunioWidget.module.css","../src/i18n/translations.ts"],"sourcesContent":["export { TunioWidget } from './components/TunioWidget';\nexport type { TunioWidgetProps } from './components/TunioWidget';\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport './TunioWidget.module.css';\nimport { getTranslations } from '../i18n/translations';\n\ntype ChatMessage = {\n id: string;\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport type TunioWidgetProps = {\n apiUrl?: string;\n sessionTokenUrl?: string;\n lang?: string;\n getSessionToken?: () => Promise<string>;\n accessToken?: string;\n title?: string;\n subtitle?: string;\n theme?: 'light' | 'dark';\n className?: string;\n onNavigate?: (href: string) => void;\n};\n\nconst createId = () => Math.random().toString(36).slice(2);\n\nexport function TunioWidget({\n apiUrl,\n sessionTokenUrl,\n lang,\n getSessionToken,\n accessToken,\n title,\n subtitle,\n theme = 'light',\n className,\n onNavigate,\n}: TunioWidgetProps) {\n const [input, setInput] = useState('');\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [sessionToken, setSessionToken] = useState<string | null>(null);\n const inputRef = useRef<HTMLTextAreaElement | null>(null);\n const messagesEndRef = useRef<HTMLDivElement | null>(null);\n\n const resolvedApiUrl = apiUrl ?? process.env.NEXT_PUBLIC_AGENT_API_URL ?? '';\n const resolvedTokenUrl =\n sessionTokenUrl ?? process.env.NEXT_PUBLIC_SESSION_TOKEN_URL ?? '';\n const resolvedUserLang = useMemo(() => {\n if (lang) return lang;\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language.split('-')[0];\n }\n return undefined;\n }, [lang]);\n const i18n = useMemo(() => getTranslations(resolvedUserLang), [resolvedUserLang]);\n const [messages, setMessages] = useState<ChatMessage[]>(() => [\n {\n id: 'welcome',\n role: 'system',\n content: i18n.systemMessage,\n },\n ]);\n const hasUserMessage = useMemo(\n () => messages.some((message) => message.role === 'user'),\n [messages]\n );\n const displayTitle = title ?? i18n.titleDefault;\n const displaySubtitle = subtitle ?? i18n.subtitleDefault;\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n useEffect(() => {\n setMessages((prev) => {\n if (prev.length === 1 && prev[0].id === 'welcome' && prev[0].role === 'system') {\n return [{ ...prev[0], content: i18n.systemMessage }];\n }\n return prev;\n });\n }, [i18n.systemMessage]);\n\n const fetchSessionToken = useCallback(async () => {\n if (getSessionToken) {\n return getSessionToken();\n }\n\n if (accessToken) {\n return accessToken;\n }\n\n if (!resolvedTokenUrl) {\n throw new Error(i18n.errorMissingTokenUrl);\n }\n\n const response = await fetch(resolvedTokenUrl, {\n credentials: accessToken ? 'omit' : 'include',\n headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,\n });\n\n if (!response.ok) {\n throw new Error(i18n.errorFailedTokenFetch);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const token = data.session_token as string;\n\n if (!token) {\n throw new Error(i18n.errorMissingToken);\n }\n\n return token;\n }, [getSessionToken, i18n, resolvedTokenUrl]);\n\n useEffect(() => {\n fetchSessionToken()\n .then(setSessionToken)\n .catch((err) => {\n setError(err instanceof Error ? err.message : i18n.errorTokenLoad);\n });\n }, [fetchSessionToken, i18n.errorTokenLoad]);\n\n const statusLabel = useMemo(() => {\n if (isStreaming) return i18n.statusStreaming;\n if (error) return i18n.statusNeedsAttention;\n return i18n.statusReady;\n }, [error, i18n, isStreaming]);\n\n const handleLinkClick = useCallback(\n (href: string | undefined, event: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n const isInternal = href.startsWith('/') && !href.startsWith('//');\n if (isInternal && onNavigate) {\n event.preventDefault();\n onNavigate(href);\n }\n },\n [onNavigate]\n );\n\n const normalizeHref = useCallback((href: string | undefined) => {\n if (!href) return href;\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return href;\n }\n if (href.startsWith('mailto:') || href.startsWith('tel:')) {\n return href;\n }\n if (typeof window === 'undefined') {\n return href;\n }\n try {\n const url = new URL(href);\n if (url.host === window.location.host) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n } catch {\n return href;\n }\n return href;\n }, []);\n\n const focusInput = () => {\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n };\n\n const sendMessage = async () => {\n if (isStreaming) {\n focusInput();\n return;\n }\n if (!input.trim() || !resolvedApiUrl) {\n focusInput();\n return;\n }\n if (!sessionToken) {\n setError(i18n.errorTokenUnavailable);\n focusInput();\n return;\n }\n\n setError(null);\n setIsStreaming(true);\n\n const userMessage: ChatMessage = {\n id: createId(),\n role: 'user',\n content: input.trim(),\n };\n\n const assistantMessage: ChatMessage = {\n id: createId(),\n role: 'assistant',\n content: '',\n };\n\n setMessages((prev) => [...prev, userMessage, assistantMessage]);\n setInput('');\n focusInput();\n\n try {\n const response = await fetch(resolvedApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n message: userMessage.content,\n session_token: sessionToken,\n user_lang: resolvedUserLang,\n }),\n });\n\n if (!response.ok || !response.body) {\n throw new Error(i18n.errorServiceUnavailable);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n\n for (const part of parts) {\n const lines = part.split('\\n');\n let eventName = 'message';\n let data = '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n eventName = line.replace('event:', '').trim();\n }\n if (line.startsWith('data:')) {\n data += line.replace('data:', '').trim();\n }\n }\n\n if (!data) continue;\n\n if (eventName === 'message') {\n const payload = JSON.parse(data) as { content?: string };\n if (payload.content !== undefined) {\n setMessages((prev) => {\n const next = [...prev];\n const last = next[next.length - 1];\n if (last && last.role === 'assistant') {\n last.content = payload.content ?? '';\n }\n return next;\n });\n }\n }\n\n if (eventName === 'error') {\n const payload = JSON.parse(data) as { message?: string };\n setError(payload.message ?? i18n.errorUnknown);\n }\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : i18n.errorUnexpected);\n } finally {\n setIsStreaming(false);\n }\n };\n\n return (\n <section\n className={`tw-widget tw-${theme}${className ? ` ${className}` : ''}`}\n data-theme={theme}\n data-tunio-widget=\"root\"\n >\n <header className=\"tw-header\">\n <div className=\"tw-brand\">\n <div className=\"tw-brand-title\">{displayTitle}</div>\n <div className=\"tw-brand-subtitle\">{displaySubtitle}</div>\n </div>\n <div className=\"tw-status\">\n <span className=\"tw-status-dot\" />\n {statusLabel}\n </div>\n </header>\n\n <div className=\"tw-messages\">\n {messages.map((message) => (\n <div\n key={message.id}\n className={`tw-message tw-${message.role}`}\n >\n {message.content ? (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n a: ({ href, children, ...props }) => {\n const normalizedHref = normalizeHref(href);\n const isInternal =\n typeof normalizedHref === 'string' &&\n normalizedHref.startsWith('/') &&\n !normalizedHref.startsWith('//');\n return (\n <a\n {...props}\n href={normalizedHref}\n onClick={(event) => handleLinkClick(normalizedHref, event)}\n target={isInternal ? undefined : '_blank'}\n rel={isInternal ? undefined : 'noreferrer'}\n >\n {children}\n </a>\n );\n },\n }}\n >\n {message.content}\n </ReactMarkdown>\n ) : message.role === 'assistant' ? (\n <span className=\"tw-typing\" role=\"status\" aria-label={i18n.thinking}>\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-sr-only\">{i18n.thinking}</span>\n </span>\n ) : (\n ''\n )}\n </div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"tw-composer\">\n {!hasUserMessage ? (\n <div className=\"tw-pill-row\">\n <span className=\"tw-pill\">{i18n.pillSynthwave}</span>\n <span className=\"tw-pill\">{i18n.pillSchedule}</span>\n <span className=\"tw-pill\">{i18n.pillPlayNow}</span>\n </div>\n ) : null}\n <div className=\"tw-input-wrap\">\n <textarea\n className=\"tw-textarea\"\n placeholder={i18n.inputPlaceholder}\n value={input}\n onChange={(event) => setInput(event.target.value)}\n onKeyDown={(event) => {\n if (event.nativeEvent.isComposing) return;\n if (event.key !== 'Enter') return;\n if (event.shiftKey) return;\n event.preventDefault();\n sendMessage();\n }}\n rows={2}\n ref={inputRef}\n />\n <button\n className=\"tw-send\"\n onClick={sendMessage}\n disabled={isStreaming || !input.trim()}\n >\n {isStreaming ? i18n.sending : i18n.send}\n </button>\n </div>\n {error ? (\n <div className=\"tw-meta\">\n {i18n.errorPrefix} {error}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\":global(.tw-widget) {\\n --panel: #f7f9fc;\\n --ink: #1b2430;\\n --muted: #6c7a90;\\n --accent: #3f6df6;\\n --accent-soft: rgba(63, 109, 246, 0.14);\\n --teal: #21b3a8;\\n --surface: #ffffff;\\n --border: rgba(16, 24, 40, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #f9fbff 0%,\\n #eef2f8 60%,\\n #e8edf6 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(255, 255, 255, 0.92),\\n rgba(243, 246, 252, 0.82) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(255, 255, 255, 0) 0%,\\n rgba(244, 246, 252, 0.92) 80% );\\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(63, 109, 246, 0.18) 0%,\\n rgba(63, 109, 246, 0) 70%);\\n --accent-glow: rgba(63, 109, 246, 0.2);\\n --bubble-user: #1b2430;\\n --bubble-user-text: #f7f9ff;\\n --bubble-assistant: #ffffff;\\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\\n --bubble-system: rgba(27, 36, 48, 0.08);\\n --bubble-system-text: #6c7a90;\\n --input-focus-border: rgba(63, 109, 246, 0.6);\\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\\n --button-bg: #1b2430;\\n --button-text: #f7f9ff;\\n --button-shadow: rgba(27, 36, 48, 0.2);\\n --pill-border: rgba(16, 24, 40, 0.2);\\n display: grid;\\n grid-template-rows: auto minmax(0, 1fr) auto;\\n width: min(100%, 420px);\\n height: min(80vh, 640px);\\n min-height: 520px;\\n border-radius: 12px;\\n background: var(--widget-bg);\\n border: 1px solid var(--border);\\n box-shadow: var(--shadow);\\n color: var(--ink);\\n overflow: hidden;\\n position: relative;\\n}\\n:global(.tw-light) {\\n color-scheme: light;\\n}\\n:global(.tw-dark) {\\n color-scheme: dark;\\n --panel: #121826;\\n --ink: #e6edf7;\\n --muted: #a5b3c8;\\n --accent: #4a7dff;\\n --accent-soft: rgba(74, 125, 255, 0.18);\\n --teal: #2db9c3;\\n --surface: #1a2232;\\n --border: rgba(230, 237, 247, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #131a2a 0%,\\n #101525 60%,\\n #0d1220 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(18, 24, 38, 0.95),\\n rgba(23, 30, 48, 0.85) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(18, 24, 38, 0) 0%,\\n rgba(18, 24, 38, 0.9) 80% );\\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(74, 125, 255, 0.2) 0%,\\n rgba(74, 125, 255, 0) 70%);\\n --accent-glow: rgba(74, 125, 255, 0.28);\\n --bubble-user: #2f3e63;\\n --bubble-user-text: #f4f7ff;\\n --bubble-assistant: #1a2233;\\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\\n --bubble-system: rgba(230, 237, 247, 0.08);\\n --bubble-system-text: #b7c3d7;\\n --input-focus-border: rgba(74, 125, 255, 0.6);\\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\\n --button-bg: #4a7dff;\\n --button-text: #f4f7ff;\\n --button-shadow: rgba(74, 125, 255, 0.35);\\n --pill-border: rgba(230, 237, 247, 0.24);\\n}\\n:global(.tw-widget)::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset: -120px 40% auto -60px;\\n height: 220px;\\n background: var(--glow);\\n pointer-events: none;\\n}\\n:global(.tw-header) {\\n padding: 22px 26px 16px;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n background: var(--header-bg);\\n backdrop-filter: blur(12px);\\n}\\n:global(.tw-brand) {\\n display: flex;\\n flex-direction: column;\\n gap: 6px;\\n}\\n:global(.tw-brand-title) {\\n font-family: var(--font-display);\\n font-size: 24px;\\n letter-spacing: -0.02em;\\n}\\n:global(.tw-brand-subtitle) {\\n color: var(--muted);\\n font-size: 13px;\\n}\\n:global(.tw-status) {\\n display: inline-flex;\\n align-items: center;\\n gap: 8px;\\n padding: 6px 12px;\\n border-radius: 8px;\\n background: var(--accent-soft);\\n color: var(--ink);\\n font-size: 12px;\\n font-weight: 600;\\n}\\n:global(.tw-status-dot) {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n background: var(--accent);\\n box-shadow: 0 0 0 4px var(--accent-glow);\\n}\\n:global(.tw-messages) {\\n padding: 16px 22px 8px;\\n display: flex;\\n flex-direction: column;\\n gap: 14px;\\n overflow-y: auto;\\n min-height: 0;\\n scroll-behavior: smooth;\\n}\\n:global(.tw-message) {\\n max-width: 85%;\\n padding: 14px 16px;\\n border-radius: 8px;\\n line-height: 1.45;\\n font-size: 14px;\\n animation: fadeUp 0.25s ease;\\n}\\n:global(.tw-message) p {\\n margin: 0;\\n}\\n:global(.tw-message) p + p {\\n margin-top: 8px;\\n}\\n:global(.tw-message) a {\\n color: var(--accent);\\n text-decoration: none;\\n font-weight: 600;\\n}\\n:global(.tw-message) a:hover {\\n text-decoration: underline;\\n}\\n:global(.tw-message) strong {\\n font-weight: 700;\\n}\\n:global(.tw-message) ul,\\n:global(.tw-message) ol {\\n margin: 8px 0 0;\\n padding-left: 18px;\\n}\\n:global(.tw-message) li {\\n margin: 4px 0;\\n}\\n:global(.tw-typing) {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n min-height: 16px;\\n color: var(--muted);\\n}\\n:global(.tw-typing-dot) {\\n width: 6px;\\n height: 6px;\\n border-radius: 999px;\\n background: currentColor;\\n opacity: 0.6;\\n animation: typingBounce 1.2s infinite ease-in-out;\\n}\\n:global(.tw-typing-dot):nth-child(2) {\\n animation-delay: 0.2s;\\n}\\n:global(.tw-typing-dot):nth-child(3) {\\n animation-delay: 0.4s;\\n}\\n:global(.tw-sr-only) {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n padding: 0;\\n margin: -1px;\\n overflow: hidden;\\n clip: rect(0, 0, 0, 0);\\n border: 0;\\n}\\n:global(.tw-user) {\\n align-self: flex-end;\\n background: var(--bubble-user);\\n color: var(--bubble-user-text);\\n border-bottom-right-radius: 8px;\\n}\\n:global(.tw-assistant) {\\n align-self: flex-start;\\n background: var(--bubble-assistant);\\n border: 1px solid var(--bubble-assistant-border);\\n border-bottom-left-radius: 8px;\\n}\\n:global(.tw-system) {\\n align-self: center;\\n max-width: 92%;\\n text-align: center;\\n background: var(--bubble-system);\\n color: var(--bubble-system-text);\\n}\\n:global(.tw-composer) {\\n padding: 16px 22px 22px;\\n display: grid;\\n gap: 10px;\\n background: var(--composer-bg);\\n}\\n:global(.tw-input-wrap) {\\n display: flex;\\n gap: 10px;\\n align-items: flex-end;\\n}\\n:global(.tw-textarea) {\\n flex: 1;\\n min-height: 54px;\\n max-height: 140px;\\n padding: 14px 16px;\\n border-radius: 8px;\\n border: 1px solid var(--border);\\n background: var(--surface);\\n color: var(--ink);\\n resize: none;\\n font-family: inherit;\\n font-size: 14px;\\n outline: none;\\n transition: border 0.2s ease, box-shadow 0.2s ease;\\n}\\n:global(.tw-textarea)::placeholder {\\n color: var(--muted);\\n opacity: 0.85;\\n}\\n:global(.tw-textarea):focus {\\n border-color: var(--input-focus-border);\\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\\n}\\n:global(.tw-send) {\\n border: none;\\n border-radius: 8px;\\n padding: 12px 18px;\\n background: var(--button-bg);\\n color: var(--button-text);\\n font-weight: 600;\\n cursor: pointer;\\n transition: transform 0.2s ease, box-shadow 0.2s ease;\\n}\\n:global(.tw-send):hover {\\n transform: translateY(-1px);\\n box-shadow: 0 12px 24px var(--button-shadow);\\n}\\n:global(.tw-send):disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n transform: none;\\n box-shadow: none;\\n}\\n:global(.tw-meta) {\\n display: flex;\\n justify-content: space-between;\\n color: var(--muted);\\n font-size: 12px;\\n}\\n:global(.tw-pill-row) {\\n display: flex;\\n gap: 8px;\\n flex-wrap: wrap;\\n}\\n:global(.tw-pill) {\\n padding: 6px 10px;\\n border-radius: 8px;\\n border: 1px dashed var(--pill-border);\\n color: var(--muted);\\n font-size: 11px;\\n}\\n@keyframes fadeUp {\\n from {\\n opacity: 0;\\n transform: translateY(8px);\\n }\\n to {\\n opacity: 1;\\n transform: translateY(0);\\n }\\n}\\n@keyframes typingBounce {\\n 0%, 80%, 100% {\\n transform: translateY(0);\\n opacity: 0.4;\\n }\\n 40% {\\n transform: translateY(-4px);\\n opacity: 0.9;\\n }\\n}\\n@media (max-width: 720px) {\\n :global(.tw-widget) {\\n width: 100%;\\n height: 100vh;\\n border-radius: 12px;\\n }\\n}\\n\")","export type Translations = {\n titleDefault: string;\n subtitleDefault: string;\n systemMessage: string;\n statusStreaming: string;\n statusNeedsAttention: string;\n statusReady: string;\n thinking: string;\n pillSynthwave: string;\n pillSchedule: string;\n pillPlayNow: string;\n inputPlaceholder: string;\n sending: string;\n send: string;\n sessionReady: string;\n sessionRequesting: string;\n missingApiUrl: string;\n errorPrefix: string;\n errorMissingTokenUrl: string;\n errorFailedTokenFetch: string;\n errorMissingToken: string;\n errorTokenLoad: string;\n errorTokenUnavailable: string;\n errorServiceUnavailable: string;\n errorUnknown: string;\n errorUnexpected: string;\n};\n\nexport const en: Translations = {\n titleDefault: 'Tunio Assistant',\n subtitleDefault: 'Live on-air assistant',\n systemMessage: 'Tell me what station or playlist you want and I will set it up.',\n statusStreaming: 'Online',\n statusNeedsAttention: 'Online',\n statusReady: 'Online',\n thinking: 'Thinking...',\n pillSynthwave: '\"Create a synthwave radio\"',\n pillSchedule: '\"Schedule a rock playlist at 6 PM\"',\n pillPlayNow: '\"Set playlist to play now\"',\n inputPlaceholder: 'Tell me what to do on air...',\n sending: 'Working...',\n send: 'Send',\n sessionReady: 'Session ready',\n sessionRequesting: 'Requesting session...',\n missingApiUrl: 'Missing API URL',\n errorPrefix: 'Error:',\n errorMissingTokenUrl: 'Missing session token URL',\n errorFailedTokenFetch: 'Failed to fetch session token',\n errorMissingToken: 'Session token missing in response',\n errorTokenLoad: 'Failed to load token',\n errorTokenUnavailable: 'Session token is not available',\n errorServiceUnavailable: 'Agent service unavailable',\n errorUnknown: 'Unknown error',\n errorUnexpected: 'Unexpected error',\n};\n\nexport const ru: Translations = {\n titleDefault: 'Tunio Ассистент',\n subtitleDefault: 'Ассистент прямого эфира',\n systemMessage: 'Скажите, какую станцию или плейлист нужно поставить, и я все настрою.',\n statusStreaming: 'Онлайн',\n statusNeedsAttention: 'Онлайн',\n statusReady: 'Онлайн',\n thinking: 'Думаю...',\n pillSynthwave: '\"Создай синтвейв-радио\"',\n pillSchedule: '\"Запланируй рок-плейлист на 18:00\"',\n pillPlayNow: '\"Включи плейлист сейчас\"',\n inputPlaceholder: 'Скажи, что сделать в эфире...',\n sending: 'Выполняю...',\n send: 'Отправить',\n sessionReady: 'Сессия готова',\n sessionRequesting: 'Запрашиваем сессию...',\n missingApiUrl: 'Не задан API URL',\n errorPrefix: 'Ошибка:',\n errorMissingTokenUrl: 'Не задан URL токена сессии',\n errorFailedTokenFetch: 'Не удалось получить токен сессии',\n errorMissingToken: 'Токен сессии отсутствует в ответе',\n errorTokenLoad: 'Не удалось загрузить токен',\n errorTokenUnavailable: 'Токен сессии недоступен',\n errorServiceUnavailable: 'Сервис агента недоступен',\n errorUnknown: 'Неизвестная ошибка',\n errorUnexpected: 'Непредвиденная ошибка',\n};\n\nconst translations: Record<string, Translations> = {\n en,\n ru,\n};\n\nexport const getTranslations = (lang?: string): Translations => {\n if (!lang) return en;\n const normalized = lang.toLowerCase();\n if (translations[normalized]) return translations[normalized];\n const base = normalized.split('-')[0];\n return translations[base] ?? en;\n};\n"],"mappings":"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","names":["_a","_b","ReactMarkdown","remarkGfm"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/TunioWidget.tsx","#style-inject:#style-inject","../src/components/TunioWidget.module.css","../src/i18n/translations.ts"],"sourcesContent":["export { TunioWidget } from './components/TunioWidget';\nexport type { TunioWidgetProps } from './components/TunioWidget';\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport './TunioWidget.module.css';\nimport { getTranslations } from '../i18n/translations';\n\ntype ChatMessage = {\n id: string;\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport type TunioWidgetProps = {\n apiUrl?: string;\n sessionTokenUrl?: string;\n lang?: string;\n getSessionToken?: () => Promise<string>;\n accessToken?: string;\n title?: string;\n subtitle?: string;\n theme?: 'light' | 'dark';\n className?: string;\n onNavigate?: (href: string) => void;\n};\n\nconst createId = () => Math.random().toString(36).slice(2);\n\nexport function TunioWidget({\n apiUrl,\n sessionTokenUrl,\n lang,\n getSessionToken,\n accessToken,\n title,\n subtitle,\n theme = 'light',\n className,\n onNavigate,\n}: TunioWidgetProps) {\n const [input, setInput] = useState('');\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [sessionToken, setSessionToken] = useState<string | null>(null);\n const inputRef = useRef<HTMLTextAreaElement | null>(null);\n const messagesEndRef = useRef<HTMLDivElement | null>(null);\n\n const resolvedApiUrl = apiUrl ?? process.env.NEXT_PUBLIC_AGENT_API_URL ?? '';\n const resolvedTokenUrl =\n sessionTokenUrl ?? process.env.NEXT_PUBLIC_SESSION_TOKEN_URL ?? '';\n const resolvedUserLang = useMemo(() => {\n if (lang) return lang;\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language.split('-')[0];\n }\n return undefined;\n }, [lang]);\n const i18n = useMemo(() => getTranslations(resolvedUserLang), [resolvedUserLang]);\n const [messages, setMessages] = useState<ChatMessage[]>(() => [\n {\n id: 'welcome',\n role: 'system',\n content: i18n.systemMessage,\n },\n ]);\n const hasUserMessage = useMemo(\n () => messages.some((message) => message.role === 'user'),\n [messages]\n );\n const displayTitle = title ?? i18n.titleDefault;\n const displaySubtitle = subtitle ?? i18n.subtitleDefault;\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n useEffect(() => {\n setMessages((prev) => {\n if (prev.length === 1 && prev[0].id === 'welcome' && prev[0].role === 'system') {\n return [{ ...prev[0], content: i18n.systemMessage }];\n }\n return prev;\n });\n }, [i18n.systemMessage]);\n\n const fetchSessionToken = useCallback(async () => {\n if (getSessionToken) {\n return getSessionToken();\n }\n\n if (accessToken) {\n return accessToken;\n }\n\n if (!resolvedTokenUrl) {\n throw new Error(i18n.errorMissingTokenUrl);\n }\n\n const response = await fetch(resolvedTokenUrl, {\n credentials: accessToken ? 'omit' : 'include',\n headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,\n });\n\n if (!response.ok) {\n throw new Error(i18n.errorFailedTokenFetch);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const token = data.session_token as string;\n\n if (!token) {\n throw new Error(i18n.errorMissingToken);\n }\n\n return token;\n }, [getSessionToken, i18n, resolvedTokenUrl]);\n\n useEffect(() => {\n fetchSessionToken()\n .then(setSessionToken)\n .catch((err) => {\n setError(err instanceof Error ? err.message : i18n.errorTokenLoad);\n });\n }, [fetchSessionToken, i18n.errorTokenLoad]);\n\n const statusLabel = useMemo(() => {\n if (isStreaming) return i18n.statusStreaming;\n if (error) return i18n.statusNeedsAttention;\n return i18n.statusReady;\n }, [error, i18n, isStreaming]);\n\n const handleLinkClick = useCallback(\n (href: string | undefined, event: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n const isInternal = href.startsWith('/') && !href.startsWith('//');\n if (isInternal && onNavigate) {\n event.preventDefault();\n onNavigate(href);\n }\n },\n [onNavigate]\n );\n\n const normalizeHref = useCallback((href: string | undefined) => {\n if (!href) return href;\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return href;\n }\n if (href.startsWith('mailto:') || href.startsWith('tel:')) {\n return href;\n }\n if (typeof window === 'undefined') {\n return href;\n }\n try {\n const url = new URL(href);\n if (url.host === window.location.host) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n } catch {\n return href;\n }\n return href;\n }, []);\n\n const focusInput = () => {\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n };\n\n const sendMessage = async () => {\n if (isStreaming) {\n focusInput();\n return;\n }\n if (!input.trim() || !resolvedApiUrl) {\n focusInput();\n return;\n }\n if (!sessionToken) {\n setError(i18n.errorTokenUnavailable);\n focusInput();\n return;\n }\n\n setError(null);\n setIsStreaming(true);\n\n const userMessage: ChatMessage = {\n id: createId(),\n role: 'user',\n content: input.trim(),\n };\n\n const assistantMessage: ChatMessage = {\n id: createId(),\n role: 'assistant',\n content: '',\n };\n\n setMessages((prev) => [...prev, userMessage, assistantMessage]);\n setInput('');\n focusInput();\n\n try {\n const response = await fetch(resolvedApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n message: userMessage.content,\n session_token: sessionToken,\n user_lang: resolvedUserLang,\n }),\n });\n\n if (!response.ok || !response.body) {\n throw new Error(i18n.errorServiceUnavailable);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n\n for (const part of parts) {\n const lines = part.split('\\n');\n let eventName = 'message';\n let data = '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n eventName = line.replace('event:', '').trim();\n }\n if (line.startsWith('data:')) {\n data += line.replace('data:', '').trim();\n }\n }\n\n if (!data) continue;\n\n if (eventName === 'message') {\n const payload = JSON.parse(data) as { content?: string };\n if (payload.content !== undefined) {\n setMessages((prev) => {\n const next = [...prev];\n const last = next[next.length - 1];\n if (last && last.role === 'assistant') {\n last.content = payload.content ?? '';\n }\n return next;\n });\n }\n }\n\n if (eventName === 'error') {\n const payload = JSON.parse(data) as { message?: string };\n setError(payload.message ?? i18n.errorUnknown);\n }\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : i18n.errorUnexpected);\n } finally {\n setIsStreaming(false);\n }\n };\n\n return (\n <section\n className={`tw-widget tw-${theme}${className ? ` ${className}` : ''}`}\n data-theme={theme}\n data-tunio-widget=\"root\"\n >\n <header className=\"tw-header\">\n <div className=\"tw-brand\">\n <div className=\"tw-brand-title\">{displayTitle}</div>\n <div className=\"tw-brand-subtitle\">{displaySubtitle}</div>\n </div>\n <div className=\"tw-status\">\n <span className=\"tw-status-dot\" />\n {statusLabel}\n </div>\n </header>\n\n <div className=\"tw-messages\">\n {messages.map((message) => (\n <div\n key={message.id}\n className={`tw-message tw-${message.role}`}\n >\n {message.content ? (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n a: ({ href, children, ...props }) => {\n const normalizedHref = normalizeHref(href);\n const isInternal =\n typeof normalizedHref === 'string' &&\n normalizedHref.startsWith('/') &&\n !normalizedHref.startsWith('//');\n return (\n <a\n {...props}\n href={normalizedHref}\n onClick={(event) => handleLinkClick(normalizedHref, event)}\n target={isInternal ? undefined : '_blank'}\n rel={isInternal ? undefined : 'noreferrer'}\n >\n {children}\n </a>\n );\n },\n }}\n >\n {message.content}\n </ReactMarkdown>\n ) : message.role === 'assistant' ? (\n <span className=\"tw-typing\" role=\"status\" aria-label={i18n.thinking}>\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-sr-only\">{i18n.thinking}</span>\n </span>\n ) : (\n ''\n )}\n </div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"tw-composer\">\n {!hasUserMessage ? (\n <div className=\"tw-pill-row\">\n <span className=\"tw-pill\">{i18n.pillSynthwave}</span>\n <span className=\"tw-pill\">{i18n.pillSchedule}</span>\n <span className=\"tw-pill\">{i18n.pillPlayNow}</span>\n </div>\n ) : null}\n <div className=\"tw-input-wrap\">\n <textarea\n className=\"tw-textarea\"\n placeholder={i18n.inputPlaceholder}\n value={input}\n onChange={(event) => setInput(event.target.value)}\n onKeyDown={(event) => {\n if (event.nativeEvent.isComposing) return;\n if (event.key !== 'Enter') return;\n if (event.shiftKey) return;\n event.preventDefault();\n sendMessage();\n }}\n rows={2}\n ref={inputRef}\n />\n <button\n className=\"tw-send\"\n onClick={sendMessage}\n disabled={isStreaming || !input.trim()}\n >\n {isStreaming ? i18n.sending : i18n.send}\n </button>\n </div>\n {error ? (\n <div className=\"tw-meta\">\n {i18n.errorPrefix} {error}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".tw-widget {\\n --panel: #f7f9fc;\\n --ink: #1b2430;\\n --muted: #6c7a90;\\n --accent: #3f6df6;\\n --accent-soft: rgba(63, 109, 246, 0.14);\\n --teal: #21b3a8;\\n --surface: #ffffff;\\n --border: rgba(16, 24, 40, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #f9fbff 0%,\\n #eef2f8 60%,\\n #e8edf6 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(255, 255, 255, 0.92),\\n rgba(243, 246, 252, 0.82) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(255, 255, 255, 0) 0%,\\n rgba(244, 246, 252, 0.92) 80% );\\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(63, 109, 246, 0.18) 0%,\\n rgba(63, 109, 246, 0) 70%);\\n --accent-glow: rgba(63, 109, 246, 0.2);\\n --bubble-user: #1b2430;\\n --bubble-user-text: #f7f9ff;\\n --bubble-assistant: #ffffff;\\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\\n --bubble-system: rgba(27, 36, 48, 0.08);\\n --bubble-system-text: #6c7a90;\\n --input-focus-border: rgba(63, 109, 246, 0.6);\\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\\n --button-bg: #1b2430;\\n --button-text: #f7f9ff;\\n --button-shadow: rgba(27, 36, 48, 0.2);\\n --pill-border: rgba(16, 24, 40, 0.2);\\n display: grid;\\n grid-template-rows: auto minmax(0, 1fr) auto;\\n width: min(100%, 420px);\\n height: min(80vh, 640px);\\n min-height: 520px;\\n border-radius: 12px;\\n background: var(--widget-bg);\\n border: 1px solid var(--border);\\n box-shadow: var(--shadow);\\n color: var(--ink);\\n overflow: hidden;\\n position: relative;\\n}\\n.tw-light {\\n color-scheme: light;\\n}\\n.tw-dark {\\n color-scheme: dark;\\n --panel: #121826;\\n --ink: #e6edf7;\\n --muted: #a5b3c8;\\n --accent: #4a7dff;\\n --accent-soft: rgba(74, 125, 255, 0.18);\\n --teal: #2db9c3;\\n --surface: #1a2232;\\n --border: rgba(230, 237, 247, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #131a2a 0%,\\n #101525 60%,\\n #0d1220 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(18, 24, 38, 0.95),\\n rgba(23, 30, 48, 0.85) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(18, 24, 38, 0) 0%,\\n rgba(18, 24, 38, 0.9) 80% );\\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(74, 125, 255, 0.2) 0%,\\n rgba(74, 125, 255, 0) 70%);\\n --accent-glow: rgba(74, 125, 255, 0.28);\\n --bubble-user: #2f3e63;\\n --bubble-user-text: #f4f7ff;\\n --bubble-assistant: #1a2233;\\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\\n --bubble-system: rgba(230, 237, 247, 0.08);\\n --bubble-system-text: #b7c3d7;\\n --input-focus-border: rgba(74, 125, 255, 0.6);\\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\\n --button-bg: #4a7dff;\\n --button-text: #f4f7ff;\\n --button-shadow: rgba(74, 125, 255, 0.35);\\n --pill-border: rgba(230, 237, 247, 0.24);\\n}\\n.tw-widget::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset: -120px 40% auto -60px;\\n height: 220px;\\n background: var(--glow);\\n pointer-events: none;\\n}\\n.tw-header {\\n padding: 22px 26px 16px;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n background: var(--header-bg);\\n backdrop-filter: blur(12px);\\n}\\n.tw-brand {\\n display: flex;\\n flex-direction: column;\\n gap: 6px;\\n}\\n.tw-brand-title {\\n font-family: var(--font-display);\\n font-size: 24px;\\n letter-spacing: -0.02em;\\n}\\n.tw-brand-subtitle {\\n color: var(--muted);\\n font-size: 13px;\\n}\\n.tw-status {\\n display: inline-flex;\\n align-items: center;\\n gap: 8px;\\n padding: 6px 12px;\\n border-radius: 8px;\\n background: var(--accent-soft);\\n color: var(--ink);\\n font-size: 12px;\\n font-weight: 600;\\n}\\n.tw-status-dot {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n background: var(--accent);\\n box-shadow: 0 0 0 4px var(--accent-glow);\\n}\\n.tw-messages {\\n padding: 16px 22px 8px;\\n display: flex;\\n flex-direction: column;\\n gap: 14px;\\n overflow-y: auto;\\n min-height: 0;\\n scroll-behavior: smooth;\\n}\\n.tw-message {\\n max-width: 85%;\\n padding: 14px 16px;\\n border-radius: 8px;\\n line-height: 1.45;\\n font-size: 14px;\\n animation: fadeUp 0.25s ease;\\n}\\n.tw-message p {\\n margin: 0;\\n}\\n.tw-message p + p {\\n margin-top: 8px;\\n}\\n.tw-message a {\\n color: var(--accent);\\n text-decoration: none;\\n font-weight: 600;\\n}\\n.tw-message a:hover {\\n text-decoration: underline;\\n}\\n.tw-message strong {\\n font-weight: 700;\\n}\\n.tw-message ul,\\n.tw-message ol {\\n margin: 8px 0 0;\\n padding-left: 18px;\\n}\\n.tw-message li {\\n margin: 4px 0;\\n}\\n.tw-typing {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n min-height: 16px;\\n color: var(--muted);\\n}\\n.tw-typing-dot {\\n width: 6px;\\n height: 6px;\\n border-radius: 999px;\\n background: currentColor;\\n opacity: 0.6;\\n animation: typingBounce 1.2s infinite ease-in-out;\\n}\\n.tw-typing-dot:nth-child(2) {\\n animation-delay: 0.2s;\\n}\\n.tw-typing-dot:nth-child(3) {\\n animation-delay: 0.4s;\\n}\\n.tw-sr-only {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n padding: 0;\\n margin: -1px;\\n overflow: hidden;\\n clip: rect(0, 0, 0, 0);\\n border: 0;\\n}\\n.tw-user {\\n align-self: flex-end;\\n background: var(--bubble-user);\\n color: var(--bubble-user-text);\\n border-bottom-right-radius: 8px;\\n}\\n.tw-assistant {\\n align-self: flex-start;\\n background: var(--bubble-assistant);\\n border: 1px solid var(--bubble-assistant-border);\\n border-bottom-left-radius: 8px;\\n}\\n.tw-system {\\n align-self: center;\\n max-width: 92%;\\n text-align: center;\\n background: var(--bubble-system);\\n color: var(--bubble-system-text);\\n}\\n.tw-composer {\\n padding: 16px 22px 22px;\\n display: grid;\\n gap: 10px;\\n background: var(--composer-bg);\\n}\\n.tw-input-wrap {\\n display: flex;\\n gap: 10px;\\n align-items: flex-end;\\n}\\n.tw-textarea {\\n flex: 1;\\n min-height: 54px;\\n max-height: 140px;\\n padding: 14px 16px;\\n border-radius: 8px;\\n border: 1px solid var(--border);\\n background: var(--surface);\\n color: var(--ink);\\n resize: none;\\n font-family: inherit;\\n font-size: 14px;\\n outline: none;\\n transition: border 0.2s ease, box-shadow 0.2s ease;\\n}\\n.tw-textarea::placeholder {\\n color: var(--muted);\\n opacity: 0.85;\\n}\\n.tw-textarea:focus {\\n border-color: var(--input-focus-border);\\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\\n}\\n.tw-send {\\n border: none;\\n border-radius: 8px;\\n padding: 12px 18px;\\n background: var(--button-bg);\\n color: var(--button-text);\\n font-weight: 600;\\n cursor: pointer;\\n transition: transform 0.2s ease, box-shadow 0.2s ease;\\n}\\n.tw-send:hover {\\n transform: translateY(-1px);\\n box-shadow: 0 12px 24px var(--button-shadow);\\n}\\n.tw-send:disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n transform: none;\\n box-shadow: none;\\n}\\n.tw-meta {\\n display: flex;\\n justify-content: space-between;\\n color: var(--muted);\\n font-size: 12px;\\n}\\n.tw-pill-row {\\n display: flex;\\n gap: 8px;\\n flex-wrap: wrap;\\n}\\n.tw-pill {\\n padding: 6px 10px;\\n border-radius: 8px;\\n border: 1px dashed var(--pill-border);\\n color: var(--muted);\\n font-size: 11px;\\n}\\n@keyframes fadeUp {\\n from {\\n opacity: 0;\\n transform: translateY(8px);\\n }\\n to {\\n opacity: 1;\\n transform: translateY(0);\\n }\\n}\\n@keyframes typingBounce {\\n 0%, 80%, 100% {\\n transform: translateY(0);\\n opacity: 0.4;\\n }\\n 40% {\\n transform: translateY(-4px);\\n opacity: 0.9;\\n }\\n}\\n@media (max-width: 720px) {\\n .tw-widget {\\n width: 100%;\\n height: 100vh;\\n border-radius: 12px;\\n }\\n}\\n\")","export type Translations = {\n titleDefault: string;\n subtitleDefault: string;\n systemMessage: string;\n statusStreaming: string;\n statusNeedsAttention: string;\n statusReady: string;\n thinking: string;\n pillSynthwave: string;\n pillSchedule: string;\n pillPlayNow: string;\n inputPlaceholder: string;\n sending: string;\n send: string;\n sessionReady: string;\n sessionRequesting: string;\n missingApiUrl: string;\n errorPrefix: string;\n errorMissingTokenUrl: string;\n errorFailedTokenFetch: string;\n errorMissingToken: string;\n errorTokenLoad: string;\n errorTokenUnavailable: string;\n errorServiceUnavailable: string;\n errorUnknown: string;\n errorUnexpected: string;\n};\n\nexport const en: Translations = {\n titleDefault: 'Tunio Assistant',\n subtitleDefault: 'Live on-air assistant',\n systemMessage: 'Tell me what station or playlist you want and I will set it up.',\n statusStreaming: 'Online',\n statusNeedsAttention: 'Online',\n statusReady: 'Online',\n thinking: 'Thinking...',\n pillSynthwave: '\"Create a synthwave radio\"',\n pillSchedule: '\"Schedule a rock playlist at 6 PM\"',\n pillPlayNow: '\"Set playlist to play now\"',\n inputPlaceholder: 'Tell me what to do on air...',\n sending: 'Working...',\n send: 'Send',\n sessionReady: 'Session ready',\n sessionRequesting: 'Requesting session...',\n missingApiUrl: 'Missing API URL',\n errorPrefix: 'Error:',\n errorMissingTokenUrl: 'Missing session token URL',\n errorFailedTokenFetch: 'Failed to fetch session token',\n errorMissingToken: 'Session token missing in response',\n errorTokenLoad: 'Failed to load token',\n errorTokenUnavailable: 'Session token is not available',\n errorServiceUnavailable: 'Agent service unavailable',\n errorUnknown: 'Unknown error',\n errorUnexpected: 'Unexpected error',\n};\n\nexport const ru: Translations = {\n titleDefault: 'Tunio Ассистент',\n subtitleDefault: 'Ассистент прямого эфира',\n systemMessage: 'Скажите, какую станцию или плейлист нужно поставить, и я все настрою.',\n statusStreaming: 'Онлайн',\n statusNeedsAttention: 'Онлайн',\n statusReady: 'Онлайн',\n thinking: 'Думаю...',\n pillSynthwave: '\"Создай синтвейв-радио\"',\n pillSchedule: '\"Запланируй рок-плейлист на 18:00\"',\n pillPlayNow: '\"Включи плейлист сейчас\"',\n inputPlaceholder: 'Скажи, что сделать в эфире...',\n sending: 'Выполняю...',\n send: 'Отправить',\n sessionReady: 'Сессия готова',\n sessionRequesting: 'Запрашиваем сессию...',\n missingApiUrl: 'Не задан API URL',\n errorPrefix: 'Ошибка:',\n errorMissingTokenUrl: 'Не задан URL токена сессии',\n errorFailedTokenFetch: 'Не удалось получить токен сессии',\n errorMissingToken: 'Токен сессии отсутствует в ответе',\n errorTokenLoad: 'Не удалось загрузить токен',\n errorTokenUnavailable: 'Токен сессии недоступен',\n errorServiceUnavailable: 'Сервис агента недоступен',\n errorUnknown: 'Неизвестная ошибка',\n errorUnexpected: 'Непредвиденная ошибка',\n};\n\nconst translations: Record<string, Translations> = {\n en,\n ru,\n};\n\nexport const getTranslations = (lang?: string): Translations => {\n if (!lang) return en;\n const normalized = lang.toLowerCase();\n if (translations[normalized]) return translations[normalized];\n const base = normalized.split('-')[0];\n return translations[base] ?? en;\n};\n"],"mappings":"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","names":["_a","_b","ReactMarkdown","remarkGfm"]}
|
package/dist/index.mjs
CHANGED
|
@@ -26,7 +26,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// src/components/TunioWidget.module.css
|
|
29
|
-
styleInject('
|
|
29
|
+
styleInject('.tw-widget {\n --panel: #f7f9fc;\n --ink: #1b2430;\n --muted: #6c7a90;\n --accent: #3f6df6;\n --accent-soft: rgba(63, 109, 246, 0.14);\n --teal: #21b3a8;\n --surface: #ffffff;\n --border: rgba(16, 24, 40, 0.12);\n --widget-bg:\n linear-gradient(\n 135deg,\n #f9fbff 0%,\n #eef2f8 60%,\n #e8edf6 100%);\n --header-bg:\n linear-gradient(\n \n 120deg,\n rgba(255, 255, 255, 0.92),\n rgba(243, 246, 252, 0.82) );\n --composer-bg:\n linear-gradient(\n \n 180deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(244, 246, 252, 0.92) 80% );\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\n --glow:\n radial-gradient(\n circle,\n rgba(63, 109, 246, 0.18) 0%,\n rgba(63, 109, 246, 0) 70%);\n --accent-glow: rgba(63, 109, 246, 0.2);\n --bubble-user: #1b2430;\n --bubble-user-text: #f7f9ff;\n --bubble-assistant: #ffffff;\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\n --bubble-system: rgba(27, 36, 48, 0.08);\n --bubble-system-text: #6c7a90;\n --input-focus-border: rgba(63, 109, 246, 0.6);\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\n --button-bg: #1b2430;\n --button-text: #f7f9ff;\n --button-shadow: rgba(27, 36, 48, 0.2);\n --pill-border: rgba(16, 24, 40, 0.2);\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n width: min(100%, 420px);\n height: min(80vh, 640px);\n min-height: 520px;\n border-radius: 12px;\n background: var(--widget-bg);\n border: 1px solid var(--border);\n box-shadow: var(--shadow);\n color: var(--ink);\n overflow: hidden;\n position: relative;\n}\n.tw-light {\n color-scheme: light;\n}\n.tw-dark {\n color-scheme: dark;\n --panel: #121826;\n --ink: #e6edf7;\n --muted: #a5b3c8;\n --accent: #4a7dff;\n --accent-soft: rgba(74, 125, 255, 0.18);\n --teal: #2db9c3;\n --surface: #1a2232;\n --border: rgba(230, 237, 247, 0.12);\n --widget-bg:\n linear-gradient(\n 135deg,\n #131a2a 0%,\n #101525 60%,\n #0d1220 100%);\n --header-bg:\n linear-gradient(\n \n 120deg,\n rgba(18, 24, 38, 0.95),\n rgba(23, 30, 48, 0.85) );\n --composer-bg:\n linear-gradient(\n \n 180deg,\n rgba(18, 24, 38, 0) 0%,\n rgba(18, 24, 38, 0.9) 80% );\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\n --glow:\n radial-gradient(\n circle,\n rgba(74, 125, 255, 0.2) 0%,\n rgba(74, 125, 255, 0) 70%);\n --accent-glow: rgba(74, 125, 255, 0.28);\n --bubble-user: #2f3e63;\n --bubble-user-text: #f4f7ff;\n --bubble-assistant: #1a2233;\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\n --bubble-system: rgba(230, 237, 247, 0.08);\n --bubble-system-text: #b7c3d7;\n --input-focus-border: rgba(74, 125, 255, 0.6);\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\n --button-bg: #4a7dff;\n --button-text: #f4f7ff;\n --button-shadow: rgba(74, 125, 255, 0.35);\n --pill-border: rgba(230, 237, 247, 0.24);\n}\n.tw-widget::before {\n content: "";\n position: absolute;\n inset: -120px 40% auto -60px;\n height: 220px;\n background: var(--glow);\n pointer-events: none;\n}\n.tw-header {\n padding: 22px 26px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--header-bg);\n backdrop-filter: blur(12px);\n}\n.tw-brand {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.tw-brand-title {\n font-family: var(--font-display);\n font-size: 24px;\n letter-spacing: -0.02em;\n}\n.tw-brand-subtitle {\n color: var(--muted);\n font-size: 13px;\n}\n.tw-status {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border-radius: 8px;\n background: var(--accent-soft);\n color: var(--ink);\n font-size: 12px;\n font-weight: 600;\n}\n.tw-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 4px var(--accent-glow);\n}\n.tw-messages {\n padding: 16px 22px 8px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n overflow-y: auto;\n min-height: 0;\n scroll-behavior: smooth;\n}\n.tw-message {\n max-width: 85%;\n padding: 14px 16px;\n border-radius: 8px;\n line-height: 1.45;\n font-size: 14px;\n animation: fadeUp 0.25s ease;\n}\n.tw-message p {\n margin: 0;\n}\n.tw-message p + p {\n margin-top: 8px;\n}\n.tw-message a {\n color: var(--accent);\n text-decoration: none;\n font-weight: 600;\n}\n.tw-message a:hover {\n text-decoration: underline;\n}\n.tw-message strong {\n font-weight: 700;\n}\n.tw-message ul,\n.tw-message ol {\n margin: 8px 0 0;\n padding-left: 18px;\n}\n.tw-message li {\n margin: 4px 0;\n}\n.tw-typing {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-height: 16px;\n color: var(--muted);\n}\n.tw-typing-dot {\n width: 6px;\n height: 6px;\n border-radius: 999px;\n background: currentColor;\n opacity: 0.6;\n animation: typingBounce 1.2s infinite ease-in-out;\n}\n.tw-typing-dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n.tw-typing-dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n.tw-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.tw-user {\n align-self: flex-end;\n background: var(--bubble-user);\n color: var(--bubble-user-text);\n border-bottom-right-radius: 8px;\n}\n.tw-assistant {\n align-self: flex-start;\n background: var(--bubble-assistant);\n border: 1px solid var(--bubble-assistant-border);\n border-bottom-left-radius: 8px;\n}\n.tw-system {\n align-self: center;\n max-width: 92%;\n text-align: center;\n background: var(--bubble-system);\n color: var(--bubble-system-text);\n}\n.tw-composer {\n padding: 16px 22px 22px;\n display: grid;\n gap: 10px;\n background: var(--composer-bg);\n}\n.tw-input-wrap {\n display: flex;\n gap: 10px;\n align-items: flex-end;\n}\n.tw-textarea {\n flex: 1;\n min-height: 54px;\n max-height: 140px;\n padding: 14px 16px;\n border-radius: 8px;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--ink);\n resize: none;\n font-family: inherit;\n font-size: 14px;\n outline: none;\n transition: border 0.2s ease, box-shadow 0.2s ease;\n}\n.tw-textarea::placeholder {\n color: var(--muted);\n opacity: 0.85;\n}\n.tw-textarea:focus {\n border-color: var(--input-focus-border);\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\n}\n.tw-send {\n border: none;\n border-radius: 8px;\n padding: 12px 18px;\n background: var(--button-bg);\n color: var(--button-text);\n font-weight: 600;\n cursor: pointer;\n transition: transform 0.2s ease, box-shadow 0.2s ease;\n}\n.tw-send:hover {\n transform: translateY(-1px);\n box-shadow: 0 12px 24px var(--button-shadow);\n}\n.tw-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n.tw-meta {\n display: flex;\n justify-content: space-between;\n color: var(--muted);\n font-size: 12px;\n}\n.tw-pill-row {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n.tw-pill {\n padding: 6px 10px;\n border-radius: 8px;\n border: 1px dashed var(--pill-border);\n color: var(--muted);\n font-size: 11px;\n}\n@keyframes fadeUp {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@keyframes typingBounce {\n 0%, 80%, 100% {\n transform: translateY(0);\n opacity: 0.4;\n }\n 40% {\n transform: translateY(-4px);\n opacity: 0.9;\n }\n}\n@media (max-width: 720px) {\n .tw-widget {\n width: 100%;\n height: 100vh;\n border-radius: 12px;\n }\n}\n');
|
|
30
30
|
|
|
31
31
|
// src/i18n/translations.ts
|
|
32
32
|
var en = {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/TunioWidget.tsx","#style-inject:#style-inject","../src/components/TunioWidget.module.css","../src/i18n/translations.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport './TunioWidget.module.css';\nimport { getTranslations } from '../i18n/translations';\n\ntype ChatMessage = {\n id: string;\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport type TunioWidgetProps = {\n apiUrl?: string;\n sessionTokenUrl?: string;\n lang?: string;\n getSessionToken?: () => Promise<string>;\n accessToken?: string;\n title?: string;\n subtitle?: string;\n theme?: 'light' | 'dark';\n className?: string;\n onNavigate?: (href: string) => void;\n};\n\nconst createId = () => Math.random().toString(36).slice(2);\n\nexport function TunioWidget({\n apiUrl,\n sessionTokenUrl,\n lang,\n getSessionToken,\n accessToken,\n title,\n subtitle,\n theme = 'light',\n className,\n onNavigate,\n}: TunioWidgetProps) {\n const [input, setInput] = useState('');\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [sessionToken, setSessionToken] = useState<string | null>(null);\n const inputRef = useRef<HTMLTextAreaElement | null>(null);\n const messagesEndRef = useRef<HTMLDivElement | null>(null);\n\n const resolvedApiUrl = apiUrl ?? process.env.NEXT_PUBLIC_AGENT_API_URL ?? '';\n const resolvedTokenUrl =\n sessionTokenUrl ?? process.env.NEXT_PUBLIC_SESSION_TOKEN_URL ?? '';\n const resolvedUserLang = useMemo(() => {\n if (lang) return lang;\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language.split('-')[0];\n }\n return undefined;\n }, [lang]);\n const i18n = useMemo(() => getTranslations(resolvedUserLang), [resolvedUserLang]);\n const [messages, setMessages] = useState<ChatMessage[]>(() => [\n {\n id: 'welcome',\n role: 'system',\n content: i18n.systemMessage,\n },\n ]);\n const hasUserMessage = useMemo(\n () => messages.some((message) => message.role === 'user'),\n [messages]\n );\n const displayTitle = title ?? i18n.titleDefault;\n const displaySubtitle = subtitle ?? i18n.subtitleDefault;\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n useEffect(() => {\n setMessages((prev) => {\n if (prev.length === 1 && prev[0].id === 'welcome' && prev[0].role === 'system') {\n return [{ ...prev[0], content: i18n.systemMessage }];\n }\n return prev;\n });\n }, [i18n.systemMessage]);\n\n const fetchSessionToken = useCallback(async () => {\n if (getSessionToken) {\n return getSessionToken();\n }\n\n if (accessToken) {\n return accessToken;\n }\n\n if (!resolvedTokenUrl) {\n throw new Error(i18n.errorMissingTokenUrl);\n }\n\n const response = await fetch(resolvedTokenUrl, {\n credentials: accessToken ? 'omit' : 'include',\n headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,\n });\n\n if (!response.ok) {\n throw new Error(i18n.errorFailedTokenFetch);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const token = data.session_token as string;\n\n if (!token) {\n throw new Error(i18n.errorMissingToken);\n }\n\n return token;\n }, [getSessionToken, i18n, resolvedTokenUrl]);\n\n useEffect(() => {\n fetchSessionToken()\n .then(setSessionToken)\n .catch((err) => {\n setError(err instanceof Error ? err.message : i18n.errorTokenLoad);\n });\n }, [fetchSessionToken, i18n.errorTokenLoad]);\n\n const statusLabel = useMemo(() => {\n if (isStreaming) return i18n.statusStreaming;\n if (error) return i18n.statusNeedsAttention;\n return i18n.statusReady;\n }, [error, i18n, isStreaming]);\n\n const handleLinkClick = useCallback(\n (href: string | undefined, event: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n const isInternal = href.startsWith('/') && !href.startsWith('//');\n if (isInternal && onNavigate) {\n event.preventDefault();\n onNavigate(href);\n }\n },\n [onNavigate]\n );\n\n const normalizeHref = useCallback((href: string | undefined) => {\n if (!href) return href;\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return href;\n }\n if (href.startsWith('mailto:') || href.startsWith('tel:')) {\n return href;\n }\n if (typeof window === 'undefined') {\n return href;\n }\n try {\n const url = new URL(href);\n if (url.host === window.location.host) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n } catch {\n return href;\n }\n return href;\n }, []);\n\n const focusInput = () => {\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n };\n\n const sendMessage = async () => {\n if (isStreaming) {\n focusInput();\n return;\n }\n if (!input.trim() || !resolvedApiUrl) {\n focusInput();\n return;\n }\n if (!sessionToken) {\n setError(i18n.errorTokenUnavailable);\n focusInput();\n return;\n }\n\n setError(null);\n setIsStreaming(true);\n\n const userMessage: ChatMessage = {\n id: createId(),\n role: 'user',\n content: input.trim(),\n };\n\n const assistantMessage: ChatMessage = {\n id: createId(),\n role: 'assistant',\n content: '',\n };\n\n setMessages((prev) => [...prev, userMessage, assistantMessage]);\n setInput('');\n focusInput();\n\n try {\n const response = await fetch(resolvedApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n message: userMessage.content,\n session_token: sessionToken,\n user_lang: resolvedUserLang,\n }),\n });\n\n if (!response.ok || !response.body) {\n throw new Error(i18n.errorServiceUnavailable);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n\n for (const part of parts) {\n const lines = part.split('\\n');\n let eventName = 'message';\n let data = '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n eventName = line.replace('event:', '').trim();\n }\n if (line.startsWith('data:')) {\n data += line.replace('data:', '').trim();\n }\n }\n\n if (!data) continue;\n\n if (eventName === 'message') {\n const payload = JSON.parse(data) as { content?: string };\n if (payload.content !== undefined) {\n setMessages((prev) => {\n const next = [...prev];\n const last = next[next.length - 1];\n if (last && last.role === 'assistant') {\n last.content = payload.content ?? '';\n }\n return next;\n });\n }\n }\n\n if (eventName === 'error') {\n const payload = JSON.parse(data) as { message?: string };\n setError(payload.message ?? i18n.errorUnknown);\n }\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : i18n.errorUnexpected);\n } finally {\n setIsStreaming(false);\n }\n };\n\n return (\n <section\n className={`tw-widget tw-${theme}${className ? ` ${className}` : ''}`}\n data-theme={theme}\n data-tunio-widget=\"root\"\n >\n <header className=\"tw-header\">\n <div className=\"tw-brand\">\n <div className=\"tw-brand-title\">{displayTitle}</div>\n <div className=\"tw-brand-subtitle\">{displaySubtitle}</div>\n </div>\n <div className=\"tw-status\">\n <span className=\"tw-status-dot\" />\n {statusLabel}\n </div>\n </header>\n\n <div className=\"tw-messages\">\n {messages.map((message) => (\n <div\n key={message.id}\n className={`tw-message tw-${message.role}`}\n >\n {message.content ? (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n a: ({ href, children, ...props }) => {\n const normalizedHref = normalizeHref(href);\n const isInternal =\n typeof normalizedHref === 'string' &&\n normalizedHref.startsWith('/') &&\n !normalizedHref.startsWith('//');\n return (\n <a\n {...props}\n href={normalizedHref}\n onClick={(event) => handleLinkClick(normalizedHref, event)}\n target={isInternal ? undefined : '_blank'}\n rel={isInternal ? undefined : 'noreferrer'}\n >\n {children}\n </a>\n );\n },\n }}\n >\n {message.content}\n </ReactMarkdown>\n ) : message.role === 'assistant' ? (\n <span className=\"tw-typing\" role=\"status\" aria-label={i18n.thinking}>\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-sr-only\">{i18n.thinking}</span>\n </span>\n ) : (\n ''\n )}\n </div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"tw-composer\">\n {!hasUserMessage ? (\n <div className=\"tw-pill-row\">\n <span className=\"tw-pill\">{i18n.pillSynthwave}</span>\n <span className=\"tw-pill\">{i18n.pillSchedule}</span>\n <span className=\"tw-pill\">{i18n.pillPlayNow}</span>\n </div>\n ) : null}\n <div className=\"tw-input-wrap\">\n <textarea\n className=\"tw-textarea\"\n placeholder={i18n.inputPlaceholder}\n value={input}\n onChange={(event) => setInput(event.target.value)}\n onKeyDown={(event) => {\n if (event.nativeEvent.isComposing) return;\n if (event.key !== 'Enter') return;\n if (event.shiftKey) return;\n event.preventDefault();\n sendMessage();\n }}\n rows={2}\n ref={inputRef}\n />\n <button\n className=\"tw-send\"\n onClick={sendMessage}\n disabled={isStreaming || !input.trim()}\n >\n {isStreaming ? i18n.sending : i18n.send}\n </button>\n </div>\n {error ? (\n <div className=\"tw-meta\">\n {i18n.errorPrefix} {error}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\":global(.tw-widget) {\\n --panel: #f7f9fc;\\n --ink: #1b2430;\\n --muted: #6c7a90;\\n --accent: #3f6df6;\\n --accent-soft: rgba(63, 109, 246, 0.14);\\n --teal: #21b3a8;\\n --surface: #ffffff;\\n --border: rgba(16, 24, 40, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #f9fbff 0%,\\n #eef2f8 60%,\\n #e8edf6 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(255, 255, 255, 0.92),\\n rgba(243, 246, 252, 0.82) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(255, 255, 255, 0) 0%,\\n rgba(244, 246, 252, 0.92) 80% );\\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(63, 109, 246, 0.18) 0%,\\n rgba(63, 109, 246, 0) 70%);\\n --accent-glow: rgba(63, 109, 246, 0.2);\\n --bubble-user: #1b2430;\\n --bubble-user-text: #f7f9ff;\\n --bubble-assistant: #ffffff;\\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\\n --bubble-system: rgba(27, 36, 48, 0.08);\\n --bubble-system-text: #6c7a90;\\n --input-focus-border: rgba(63, 109, 246, 0.6);\\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\\n --button-bg: #1b2430;\\n --button-text: #f7f9ff;\\n --button-shadow: rgba(27, 36, 48, 0.2);\\n --pill-border: rgba(16, 24, 40, 0.2);\\n display: grid;\\n grid-template-rows: auto minmax(0, 1fr) auto;\\n width: min(100%, 420px);\\n height: min(80vh, 640px);\\n min-height: 520px;\\n border-radius: 12px;\\n background: var(--widget-bg);\\n border: 1px solid var(--border);\\n box-shadow: var(--shadow);\\n color: var(--ink);\\n overflow: hidden;\\n position: relative;\\n}\\n:global(.tw-light) {\\n color-scheme: light;\\n}\\n:global(.tw-dark) {\\n color-scheme: dark;\\n --panel: #121826;\\n --ink: #e6edf7;\\n --muted: #a5b3c8;\\n --accent: #4a7dff;\\n --accent-soft: rgba(74, 125, 255, 0.18);\\n --teal: #2db9c3;\\n --surface: #1a2232;\\n --border: rgba(230, 237, 247, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #131a2a 0%,\\n #101525 60%,\\n #0d1220 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(18, 24, 38, 0.95),\\n rgba(23, 30, 48, 0.85) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(18, 24, 38, 0) 0%,\\n rgba(18, 24, 38, 0.9) 80% );\\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(74, 125, 255, 0.2) 0%,\\n rgba(74, 125, 255, 0) 70%);\\n --accent-glow: rgba(74, 125, 255, 0.28);\\n --bubble-user: #2f3e63;\\n --bubble-user-text: #f4f7ff;\\n --bubble-assistant: #1a2233;\\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\\n --bubble-system: rgba(230, 237, 247, 0.08);\\n --bubble-system-text: #b7c3d7;\\n --input-focus-border: rgba(74, 125, 255, 0.6);\\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\\n --button-bg: #4a7dff;\\n --button-text: #f4f7ff;\\n --button-shadow: rgba(74, 125, 255, 0.35);\\n --pill-border: rgba(230, 237, 247, 0.24);\\n}\\n:global(.tw-widget)::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset: -120px 40% auto -60px;\\n height: 220px;\\n background: var(--glow);\\n pointer-events: none;\\n}\\n:global(.tw-header) {\\n padding: 22px 26px 16px;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n background: var(--header-bg);\\n backdrop-filter: blur(12px);\\n}\\n:global(.tw-brand) {\\n display: flex;\\n flex-direction: column;\\n gap: 6px;\\n}\\n:global(.tw-brand-title) {\\n font-family: var(--font-display);\\n font-size: 24px;\\n letter-spacing: -0.02em;\\n}\\n:global(.tw-brand-subtitle) {\\n color: var(--muted);\\n font-size: 13px;\\n}\\n:global(.tw-status) {\\n display: inline-flex;\\n align-items: center;\\n gap: 8px;\\n padding: 6px 12px;\\n border-radius: 8px;\\n background: var(--accent-soft);\\n color: var(--ink);\\n font-size: 12px;\\n font-weight: 600;\\n}\\n:global(.tw-status-dot) {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n background: var(--accent);\\n box-shadow: 0 0 0 4px var(--accent-glow);\\n}\\n:global(.tw-messages) {\\n padding: 16px 22px 8px;\\n display: flex;\\n flex-direction: column;\\n gap: 14px;\\n overflow-y: auto;\\n min-height: 0;\\n scroll-behavior: smooth;\\n}\\n:global(.tw-message) {\\n max-width: 85%;\\n padding: 14px 16px;\\n border-radius: 8px;\\n line-height: 1.45;\\n font-size: 14px;\\n animation: fadeUp 0.25s ease;\\n}\\n:global(.tw-message) p {\\n margin: 0;\\n}\\n:global(.tw-message) p + p {\\n margin-top: 8px;\\n}\\n:global(.tw-message) a {\\n color: var(--accent);\\n text-decoration: none;\\n font-weight: 600;\\n}\\n:global(.tw-message) a:hover {\\n text-decoration: underline;\\n}\\n:global(.tw-message) strong {\\n font-weight: 700;\\n}\\n:global(.tw-message) ul,\\n:global(.tw-message) ol {\\n margin: 8px 0 0;\\n padding-left: 18px;\\n}\\n:global(.tw-message) li {\\n margin: 4px 0;\\n}\\n:global(.tw-typing) {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n min-height: 16px;\\n color: var(--muted);\\n}\\n:global(.tw-typing-dot) {\\n width: 6px;\\n height: 6px;\\n border-radius: 999px;\\n background: currentColor;\\n opacity: 0.6;\\n animation: typingBounce 1.2s infinite ease-in-out;\\n}\\n:global(.tw-typing-dot):nth-child(2) {\\n animation-delay: 0.2s;\\n}\\n:global(.tw-typing-dot):nth-child(3) {\\n animation-delay: 0.4s;\\n}\\n:global(.tw-sr-only) {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n padding: 0;\\n margin: -1px;\\n overflow: hidden;\\n clip: rect(0, 0, 0, 0);\\n border: 0;\\n}\\n:global(.tw-user) {\\n align-self: flex-end;\\n background: var(--bubble-user);\\n color: var(--bubble-user-text);\\n border-bottom-right-radius: 8px;\\n}\\n:global(.tw-assistant) {\\n align-self: flex-start;\\n background: var(--bubble-assistant);\\n border: 1px solid var(--bubble-assistant-border);\\n border-bottom-left-radius: 8px;\\n}\\n:global(.tw-system) {\\n align-self: center;\\n max-width: 92%;\\n text-align: center;\\n background: var(--bubble-system);\\n color: var(--bubble-system-text);\\n}\\n:global(.tw-composer) {\\n padding: 16px 22px 22px;\\n display: grid;\\n gap: 10px;\\n background: var(--composer-bg);\\n}\\n:global(.tw-input-wrap) {\\n display: flex;\\n gap: 10px;\\n align-items: flex-end;\\n}\\n:global(.tw-textarea) {\\n flex: 1;\\n min-height: 54px;\\n max-height: 140px;\\n padding: 14px 16px;\\n border-radius: 8px;\\n border: 1px solid var(--border);\\n background: var(--surface);\\n color: var(--ink);\\n resize: none;\\n font-family: inherit;\\n font-size: 14px;\\n outline: none;\\n transition: border 0.2s ease, box-shadow 0.2s ease;\\n}\\n:global(.tw-textarea)::placeholder {\\n color: var(--muted);\\n opacity: 0.85;\\n}\\n:global(.tw-textarea):focus {\\n border-color: var(--input-focus-border);\\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\\n}\\n:global(.tw-send) {\\n border: none;\\n border-radius: 8px;\\n padding: 12px 18px;\\n background: var(--button-bg);\\n color: var(--button-text);\\n font-weight: 600;\\n cursor: pointer;\\n transition: transform 0.2s ease, box-shadow 0.2s ease;\\n}\\n:global(.tw-send):hover {\\n transform: translateY(-1px);\\n box-shadow: 0 12px 24px var(--button-shadow);\\n}\\n:global(.tw-send):disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n transform: none;\\n box-shadow: none;\\n}\\n:global(.tw-meta) {\\n display: flex;\\n justify-content: space-between;\\n color: var(--muted);\\n font-size: 12px;\\n}\\n:global(.tw-pill-row) {\\n display: flex;\\n gap: 8px;\\n flex-wrap: wrap;\\n}\\n:global(.tw-pill) {\\n padding: 6px 10px;\\n border-radius: 8px;\\n border: 1px dashed var(--pill-border);\\n color: var(--muted);\\n font-size: 11px;\\n}\\n@keyframes fadeUp {\\n from {\\n opacity: 0;\\n transform: translateY(8px);\\n }\\n to {\\n opacity: 1;\\n transform: translateY(0);\\n }\\n}\\n@keyframes typingBounce {\\n 0%, 80%, 100% {\\n transform: translateY(0);\\n opacity: 0.4;\\n }\\n 40% {\\n transform: translateY(-4px);\\n opacity: 0.9;\\n }\\n}\\n@media (max-width: 720px) {\\n :global(.tw-widget) {\\n width: 100%;\\n height: 100vh;\\n border-radius: 12px;\\n }\\n}\\n\")","export type Translations = {\n titleDefault: string;\n subtitleDefault: string;\n systemMessage: string;\n statusStreaming: string;\n statusNeedsAttention: string;\n statusReady: string;\n thinking: string;\n pillSynthwave: string;\n pillSchedule: string;\n pillPlayNow: string;\n inputPlaceholder: string;\n sending: string;\n send: string;\n sessionReady: string;\n sessionRequesting: string;\n missingApiUrl: string;\n errorPrefix: string;\n errorMissingTokenUrl: string;\n errorFailedTokenFetch: string;\n errorMissingToken: string;\n errorTokenLoad: string;\n errorTokenUnavailable: string;\n errorServiceUnavailable: string;\n errorUnknown: string;\n errorUnexpected: string;\n};\n\nexport const en: Translations = {\n titleDefault: 'Tunio Assistant',\n subtitleDefault: 'Live on-air assistant',\n systemMessage: 'Tell me what station or playlist you want and I will set it up.',\n statusStreaming: 'Online',\n statusNeedsAttention: 'Online',\n statusReady: 'Online',\n thinking: 'Thinking...',\n pillSynthwave: '\"Create a synthwave radio\"',\n pillSchedule: '\"Schedule a rock playlist at 6 PM\"',\n pillPlayNow: '\"Set playlist to play now\"',\n inputPlaceholder: 'Tell me what to do on air...',\n sending: 'Working...',\n send: 'Send',\n sessionReady: 'Session ready',\n sessionRequesting: 'Requesting session...',\n missingApiUrl: 'Missing API URL',\n errorPrefix: 'Error:',\n errorMissingTokenUrl: 'Missing session token URL',\n errorFailedTokenFetch: 'Failed to fetch session token',\n errorMissingToken: 'Session token missing in response',\n errorTokenLoad: 'Failed to load token',\n errorTokenUnavailable: 'Session token is not available',\n errorServiceUnavailable: 'Agent service unavailable',\n errorUnknown: 'Unknown error',\n errorUnexpected: 'Unexpected error',\n};\n\nexport const ru: Translations = {\n titleDefault: 'Tunio Ассистент',\n subtitleDefault: 'Ассистент прямого эфира',\n systemMessage: 'Скажите, какую станцию или плейлист нужно поставить, и я все настрою.',\n statusStreaming: 'Онлайн',\n statusNeedsAttention: 'Онлайн',\n statusReady: 'Онлайн',\n thinking: 'Думаю...',\n pillSynthwave: '\"Создай синтвейв-радио\"',\n pillSchedule: '\"Запланируй рок-плейлист на 18:00\"',\n pillPlayNow: '\"Включи плейлист сейчас\"',\n inputPlaceholder: 'Скажи, что сделать в эфире...',\n sending: 'Выполняю...',\n send: 'Отправить',\n sessionReady: 'Сессия готова',\n sessionRequesting: 'Запрашиваем сессию...',\n missingApiUrl: 'Не задан API URL',\n errorPrefix: 'Ошибка:',\n errorMissingTokenUrl: 'Не задан URL токена сессии',\n errorFailedTokenFetch: 'Не удалось получить токен сессии',\n errorMissingToken: 'Токен сессии отсутствует в ответе',\n errorTokenLoad: 'Не удалось загрузить токен',\n errorTokenUnavailable: 'Токен сессии недоступен',\n errorServiceUnavailable: 'Сервис агента недоступен',\n errorUnknown: 'Неизвестная ошибка',\n errorUnexpected: 'Непредвиденная ошибка',\n};\n\nconst translations: Record<string, Translations> = {\n en,\n ru,\n};\n\nexport const getTranslations = (lang?: string): Translations => {\n if (!lang) return en;\n const normalized = lang.toLowerCase();\n if (translations[normalized]) return translations[normalized];\n const base = normalized.split('-')[0];\n return translations[base] ?? en;\n};\n"],"mappings":"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","names":["_a","_b"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/TunioWidget.tsx","#style-inject:#style-inject","../src/components/TunioWidget.module.css","../src/i18n/translations.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport './TunioWidget.module.css';\nimport { getTranslations } from '../i18n/translations';\n\ntype ChatMessage = {\n id: string;\n role: 'user' | 'assistant' | 'system';\n content: string;\n};\n\nexport type TunioWidgetProps = {\n apiUrl?: string;\n sessionTokenUrl?: string;\n lang?: string;\n getSessionToken?: () => Promise<string>;\n accessToken?: string;\n title?: string;\n subtitle?: string;\n theme?: 'light' | 'dark';\n className?: string;\n onNavigate?: (href: string) => void;\n};\n\nconst createId = () => Math.random().toString(36).slice(2);\n\nexport function TunioWidget({\n apiUrl,\n sessionTokenUrl,\n lang,\n getSessionToken,\n accessToken,\n title,\n subtitle,\n theme = 'light',\n className,\n onNavigate,\n}: TunioWidgetProps) {\n const [input, setInput] = useState('');\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [sessionToken, setSessionToken] = useState<string | null>(null);\n const inputRef = useRef<HTMLTextAreaElement | null>(null);\n const messagesEndRef = useRef<HTMLDivElement | null>(null);\n\n const resolvedApiUrl = apiUrl ?? process.env.NEXT_PUBLIC_AGENT_API_URL ?? '';\n const resolvedTokenUrl =\n sessionTokenUrl ?? process.env.NEXT_PUBLIC_SESSION_TOKEN_URL ?? '';\n const resolvedUserLang = useMemo(() => {\n if (lang) return lang;\n if (typeof navigator !== 'undefined' && navigator.language) {\n return navigator.language.split('-')[0];\n }\n return undefined;\n }, [lang]);\n const i18n = useMemo(() => getTranslations(resolvedUserLang), [resolvedUserLang]);\n const [messages, setMessages] = useState<ChatMessage[]>(() => [\n {\n id: 'welcome',\n role: 'system',\n content: i18n.systemMessage,\n },\n ]);\n const hasUserMessage = useMemo(\n () => messages.some((message) => message.role === 'user'),\n [messages]\n );\n const displayTitle = title ?? i18n.titleDefault;\n const displaySubtitle = subtitle ?? i18n.subtitleDefault;\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n useEffect(() => {\n setMessages((prev) => {\n if (prev.length === 1 && prev[0].id === 'welcome' && prev[0].role === 'system') {\n return [{ ...prev[0], content: i18n.systemMessage }];\n }\n return prev;\n });\n }, [i18n.systemMessage]);\n\n const fetchSessionToken = useCallback(async () => {\n if (getSessionToken) {\n return getSessionToken();\n }\n\n if (accessToken) {\n return accessToken;\n }\n\n if (!resolvedTokenUrl) {\n throw new Error(i18n.errorMissingTokenUrl);\n }\n\n const response = await fetch(resolvedTokenUrl, {\n credentials: accessToken ? 'omit' : 'include',\n headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,\n });\n\n if (!response.ok) {\n throw new Error(i18n.errorFailedTokenFetch);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n const token = data.session_token as string;\n\n if (!token) {\n throw new Error(i18n.errorMissingToken);\n }\n\n return token;\n }, [getSessionToken, i18n, resolvedTokenUrl]);\n\n useEffect(() => {\n fetchSessionToken()\n .then(setSessionToken)\n .catch((err) => {\n setError(err instanceof Error ? err.message : i18n.errorTokenLoad);\n });\n }, [fetchSessionToken, i18n.errorTokenLoad]);\n\n const statusLabel = useMemo(() => {\n if (isStreaming) return i18n.statusStreaming;\n if (error) return i18n.statusNeedsAttention;\n return i18n.statusReady;\n }, [error, i18n, isStreaming]);\n\n const handleLinkClick = useCallback(\n (href: string | undefined, event: React.MouseEvent<HTMLAnchorElement>) => {\n if (!href) return;\n const isInternal = href.startsWith('/') && !href.startsWith('//');\n if (isInternal && onNavigate) {\n event.preventDefault();\n onNavigate(href);\n }\n },\n [onNavigate]\n );\n\n const normalizeHref = useCallback((href: string | undefined) => {\n if (!href) return href;\n if (href.startsWith('/') || href.startsWith('#') || href.startsWith('?')) {\n return href;\n }\n if (href.startsWith('mailto:') || href.startsWith('tel:')) {\n return href;\n }\n if (typeof window === 'undefined') {\n return href;\n }\n try {\n const url = new URL(href);\n if (url.host === window.location.host) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n } catch {\n return href;\n }\n return href;\n }, []);\n\n const focusInput = () => {\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n };\n\n const sendMessage = async () => {\n if (isStreaming) {\n focusInput();\n return;\n }\n if (!input.trim() || !resolvedApiUrl) {\n focusInput();\n return;\n }\n if (!sessionToken) {\n setError(i18n.errorTokenUnavailable);\n focusInput();\n return;\n }\n\n setError(null);\n setIsStreaming(true);\n\n const userMessage: ChatMessage = {\n id: createId(),\n role: 'user',\n content: input.trim(),\n };\n\n const assistantMessage: ChatMessage = {\n id: createId(),\n role: 'assistant',\n content: '',\n };\n\n setMessages((prev) => [...prev, userMessage, assistantMessage]);\n setInput('');\n focusInput();\n\n try {\n const response = await fetch(resolvedApiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n message: userMessage.content,\n session_token: sessionToken,\n user_lang: resolvedUserLang,\n }),\n });\n\n if (!response.ok || !response.body) {\n throw new Error(i18n.errorServiceUnavailable);\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const parts = buffer.split('\\n\\n');\n buffer = parts.pop() ?? '';\n\n for (const part of parts) {\n const lines = part.split('\\n');\n let eventName = 'message';\n let data = '';\n\n for (const line of lines) {\n if (line.startsWith('event:')) {\n eventName = line.replace('event:', '').trim();\n }\n if (line.startsWith('data:')) {\n data += line.replace('data:', '').trim();\n }\n }\n\n if (!data) continue;\n\n if (eventName === 'message') {\n const payload = JSON.parse(data) as { content?: string };\n if (payload.content !== undefined) {\n setMessages((prev) => {\n const next = [...prev];\n const last = next[next.length - 1];\n if (last && last.role === 'assistant') {\n last.content = payload.content ?? '';\n }\n return next;\n });\n }\n }\n\n if (eventName === 'error') {\n const payload = JSON.parse(data) as { message?: string };\n setError(payload.message ?? i18n.errorUnknown);\n }\n }\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : i18n.errorUnexpected);\n } finally {\n setIsStreaming(false);\n }\n };\n\n return (\n <section\n className={`tw-widget tw-${theme}${className ? ` ${className}` : ''}`}\n data-theme={theme}\n data-tunio-widget=\"root\"\n >\n <header className=\"tw-header\">\n <div className=\"tw-brand\">\n <div className=\"tw-brand-title\">{displayTitle}</div>\n <div className=\"tw-brand-subtitle\">{displaySubtitle}</div>\n </div>\n <div className=\"tw-status\">\n <span className=\"tw-status-dot\" />\n {statusLabel}\n </div>\n </header>\n\n <div className=\"tw-messages\">\n {messages.map((message) => (\n <div\n key={message.id}\n className={`tw-message tw-${message.role}`}\n >\n {message.content ? (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n components={{\n a: ({ href, children, ...props }) => {\n const normalizedHref = normalizeHref(href);\n const isInternal =\n typeof normalizedHref === 'string' &&\n normalizedHref.startsWith('/') &&\n !normalizedHref.startsWith('//');\n return (\n <a\n {...props}\n href={normalizedHref}\n onClick={(event) => handleLinkClick(normalizedHref, event)}\n target={isInternal ? undefined : '_blank'}\n rel={isInternal ? undefined : 'noreferrer'}\n >\n {children}\n </a>\n );\n },\n }}\n >\n {message.content}\n </ReactMarkdown>\n ) : message.role === 'assistant' ? (\n <span className=\"tw-typing\" role=\"status\" aria-label={i18n.thinking}>\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-typing-dot\" />\n <span className=\"tw-sr-only\">{i18n.thinking}</span>\n </span>\n ) : (\n ''\n )}\n </div>\n ))}\n <div ref={messagesEndRef} />\n </div>\n\n <div className=\"tw-composer\">\n {!hasUserMessage ? (\n <div className=\"tw-pill-row\">\n <span className=\"tw-pill\">{i18n.pillSynthwave}</span>\n <span className=\"tw-pill\">{i18n.pillSchedule}</span>\n <span className=\"tw-pill\">{i18n.pillPlayNow}</span>\n </div>\n ) : null}\n <div className=\"tw-input-wrap\">\n <textarea\n className=\"tw-textarea\"\n placeholder={i18n.inputPlaceholder}\n value={input}\n onChange={(event) => setInput(event.target.value)}\n onKeyDown={(event) => {\n if (event.nativeEvent.isComposing) return;\n if (event.key !== 'Enter') return;\n if (event.shiftKey) return;\n event.preventDefault();\n sendMessage();\n }}\n rows={2}\n ref={inputRef}\n />\n <button\n className=\"tw-send\"\n onClick={sendMessage}\n disabled={isStreaming || !input.trim()}\n >\n {isStreaming ? i18n.sending : i18n.send}\n </button>\n </div>\n {error ? (\n <div className=\"tw-meta\">\n {i18n.errorPrefix} {error}\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".tw-widget {\\n --panel: #f7f9fc;\\n --ink: #1b2430;\\n --muted: #6c7a90;\\n --accent: #3f6df6;\\n --accent-soft: rgba(63, 109, 246, 0.14);\\n --teal: #21b3a8;\\n --surface: #ffffff;\\n --border: rgba(16, 24, 40, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #f9fbff 0%,\\n #eef2f8 60%,\\n #e8edf6 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(255, 255, 255, 0.92),\\n rgba(243, 246, 252, 0.82) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(255, 255, 255, 0) 0%,\\n rgba(244, 246, 252, 0.92) 80% );\\n --shadow: 0 24px 70px rgba(18, 24, 40, 0.18);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(63, 109, 246, 0.18) 0%,\\n rgba(63, 109, 246, 0) 70%);\\n --accent-glow: rgba(63, 109, 246, 0.2);\\n --bubble-user: #1b2430;\\n --bubble-user-text: #f7f9ff;\\n --bubble-assistant: #ffffff;\\n --bubble-assistant-border: rgba(16, 24, 40, 0.08);\\n --bubble-system: rgba(27, 36, 48, 0.08);\\n --bubble-system-text: #6c7a90;\\n --input-focus-border: rgba(63, 109, 246, 0.6);\\n --input-focus-shadow: rgba(63, 109, 246, 0.2);\\n --button-bg: #1b2430;\\n --button-text: #f7f9ff;\\n --button-shadow: rgba(27, 36, 48, 0.2);\\n --pill-border: rgba(16, 24, 40, 0.2);\\n display: grid;\\n grid-template-rows: auto minmax(0, 1fr) auto;\\n width: min(100%, 420px);\\n height: min(80vh, 640px);\\n min-height: 520px;\\n border-radius: 12px;\\n background: var(--widget-bg);\\n border: 1px solid var(--border);\\n box-shadow: var(--shadow);\\n color: var(--ink);\\n overflow: hidden;\\n position: relative;\\n}\\n.tw-light {\\n color-scheme: light;\\n}\\n.tw-dark {\\n color-scheme: dark;\\n --panel: #121826;\\n --ink: #e6edf7;\\n --muted: #a5b3c8;\\n --accent: #4a7dff;\\n --accent-soft: rgba(74, 125, 255, 0.18);\\n --teal: #2db9c3;\\n --surface: #1a2232;\\n --border: rgba(230, 237, 247, 0.12);\\n --widget-bg:\\n linear-gradient(\\n 135deg,\\n #131a2a 0%,\\n #101525 60%,\\n #0d1220 100%);\\n --header-bg:\\n linear-gradient(\\n \\n 120deg,\\n rgba(18, 24, 38, 0.95),\\n rgba(23, 30, 48, 0.85) );\\n --composer-bg:\\n linear-gradient(\\n \\n 180deg,\\n rgba(18, 24, 38, 0) 0%,\\n rgba(18, 24, 38, 0.9) 80% );\\n --shadow: 0 28px 90px rgba(2, 7, 14, 0.65);\\n --glow:\\n radial-gradient(\\n circle,\\n rgba(74, 125, 255, 0.2) 0%,\\n rgba(74, 125, 255, 0) 70%);\\n --accent-glow: rgba(74, 125, 255, 0.28);\\n --bubble-user: #2f3e63;\\n --bubble-user-text: #f4f7ff;\\n --bubble-assistant: #1a2233;\\n --bubble-assistant-border: rgba(230, 237, 247, 0.1);\\n --bubble-system: rgba(230, 237, 247, 0.08);\\n --bubble-system-text: #b7c3d7;\\n --input-focus-border: rgba(74, 125, 255, 0.6);\\n --input-focus-shadow: rgba(74, 125, 255, 0.25);\\n --button-bg: #4a7dff;\\n --button-text: #f4f7ff;\\n --button-shadow: rgba(74, 125, 255, 0.35);\\n --pill-border: rgba(230, 237, 247, 0.24);\\n}\\n.tw-widget::before {\\n content: \\\"\\\";\\n position: absolute;\\n inset: -120px 40% auto -60px;\\n height: 220px;\\n background: var(--glow);\\n pointer-events: none;\\n}\\n.tw-header {\\n padding: 22px 26px 16px;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n background: var(--header-bg);\\n backdrop-filter: blur(12px);\\n}\\n.tw-brand {\\n display: flex;\\n flex-direction: column;\\n gap: 6px;\\n}\\n.tw-brand-title {\\n font-family: var(--font-display);\\n font-size: 24px;\\n letter-spacing: -0.02em;\\n}\\n.tw-brand-subtitle {\\n color: var(--muted);\\n font-size: 13px;\\n}\\n.tw-status {\\n display: inline-flex;\\n align-items: center;\\n gap: 8px;\\n padding: 6px 12px;\\n border-radius: 8px;\\n background: var(--accent-soft);\\n color: var(--ink);\\n font-size: 12px;\\n font-weight: 600;\\n}\\n.tw-status-dot {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n background: var(--accent);\\n box-shadow: 0 0 0 4px var(--accent-glow);\\n}\\n.tw-messages {\\n padding: 16px 22px 8px;\\n display: flex;\\n flex-direction: column;\\n gap: 14px;\\n overflow-y: auto;\\n min-height: 0;\\n scroll-behavior: smooth;\\n}\\n.tw-message {\\n max-width: 85%;\\n padding: 14px 16px;\\n border-radius: 8px;\\n line-height: 1.45;\\n font-size: 14px;\\n animation: fadeUp 0.25s ease;\\n}\\n.tw-message p {\\n margin: 0;\\n}\\n.tw-message p + p {\\n margin-top: 8px;\\n}\\n.tw-message a {\\n color: var(--accent);\\n text-decoration: none;\\n font-weight: 600;\\n}\\n.tw-message a:hover {\\n text-decoration: underline;\\n}\\n.tw-message strong {\\n font-weight: 700;\\n}\\n.tw-message ul,\\n.tw-message ol {\\n margin: 8px 0 0;\\n padding-left: 18px;\\n}\\n.tw-message li {\\n margin: 4px 0;\\n}\\n.tw-typing {\\n display: inline-flex;\\n align-items: center;\\n gap: 6px;\\n min-height: 16px;\\n color: var(--muted);\\n}\\n.tw-typing-dot {\\n width: 6px;\\n height: 6px;\\n border-radius: 999px;\\n background: currentColor;\\n opacity: 0.6;\\n animation: typingBounce 1.2s infinite ease-in-out;\\n}\\n.tw-typing-dot:nth-child(2) {\\n animation-delay: 0.2s;\\n}\\n.tw-typing-dot:nth-child(3) {\\n animation-delay: 0.4s;\\n}\\n.tw-sr-only {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n padding: 0;\\n margin: -1px;\\n overflow: hidden;\\n clip: rect(0, 0, 0, 0);\\n border: 0;\\n}\\n.tw-user {\\n align-self: flex-end;\\n background: var(--bubble-user);\\n color: var(--bubble-user-text);\\n border-bottom-right-radius: 8px;\\n}\\n.tw-assistant {\\n align-self: flex-start;\\n background: var(--bubble-assistant);\\n border: 1px solid var(--bubble-assistant-border);\\n border-bottom-left-radius: 8px;\\n}\\n.tw-system {\\n align-self: center;\\n max-width: 92%;\\n text-align: center;\\n background: var(--bubble-system);\\n color: var(--bubble-system-text);\\n}\\n.tw-composer {\\n padding: 16px 22px 22px;\\n display: grid;\\n gap: 10px;\\n background: var(--composer-bg);\\n}\\n.tw-input-wrap {\\n display: flex;\\n gap: 10px;\\n align-items: flex-end;\\n}\\n.tw-textarea {\\n flex: 1;\\n min-height: 54px;\\n max-height: 140px;\\n padding: 14px 16px;\\n border-radius: 8px;\\n border: 1px solid var(--border);\\n background: var(--surface);\\n color: var(--ink);\\n resize: none;\\n font-family: inherit;\\n font-size: 14px;\\n outline: none;\\n transition: border 0.2s ease, box-shadow 0.2s ease;\\n}\\n.tw-textarea::placeholder {\\n color: var(--muted);\\n opacity: 0.85;\\n}\\n.tw-textarea:focus {\\n border-color: var(--input-focus-border);\\n box-shadow: 0 0 0 3px var(--input-focus-shadow);\\n}\\n.tw-send {\\n border: none;\\n border-radius: 8px;\\n padding: 12px 18px;\\n background: var(--button-bg);\\n color: var(--button-text);\\n font-weight: 600;\\n cursor: pointer;\\n transition: transform 0.2s ease, box-shadow 0.2s ease;\\n}\\n.tw-send:hover {\\n transform: translateY(-1px);\\n box-shadow: 0 12px 24px var(--button-shadow);\\n}\\n.tw-send:disabled {\\n opacity: 0.5;\\n cursor: not-allowed;\\n transform: none;\\n box-shadow: none;\\n}\\n.tw-meta {\\n display: flex;\\n justify-content: space-between;\\n color: var(--muted);\\n font-size: 12px;\\n}\\n.tw-pill-row {\\n display: flex;\\n gap: 8px;\\n flex-wrap: wrap;\\n}\\n.tw-pill {\\n padding: 6px 10px;\\n border-radius: 8px;\\n border: 1px dashed var(--pill-border);\\n color: var(--muted);\\n font-size: 11px;\\n}\\n@keyframes fadeUp {\\n from {\\n opacity: 0;\\n transform: translateY(8px);\\n }\\n to {\\n opacity: 1;\\n transform: translateY(0);\\n }\\n}\\n@keyframes typingBounce {\\n 0%, 80%, 100% {\\n transform: translateY(0);\\n opacity: 0.4;\\n }\\n 40% {\\n transform: translateY(-4px);\\n opacity: 0.9;\\n }\\n}\\n@media (max-width: 720px) {\\n .tw-widget {\\n width: 100%;\\n height: 100vh;\\n border-radius: 12px;\\n }\\n}\\n\")","export type Translations = {\n titleDefault: string;\n subtitleDefault: string;\n systemMessage: string;\n statusStreaming: string;\n statusNeedsAttention: string;\n statusReady: string;\n thinking: string;\n pillSynthwave: string;\n pillSchedule: string;\n pillPlayNow: string;\n inputPlaceholder: string;\n sending: string;\n send: string;\n sessionReady: string;\n sessionRequesting: string;\n missingApiUrl: string;\n errorPrefix: string;\n errorMissingTokenUrl: string;\n errorFailedTokenFetch: string;\n errorMissingToken: string;\n errorTokenLoad: string;\n errorTokenUnavailable: string;\n errorServiceUnavailable: string;\n errorUnknown: string;\n errorUnexpected: string;\n};\n\nexport const en: Translations = {\n titleDefault: 'Tunio Assistant',\n subtitleDefault: 'Live on-air assistant',\n systemMessage: 'Tell me what station or playlist you want and I will set it up.',\n statusStreaming: 'Online',\n statusNeedsAttention: 'Online',\n statusReady: 'Online',\n thinking: 'Thinking...',\n pillSynthwave: '\"Create a synthwave radio\"',\n pillSchedule: '\"Schedule a rock playlist at 6 PM\"',\n pillPlayNow: '\"Set playlist to play now\"',\n inputPlaceholder: 'Tell me what to do on air...',\n sending: 'Working...',\n send: 'Send',\n sessionReady: 'Session ready',\n sessionRequesting: 'Requesting session...',\n missingApiUrl: 'Missing API URL',\n errorPrefix: 'Error:',\n errorMissingTokenUrl: 'Missing session token URL',\n errorFailedTokenFetch: 'Failed to fetch session token',\n errorMissingToken: 'Session token missing in response',\n errorTokenLoad: 'Failed to load token',\n errorTokenUnavailable: 'Session token is not available',\n errorServiceUnavailable: 'Agent service unavailable',\n errorUnknown: 'Unknown error',\n errorUnexpected: 'Unexpected error',\n};\n\nexport const ru: Translations = {\n titleDefault: 'Tunio Ассистент',\n subtitleDefault: 'Ассистент прямого эфира',\n systemMessage: 'Скажите, какую станцию или плейлист нужно поставить, и я все настрою.',\n statusStreaming: 'Онлайн',\n statusNeedsAttention: 'Онлайн',\n statusReady: 'Онлайн',\n thinking: 'Думаю...',\n pillSynthwave: '\"Создай синтвейв-радио\"',\n pillSchedule: '\"Запланируй рок-плейлист на 18:00\"',\n pillPlayNow: '\"Включи плейлист сейчас\"',\n inputPlaceholder: 'Скажи, что сделать в эфире...',\n sending: 'Выполняю...',\n send: 'Отправить',\n sessionReady: 'Сессия готова',\n sessionRequesting: 'Запрашиваем сессию...',\n missingApiUrl: 'Не задан API URL',\n errorPrefix: 'Ошибка:',\n errorMissingTokenUrl: 'Не задан URL токена сессии',\n errorFailedTokenFetch: 'Не удалось получить токен сессии',\n errorMissingToken: 'Токен сессии отсутствует в ответе',\n errorTokenLoad: 'Не удалось загрузить токен',\n errorTokenUnavailable: 'Токен сессии недоступен',\n errorServiceUnavailable: 'Сервис агента недоступен',\n errorUnknown: 'Неизвестная ошибка',\n errorUnexpected: 'Непредвиденная ошибка',\n};\n\nconst translations: Record<string, Translations> = {\n en,\n ru,\n};\n\nexport const getTranslations = (lang?: string): Translations => {\n if (!lang) return en;\n const normalized = lang.toLowerCase();\n if (translations[normalized]) return translations[normalized];\n const base = normalized.split('-')[0];\n return translations[base] ?? en;\n};\n"],"mappings":"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","names":["_a","_b"]}
|