rushchatbot 1.6.7

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.
Files changed (51) hide show
  1. package/INTEGRATION.md +559 -0
  2. package/index.html +13 -0
  3. package/package/INTEGRATION.md +717 -0
  4. package/package/README.md +717 -0
  5. package/package/package-lock.json +3221 -0
  6. package/package/package.json +101 -0
  7. package/package/src/cli/index.ts +94 -0
  8. package/package/src/client/ChatWidget.tsx +163 -0
  9. package/package/src/client.ts +2 -0
  10. package/package/src/core/RushChatbot.ts +192 -0
  11. package/package/src/index.ts +5 -0
  12. package/package/src/server/guardrails.ts +107 -0
  13. package/package/src/server/index.ts +210 -0
  14. package/package/src/server/matcher.ts +140 -0
  15. package/package/src/shared/types.ts +58 -0
  16. package/package/src/shims-vue.d.ts +5 -0
  17. package/package/src/vanilla/index.ts +302 -0
  18. package/package/src/vanilla.ts +3 -0
  19. package/package/src/vue/ChatWidget.vue +198 -0
  20. package/package/src/vue.ts +3 -0
  21. package/package/src/vue2/ChatWidget.vue +204 -0
  22. package/package/src/vue2.ts +3 -0
  23. package/package/tsconfig.json +19 -0
  24. package/package/vite.config.ts +86 -0
  25. package/package.json +35 -0
  26. package/public/favicon.svg +1 -0
  27. package/public/icons.svg +24 -0
  28. package/server/data/analytics.json +795 -0
  29. package/server/data/chatbot.config.json +26 -0
  30. package/server/data/questions.json +238 -0
  31. package/server/data/questions.tl.json +182 -0
  32. package/server/index.ts +11 -0
  33. package/server/lib/guardrails.ts +107 -0
  34. package/server/lib/matcher.ts +140 -0
  35. package/server/routes/chat.ts +180 -0
  36. package/server/tsconfig.json +11 -0
  37. package/src/App.css +184 -0
  38. package/src/App.tsx +135 -0
  39. package/src/assets/hero.png +0 -0
  40. package/src/assets/react.svg +1 -0
  41. package/src/assets/vite.svg +1 -0
  42. package/src/components/ChatWidget.tsx +417 -0
  43. package/src/components/QuestionTree.tsx +57 -0
  44. package/src/hooks/useChatData.ts +49 -0
  45. package/src/index.css +111 -0
  46. package/src/main.tsx +10 -0
  47. package/src/types/index.ts +40 -0
  48. package/tsconfig.app.json +26 -0
  49. package/tsconfig.json +7 -0
  50. package/tsconfig.node.json +23 -0
  51. package/vite.config.ts +7 -0
@@ -0,0 +1,302 @@
1
+ import { RushChatbot, parseMarkdown } from '../core/RushChatbot'
2
+ import type { RushChatbotState, WidgetSettings } from '../core/RushChatbot'
3
+ import type { Question, Message } from '../shared/types'
4
+
5
+ const STYLES = `
6
+ @keyframes rushchatbot-bounce {
7
+ 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
8
+ 40% { transform: translateY(-6px); opacity: 1; }
9
+ }
10
+ @keyframes rushchatbot-fadeSlideUp {
11
+ from { opacity: 0; transform: translateY(12px); }
12
+ to { opacity: 1; transform: translateY(0); }
13
+ }
14
+ * { box-sizing: border-box; }
15
+ :host { display: block; font-family: Inter, sans-serif; width: 100%; height: 100%; }
16
+ .container { display: flex; flex-direction: column; overflow: hidden; width: 100%; height: 100%; }
17
+ @media (max-width: 480px) {
18
+ .bubble { max-width: 90%; }
19
+ }
20
+ .header { padding: 16px 20px; color: #fff; }
21
+ .header-title { font-weight: 700; font-size: 16px; }
22
+ .header-subtitle { font-size: 12px; opacity: 0.85; }
23
+ .messages-wrap { flex: 1; position: relative; overflow: hidden; }
24
+ .messages { height: 100%; overflow-y: auto; padding: 16px; padding-top: 20px; display: flex; flex-direction: column; gap: 8px; }
25
+ .msg-row { display: flex; flex-direction: column; }
26
+ .msg-row.user { align-items: flex-end; }
27
+ .msg-row.bot { align-items: flex-start; }
28
+ .bubble { padding: 10px 14px; border-radius: 12px; max-width: 80%; font-size: 14px; line-height: 1.5; }
29
+ .btn { display: block; width: 100%; text-align: left; padding: 8px 12px; margin: 0; border-radius: 8px; background: #fff; cursor: pointer; font-size: 14px; }
30
+ .main-btn { text-align: left; padding: 8px 12px; border-radius: 8px; background: transparent; cursor: pointer; font-size: 14px; }
31
+ .sub-list { display: flex; flex-direction: column; gap: 4px; animation: rushchatbot-fadeSlideUp 0.35s ease-out; }
32
+ .main-list { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; animation: rushchatbot-fadeSlideUp 0.35s ease-out; }
33
+ .end-btn { text-align: left; padding: 8px 12px; border-radius: 8px; background: transparent; cursor: pointer; font-size: 14px; margin-top: 4px; border: 1px solid #EF4444; color: #EF4444; width: 100%; }
34
+ .new-chat-btn { display: block; margin-top: 10px; padding: 7px 14px; border-radius: 8px; border: none; color: #fff; cursor: pointer; font-size: 13px; width: 100%; }
35
+ .scroll-btn { position: absolute; bottom: 12px; right: 12px; z-index: 10; width: 34px; height: 34px; border-radius: 50%; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
36
+ .input-row { display: flex; border-top: 1px solid #e5e7eb; padding: 12px; gap: 8px; }
37
+ .input-row input { flex: 1; padding: 8px 12px; border-radius: 8px; border: 1px solid #e5e7eb; font-size: 14px; outline: none; }
38
+ .input-row button { border: none; border-radius: 8px; padding: 8px 16px; color: #fff; cursor: pointer; font-size: 14px; }
39
+ .thinking { align-self: flex-start; padding: 10px 16px; border-radius: 12px; display: flex; gap: 4px; align-items: center; }
40
+ .dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
41
+ .rushchatbot-md p { margin: 0 0 6px 0; }
42
+ .rushchatbot-md p:last-child { margin-bottom: 0; }
43
+ .rushchatbot-md strong { font-weight: 600; }
44
+ .rushchatbot-md ul, .rushchatbot-md ol { margin: 4px 0 4px 16px; padding: 0; }
45
+ .rushchatbot-md li { margin-bottom: 2px; }
46
+ .rushchatbot-md h1, .rushchatbot-md h2, .rushchatbot-md h3 { margin: 4px 0 6px 0; font-weight: 700; line-height: 1.3; }
47
+ .rushchatbot-md h1 { font-size: 1.1em; }
48
+ .rushchatbot-md h2 { font-size: 1em; }
49
+ .rushchatbot-md h3 { font-size: 0.95em; }
50
+ `
51
+
52
+ const OBSERVED = [
53
+ 'api-base', 'width', 'height',
54
+ 'primary-color', 'bg-color', 'border-radius', 'show-shadow',
55
+ 'placeholder', 'send-label', 'hide-input', 'hide-end-chat',
56
+ 'auto-open', 'position',
57
+ ]
58
+
59
+ class RushChatbotElement extends HTMLElement {
60
+ private chat!: RushChatbot
61
+ private shadow!: ShadowRoot
62
+ private state: RushChatbotState | null = null
63
+ private showScrollBtn = false
64
+ private resizeObserver: ResizeObserver | null = null
65
+
66
+ static get observedAttributes() { return OBSERVED }
67
+
68
+ connectedCallback() {
69
+ this.shadow = this.attachShadow({ mode: 'open' })
70
+ this.chat = new RushChatbot({ apiBase: this.getAttribute('api-base') ?? undefined, settings: this.readSettings() })
71
+ this.chat.subscribe(state => { this.state = state; this.render() })
72
+ this.chat.init()
73
+ if (typeof ResizeObserver !== 'undefined') {
74
+ this.resizeObserver = new ResizeObserver(() => this.render())
75
+ this.resizeObserver.observe(this)
76
+ }
77
+ }
78
+
79
+ disconnectedCallback() {
80
+ if (this.resizeObserver) this.resizeObserver.disconnect()
81
+ }
82
+
83
+ attributeChangedCallback() {
84
+ if (this.chat) this.render()
85
+ }
86
+
87
+ private readSettings(): WidgetSettings {
88
+ const bool = (attr: string, def: boolean) => {
89
+ const v = this.getAttribute(attr)
90
+ return v === null ? def : v !== 'false'
91
+ }
92
+ return {
93
+ primaryColor: this.getAttribute('primary-color') ?? undefined,
94
+ bgColor: this.getAttribute('bg-color') ?? undefined,
95
+ borderRadius: this.getAttribute('border-radius') ? Number(this.getAttribute('border-radius')) : undefined,
96
+ showShadow: bool('show-shadow', true),
97
+ placeholder: this.getAttribute('placeholder') ?? undefined,
98
+ sendLabel: this.getAttribute('send-label') ?? undefined,
99
+ hideInput: bool('hide-input', false),
100
+ hideEndChat: bool('hide-end-chat', false),
101
+ autoOpen: bool('auto-open', false),
102
+ position: (this.getAttribute('position') as WidgetSettings['position']) ?? undefined,
103
+ }
104
+ }
105
+
106
+ private get settings(): WidgetSettings { return this.readSettings() }
107
+
108
+
109
+ private get theme() {
110
+ const s = this.state
111
+ const base = (s && s.config && s.config.branding && s.config.branding.theme) || {} as any
112
+ return {
113
+ ...base,
114
+ ...(this.settings.primaryColor ? { primaryColor: this.settings.primaryColor } : {}),
115
+ ...(this.settings.bgColor ? { backgroundColor: this.settings.bgColor } : {}),
116
+ }
117
+ }
118
+
119
+ private get w() {
120
+ const attr = this.getAttribute('width')
121
+ return attr ? `${attr}px` : '100%'
122
+ }
123
+ private get h() {
124
+ const attr = this.getAttribute('height')
125
+ return attr ? `${attr}px` : '100%'
126
+ }
127
+
128
+ private render() {
129
+ if (!this.state || !this.state.config) return
130
+ const { theme, state, settings } = this
131
+
132
+ const borderRadius = settings.borderRadius ?? 16
133
+ const shadow = settings.showShadow !== false ? '0 8px 32px rgba(0,0,0,0.15)' : 'none'
134
+ const placeholder = settings.placeholder ?? 'Type a message...'
135
+ const sendLabel = settings.sendLabel ?? 'Send'
136
+
137
+ const msgsEl = this.shadow.querySelector('.messages') as HTMLElement | null
138
+ const wasAtBottom = msgsEl ? msgsEl.scrollHeight - msgsEl.scrollTop - msgsEl.clientHeight < 60 : true
139
+
140
+ this.shadow.innerHTML = `
141
+ <style>${STYLES}</style>
142
+ <div class="container" style="background:${theme.backgroundColor};color:${theme.textColor};width:${this.w};height:${this.h};font-family:${theme.fontFamily};border-radius:${borderRadius}px;box-shadow:${shadow}">
143
+ <div class="header" style="background:${theme.primaryColor}">
144
+ <div class="header-title">${state.config!.branding.title}</div>
145
+ <div class="header-subtitle">${state.config!.branding.subtitle}</div>
146
+ </div>
147
+ <div class="messages-wrap">
148
+ ${this.showScrollBtn ? `
149
+ <button class="scroll-btn" id="scrollBtn" style="background:${theme.primaryColor}" aria-label="Scroll to latest">
150
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 9l5 5 5-5" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
151
+ </button>` : ''}
152
+ <div class="messages" id="msgs">
153
+ ${state.messages.map((msg, idx) => this.renderMsg(msg, idx)).join('')}
154
+ ${state.loading ? `<div class="thinking" style="background:${theme.botBubbleColor}">
155
+ ${[0,1,2].map(i => `<span class="dot" style="background:${theme.primaryColor};animation:rushchatbot-bounce 1.2s ease-in-out ${i*0.2}s infinite"></span>`).join('')}
156
+ </div>` : ''}
157
+ ${state.showMain && state.questions.length > 0 ? `
158
+ <div class="main-list">
159
+ ${state.questions.map(q => `
160
+ <button class="main-btn" data-action="main" data-id="${q.id}"
161
+ style="border:1px solid ${theme.primaryColor};color:${theme.primaryColor}">
162
+ ${q.question}
163
+ </button>`).join('')}
164
+ ${settings.hideEndChat ? '' : `<button class="end-btn" data-action="endchat">👋 I'm done, thanks!</button>`}
165
+ </div>` : ''}
166
+ <div id="bottom"></div>
167
+ </div>
168
+ </div>
169
+ ${settings.hideInput ? '' : `
170
+ <form class="input-row" id="form">
171
+ <input id="input" placeholder="${placeholder}" value="${state.input}" ${state.loading ? 'disabled' : ''} />
172
+ <button type="submit" style="background:${theme.primaryColor}" ${state.loading ? 'disabled' : ''}>${sendLabel}</button>
173
+ </form>`}
174
+ </div>
175
+ `
176
+
177
+ const newMsgsEl = this.shadow.getElementById('msgs') as HTMLElement
178
+ if (wasAtBottom) {
179
+ newMsgsEl.scrollTop = newMsgsEl.scrollHeight
180
+ this.showScrollBtn = false
181
+ } else {
182
+ this.showScrollBtn = true
183
+ }
184
+
185
+ if (state.showMain) {
186
+ setTimeout(() => {
187
+ const el = this.shadow.getElementById('msgs') as HTMLElement
188
+ if (!el) return
189
+ const overflows = el.scrollHeight > el.clientHeight + 10
190
+ if (overflows !== this.showScrollBtn) {
191
+ this.showScrollBtn = overflows
192
+ this.render()
193
+ }
194
+ }, 50)
195
+ }
196
+
197
+ this.bindEvents()
198
+ }
199
+
200
+ private bindEvents() {
201
+ const state = this.state!
202
+
203
+ this.shadow.getElementById('scrollBtn')?.addEventListener('click', () => {
204
+ const el = this.shadow.getElementById('msgs') as HTMLElement
205
+ el.scrollTop = el.scrollHeight
206
+ this.showScrollBtn = false
207
+ const btn = this.shadow.getElementById('scrollBtn')
208
+ if (btn) btn.style.display = 'none'
209
+ })
210
+
211
+ this.shadow.getElementById('msgs')?.addEventListener('scroll', () => {
212
+ const el = this.shadow.getElementById('msgs') as HTMLElement
213
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 60
214
+ if (atBottom && this.showScrollBtn) {
215
+ this.showScrollBtn = false
216
+ this.render()
217
+ }
218
+ })
219
+
220
+ this.shadow.getElementById('form')?.addEventListener('submit', e => {
221
+ e.preventDefault()
222
+ const inputEl = this.shadow.getElementById('input') as HTMLInputElement
223
+ if (inputEl) inputEl.value = ''
224
+ this.chat.submitQuery()
225
+ })
226
+
227
+ this.shadow.getElementById('input')?.addEventListener('input', e => {
228
+ this.chat.setInput((e.target as HTMLInputElement).value)
229
+ })
230
+
231
+ this.shadow.querySelectorAll('[data-action]').forEach(el => {
232
+ el.addEventListener('click', () => {
233
+ const action = el.getAttribute('data-action')!
234
+ const id = el.getAttribute('data-id')!
235
+
236
+ if (action === 'main') {
237
+ const q = state.questions.find((q: Question) => q.id === id)
238
+ if (q) this.chat.clickMain(q)
239
+ } else if (action === 'sub') {
240
+ const siblingIds = el.getAttribute('data-siblings')?.split(',') ?? []
241
+ let siblings: Question[] = []
242
+ for (const msg of state.messages) {
243
+ if (msg.subquestions?.some((s: Question) => siblingIds.includes(s.id))) {
244
+ siblings = msg.subquestions!
245
+ }
246
+ }
247
+ const q = siblings.find((s: Question) => s.id === id)
248
+ if (q) this.chat.clickSub(q, siblings)
249
+ } else if (action === 'back') {
250
+ this.chat.backToMain()
251
+ } else if (action === 'endchat') {
252
+ this.chat.endChat()
253
+ } else if (action === 'newchat') {
254
+ this.chat.startNewChat()
255
+ }
256
+ })
257
+ })
258
+ }
259
+
260
+ private renderMsg(msg: Message, idx: number): string {
261
+ const { theme } = this
262
+ const isUser = msg.role === 'user'
263
+ const bgColor = isUser ? theme.userBubbleColor : theme.botBubbleColor
264
+ const color = isUser ? '#fff' : theme.textColor
265
+ const firstStyle = idx === 0 ? 'margin-top:48px;' : ''
266
+
267
+ if (msg.subquestions && msg.subquestions.length > 0) {
268
+ const siblingIds = msg.subquestions.map((s: Question) => s.id).join(',')
269
+ return `
270
+ <div class="msg-row bot" style="${firstStyle}">
271
+ <div class="bubble" style="background:${bgColor};color:${color}">
272
+ <div class="rushchatbot-md" style="margin-bottom:8px">${parseMarkdown(msg.text)}</div>
273
+ <div class="sub-list">
274
+ ${msg.subquestions.map((sub: Question) => `
275
+ <button class="btn" data-action="sub" data-id="${sub.id}" data-siblings="${siblingIds}"
276
+ style="border:1px solid ${theme.primaryColor};color:${theme.primaryColor}">
277
+ ${sub.question}
278
+ </button>`).join('')}
279
+ <button class="btn" data-action="back" style="border:1px solid #6B7280;color:#6B7280">← Back to main menu</button>
280
+ </div>
281
+ </div>
282
+ </div>`
283
+ }
284
+
285
+ return `
286
+ <div class="msg-row ${isUser ? 'user' : 'bot'}" style="${firstStyle}">
287
+ <div class="bubble" style="background:${bgColor};color:${color}">
288
+ ${isUser
289
+ ? msg.text
290
+ : `<div class="rushchatbot-md">${parseMarkdown(msg.text)}</div>
291
+ ${msg.isEndChat ? `<button class="new-chat-btn" data-action="newchat" style="background:${theme.primaryColor}">🔄 Start a new chat</button>` : ''}`
292
+ }
293
+ </div>
294
+ </div>`
295
+ }
296
+ }
297
+
298
+ if (!customElements.get('rush-chatbot')) {
299
+ customElements.define('rush-chatbot', RushChatbotElement)
300
+ }
301
+
302
+ export { RushChatbotElement }
@@ -0,0 +1,3 @@
1
+ export { RushChatbotElement } from './vanilla/index'
2
+ export { RushChatbot, parseMarkdown } from './core/RushChatbot'
3
+ export type { RushChatbotConfig, RushChatbotState } from './core/RushChatbot'
@@ -0,0 +1,198 @@
1
+ <template>
2
+ <div v-if="state" :style="containerStyle">
3
+ <!-- Header -->
4
+ <div :style="{ background: theme.primaryColor, padding: '16px 20px', color: '#fff' }">
5
+ <div :style="{ fontWeight: 700, fontSize: '16px' }">{{ state.config.branding.title }}</div>
6
+ <div :style="{ fontSize: '12px', opacity: 0.85 }">{{ state.config.branding.subtitle }}</div>
7
+ </div>
8
+
9
+ <!-- Messages -->
10
+ <div ref="bottomContainer" :style="messagesStyle">
11
+ <div v-for="msg in state.messages" :key="msg.id"
12
+ :style="{ display: 'flex', flexDirection: 'column', alignItems: msg.role === 'user' ? 'flex-end' : 'flex-start' }">
13
+ <div :style="bubbleStyle(msg.role === 'user')">
14
+ <template v-if="msg.subquestions && msg.subquestions.length > 0">
15
+ <div v-if="msg.isFollowUp" :style="{ marginBottom: '8px' }" v-html="md(msg.text)" />
16
+ <button v-for="sub in msg.subquestions" :key="sub.id"
17
+ @click="chat.clickSub(sub, msg.subquestions)"
18
+ :style="subBtnStyle">
19
+ {{ sub.question }}
20
+ </button>
21
+ <button @click="chat.backToMain()" :style="backBtnStyle">← Back to main menu</button>
22
+ </template>
23
+ <template v-else>
24
+ <div v-if="msg.role === 'bot'" v-html="md(msg.text)" />
25
+ <button v-if="msg.role === 'bot' && msg.isEndChat" @click="chat.startNewChat()" :style="newChatBtnStyle">🔄 Start a new chat</button>
26
+ <span v-else-if="msg.role === 'user'">{{ msg.text }}</span>
27
+ </template>
28
+ </div>
29
+ </div>
30
+
31
+ <!-- Thinking dots -->
32
+ <div v-if="state.loading" :style="{ alignSelf: 'flex-start', background: theme.botBubbleColor, padding: '10px 16px', borderRadius: '12px' }">
33
+ <span :style="{ display: 'flex', gap: '4px', alignItems: 'center' }">
34
+ <span v-for="i in 3" :key="i" :style="dotStyle(i - 1)" />
35
+ </span>
36
+ </div>
37
+
38
+ <!-- Main questions -->
39
+ <div v-if="state.showMain && state.questions.length > 0" :style="{ display: 'flex', flexDirection: 'column', gap: '4px', marginTop: '4px' }">
40
+ <button v-for="q in state.questions" :key="q.id"
41
+ @click="chat.clickMain(q)"
42
+ :style="mainBtnStyle">
43
+ {{ q.question }}
44
+ </button>
45
+ <button v-if="!hideEndChat" @click="chat.endChat()" :style="endBtnStyle">👋 I'm done, thanks!</button>
46
+ </div>
47
+
48
+ <div ref="bottom" />
49
+ </div>
50
+
51
+ <!-- Input -->
52
+ <form v-if="!hideInput" @submit.prevent="chat.submitQuery()" :style="{ display: 'flex', borderTop: '1px solid #e5e7eb', padding: '12px', gap: '8px' }">
53
+ <input
54
+ :value="state.input"
55
+ @input="chat.setInput(($event.target as HTMLInputElement).value)"
56
+ :placeholder="placeholder || 'Type a message...'"
57
+ :disabled="state.loading"
58
+ :style="inputStyle"
59
+ />
60
+ <button type="submit" :disabled="state.loading" :style="sendBtnStyle">{{ sendLabel || 'Send' }}</button>
61
+ </form>
62
+ </div>
63
+ </template>
64
+
65
+ <script setup lang="ts">
66
+ import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
67
+ import { RushChatbot, parseMarkdown } from '../core/RushChatbot'
68
+ import type { RushChatbotState } from '../core/RushChatbot'
69
+
70
+ const props = withDefaults(defineProps<{
71
+ apiBase?: string
72
+ width?: number
73
+ height?: number
74
+ primaryColor?: string
75
+ bgColor?: string
76
+ borderRadius?: number
77
+ showShadow?: boolean
78
+ placeholder?: string
79
+ sendLabel?: string
80
+ hideInput?: boolean
81
+ hideEndChat?: boolean
82
+ }>(), { borderRadius: 16, showShadow: true, hideInput: false, hideEndChat: false })
83
+
84
+ const state = ref<RushChatbotState | null>(null)
85
+ const bottom = ref<HTMLDivElement | null>(null)
86
+ let chat: RushChatbot
87
+ let unsub: () => void
88
+
89
+ onMounted(() => {
90
+ chat = new RushChatbot({ apiBase: props.apiBase })
91
+ unsub = chat.subscribe(s => { state.value = s })
92
+ chat.init()
93
+ })
94
+
95
+ onUnmounted(() => unsub?.())
96
+
97
+ watch(() => [state.value?.messages, state.value?.loading], () => {
98
+ nextTick(() => bottom.value?.scrollIntoView({ behavior: 'smooth' }))
99
+ })
100
+
101
+ const theme = computed(() => ({
102
+ ...state.value?.config?.branding?.theme ?? {},
103
+ ...(props.primaryColor ? { primaryColor: props.primaryColor } : {}),
104
+ ...(props.bgColor ? { backgroundColor: props.bgColor } : {}),
105
+ }))
106
+
107
+ const md = (text: string) => parseMarkdown(text)
108
+
109
+ const containerStyle = computed(() => ({
110
+ fontFamily: theme.value.fontFamily,
111
+ color: theme.value.textColor,
112
+ background: theme.value.backgroundColor,
113
+ width: props.width ? `${props.width}px` : '100%',
114
+ height: props.height ? `${props.height}px` : '100%',
115
+ borderRadius: `${props.borderRadius}px`,
116
+ boxShadow: props.showShadow !== false ? '0 8px 32px rgba(0,0,0,0.15)' : 'none',
117
+ display: 'flex',
118
+ flexDirection: 'column' as const,
119
+ overflow: 'hidden',
120
+ }))
121
+
122
+ const messagesStyle = {
123
+ flex: 1, overflowY: 'auto' as const, padding: '16px',
124
+ display: 'flex', flexDirection: 'column' as const, gap: '8px',
125
+ }
126
+
127
+ const bubbleStyle = (isUser: boolean) => ({
128
+ background: isUser ? theme.value.userBubbleColor : theme.value.botBubbleColor,
129
+ color: isUser ? '#fff' : theme.value.textColor,
130
+ padding: '10px 14px', borderRadius: '12px', maxWidth: '80%', fontSize: '14px', lineHeight: 1.5,
131
+ })
132
+
133
+ const subBtnStyle = {
134
+ display: 'block', width: '100%', textAlign: 'left' as const,
135
+ padding: '7px 10px', margin: '4px 0', borderRadius: '8px',
136
+ border: `1px solid ${theme.value.primaryColor}`, background: '#fff',
137
+ color: theme.value.primaryColor, cursor: 'pointer', fontSize: '13px',
138
+ }
139
+
140
+ const backBtnStyle = {
141
+ display: 'block', width: '100%', textAlign: 'left' as const,
142
+ padding: '7px 10px', margin: '4px 0', borderRadius: '8px',
143
+ border: '1px solid #6B7280', background: '#fff',
144
+ color: '#6B7280', cursor: 'pointer', fontSize: '13px',
145
+ }
146
+
147
+ const mainBtnStyle = computed(() => ({
148
+ textAlign: 'left' as const, padding: '8px 12px', borderRadius: '8px',
149
+ border: `1px solid ${theme.value.primaryColor}`, background: 'transparent',
150
+ color: theme.value.primaryColor, cursor: 'pointer', fontSize: '14px',
151
+ }))
152
+
153
+ const dotStyle = (i: number) => ({
154
+ width: '7px', height: '7px', borderRadius: '50%',
155
+ background: theme.value.primaryColor, display: 'inline-block',
156
+ animation: `rushchatbot-bounce 1.2s ease-in-out ${i * 0.2}s infinite`,
157
+ })
158
+
159
+ const endBtnStyle = {
160
+ textAlign: 'left' as const, padding: '8px 12px', borderRadius: '8px',
161
+ border: '1px solid #EF4444', background: 'transparent',
162
+ color: '#EF4444', cursor: 'pointer', fontSize: '14px', marginTop: '4px', width: '100%',
163
+ }
164
+
165
+ const newChatBtnStyle = computed(() => ({
166
+ display: 'block', marginTop: '10px', padding: '7px 14px', borderRadius: '8px',
167
+ border: 'none', background: theme.value.primaryColor,
168
+ color: '#fff', cursor: 'pointer', fontSize: '13px', width: '100%',
169
+ }))
170
+
171
+ const inputStyle = {
172
+ flex: 1, padding: '8px 12px', borderRadius: '8px',
173
+ border: '1px solid #e5e7eb', fontSize: '14px', outline: 'none',
174
+ }
175
+
176
+ const sendBtnStyle = computed(() => ({
177
+ background: theme.value.primaryColor, color: '#fff', border: 'none',
178
+ borderRadius: '8px', padding: '8px 16px',
179
+ cursor: state.value?.loading ? 'not-allowed' : 'pointer', fontSize: '14px',
180
+ }))
181
+ </script>
182
+
183
+ <style>
184
+ @keyframes rushchatbot-bounce {
185
+ 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; }
186
+ 40% { transform: translateY(-6px); opacity: 1; }
187
+ }
188
+ .rushchatbot-md p { margin: 0 0 6px 0; }
189
+ .rushchatbot-md p:last-child { margin-bottom: 0; }
190
+ .rushchatbot-md strong { font-weight: 600; }
191
+ .rushchatbot-md ul, .rushchatbot-md ol { margin: 4px 0 4px 16px; padding: 0; }
192
+ .rushchatbot-md li { margin-bottom: 2px; }
193
+ .rushchatbot-md a { color: inherit; text-decoration: underline; }
194
+ .rushchatbot-md h1, .rushchatbot-md h2, .rushchatbot-md h3 { margin: 4px 0 6px 0; font-weight: 700; line-height: 1.3; }
195
+ .rushchatbot-md h1 { font-size: 1.1em; }
196
+ .rushchatbot-md h2 { font-size: 1em; }
197
+ .rushchatbot-md h3 { font-size: 0.95em; }
198
+ </style>
@@ -0,0 +1,3 @@
1
+ export { RushChatbotElement } from './vanilla/index'
2
+ export { RushChatbot, parseMarkdown } from './core/RushChatbot'
3
+ export type { RushChatbotConfig, RushChatbotState } from './core/RushChatbot'