@docsector/docsector-reader 4.3.2 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.env.example +18 -0
  2. package/README.md +136 -5
  3. package/bin/docsector.js +36 -1
  4. package/docsector.config.js +44 -0
  5. package/package.json +3 -2
  6. package/public/robots.txt +4 -0
  7. package/src/ai-assistant/config.js +91 -0
  8. package/src/ai-assistant/indexing.js +50 -0
  9. package/src/ai-assistant/layout.js +56 -0
  10. package/src/ai-assistant/messages.js +41 -0
  11. package/src/ai-assistant/panel.js +22 -0
  12. package/src/ai-assistant/server.js +348 -0
  13. package/src/ai-assistant/session.js +91 -0
  14. package/src/ai-assistant/stream.js +125 -0
  15. package/src/components/DAssistantPanel.vue +701 -0
  16. package/src/components/DPage.vue +114 -4
  17. package/src/components/DPageAnchor.vue +11 -7
  18. package/src/components/DPageRichContent.vue +105 -0
  19. package/src/components/DPageTokens.vue +27 -16
  20. package/src/components/api-block-model.js +77 -1
  21. package/src/components/inline-code-copy.js +58 -0
  22. package/src/components/page-section-tokens.js +6 -4
  23. package/src/components/quasar-api-extends.json +235 -0
  24. package/src/composables/useAssistant.js +201 -0
  25. package/src/i18n/helpers.js +2 -0
  26. package/src/i18n/languages/en-US.hjson +22 -0
  27. package/src/i18n/languages/pt-BR.hjson +22 -0
  28. package/src/layouts/DefaultLayout.vue +22 -0
  29. package/src/markdown-agent.js +32 -0
  30. package/src/pages/manual/basic/ai-assistant.overview.en-US.md +69 -0
  31. package/src/pages/manual/basic/ai-assistant.overview.pt-BR.md +69 -0
  32. package/src/pages/manual/basic/d-page-anchor.overview.en-US.md +1 -1
  33. package/src/pages/manual/basic/d-page-anchor.overview.pt-BR.md +1 -1
  34. package/src/pages/manual.index.js +29 -0
  35. package/src/quasar.factory.js +166 -33
  36. package/src/sitemap.js +103 -0
  37. package/src/store/Layout.js +9 -1
@@ -0,0 +1,701 @@
1
+ <script setup>
2
+ import { computed, nextTick, ref, watch } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+ import { useRoute } from 'vue-router'
5
+ import { useQuasar } from 'quasar'
6
+
7
+ import useAssistant from '../composables/useAssistant'
8
+ import { isAssistantThinkingState, listVisibleAssistantMessages } from '../ai-assistant/panel'
9
+ import DPageTokens from './DPageTokens.vue'
10
+ import { tokenizePageSectionSource } from './page-section-tokens'
11
+
12
+ const emit = defineEmits(['close', 'resize'])
13
+
14
+ const props = defineProps({
15
+ contextTitle: {
16
+ type: String,
17
+ default: ''
18
+ },
19
+ markdownUrl: {
20
+ type: String,
21
+ default: ''
22
+ },
23
+ width: {
24
+ type: Number,
25
+ default: 0
26
+ },
27
+ resizable: {
28
+ type: Boolean,
29
+ default: false
30
+ }
31
+ })
32
+
33
+ const siteFavicon = '/favicon.ico'
34
+
35
+ const resolveAssetUrl = (src = '') => {
36
+ const raw = String(src || '').trim()
37
+ if (!raw) return ''
38
+ if (/^(?:https?:)?\/\//i.test(raw) || raw.startsWith('data:')) return raw
39
+
40
+ try {
41
+ return new URL(raw, typeof window !== 'undefined' ? window.location.origin : 'https://localhost').href
42
+ } catch {
43
+ return raw
44
+ }
45
+ }
46
+
47
+ const route = useRoute()
48
+ const $q = useQuasar()
49
+ const { t, locale } = useI18n()
50
+ const input = ref('')
51
+ const scrollArea = ref(null)
52
+
53
+ const assistant = useAssistant({
54
+ route,
55
+ locale,
56
+ getContext: () => ({
57
+ title: props.contextTitle,
58
+ markdownUrl: props.markdownUrl,
59
+ selectedText: typeof window !== 'undefined' ? String(window.getSelection?.() || '') : ''
60
+ })
61
+ })
62
+
63
+ const prompts = computed(() => assistant.config.ui.suggestedPrompts)
64
+ const title = computed(() => assistant.config.ui.title || t('assistant.title'))
65
+ const subtitle = computed(() => assistant.config.ui.subtitle || t('assistant.subtitle'))
66
+ const greeting = computed(() => {
67
+ const hour = new Date().getHours()
68
+ if (hour < 12) return t('assistant.greeting.morning')
69
+ if (hour < 18) return t('assistant.greeting.afternoon')
70
+ return t('assistant.greeting.evening')
71
+ })
72
+
73
+ const panelTone = computed(() => $q.dark.isActive ? 'dark' : 'light')
74
+
75
+ const sourceHref = (source) => {
76
+ const key = String(source?.key || '').trim()
77
+ if (!key) return '#'
78
+ if (/^https?:\/\//i.test(key) || key.startsWith('/')) return key
79
+ return `/${key}`
80
+ }
81
+
82
+ const faviconFor = () => resolveAssetUrl(siteFavicon)
83
+
84
+ const sources = computed(() => assistant.sources.value)
85
+ const hasSources = computed(() => sources.value.length > 0 && assistant.config.ui.showCitations)
86
+ const sourceAvatars = computed(() => sources.value.slice(0, 4))
87
+ const sourcesLabel = computed(() => t('assistant.sourcesCount', { count: sources.value.length }))
88
+ const visibleMessages = computed(() => listVisibleAssistantMessages(assistant.messages.value))
89
+
90
+ const isThinking = computed(() => isAssistantThinkingState({
91
+ loading: assistant.loading.value,
92
+ messages: assistant.messages.value
93
+ }))
94
+
95
+ const renderMessageTokens = (message) => {
96
+ if (message?.role !== 'assistant') {
97
+ return []
98
+ }
99
+
100
+ return tokenizePageSectionSource(message?.content || '', { allowHeadingTokens: false })
101
+ }
102
+
103
+ const startResize = (event) => {
104
+ if (!props.resizable) return
105
+ event.preventDefault()
106
+ const startX = event.clientX
107
+ const startWidth = props.width || 380
108
+
109
+ const onMove = (moveEvent) => {
110
+ const delta = startX - moveEvent.clientX
111
+ emit('resize', startWidth + delta)
112
+ }
113
+
114
+ const onUp = () => {
115
+ window.removeEventListener('pointermove', onMove)
116
+ window.removeEventListener('pointerup', onUp)
117
+ document.body.style.userSelect = ''
118
+ document.body.style.cursor = ''
119
+ }
120
+
121
+ document.body.style.userSelect = 'none'
122
+ document.body.style.cursor = 'col-resize'
123
+ window.addEventListener('pointermove', onMove)
124
+ window.addEventListener('pointerup', onUp)
125
+ }
126
+
127
+ const scrollToBottom = () => {
128
+ nextTick(() => {
129
+ const target = scrollArea.value?.$el?.querySelector('.q-scrollarea__container')
130
+ if (target) {
131
+ target.scrollTop = target.scrollHeight
132
+ }
133
+ })
134
+ }
135
+
136
+ const submit = async (value = input.value) => {
137
+ const prompt = String(value || '').trim()
138
+ if (!prompt) return
139
+ input.value = ''
140
+ await assistant.send(prompt)
141
+ }
142
+
143
+ const handleKeydown = (event) => {
144
+ if (event.key !== 'Enter' || event.shiftKey) return
145
+ event.preventDefault()
146
+ submit()
147
+ }
148
+
149
+ watch(assistant.messages, scrollToBottom, { deep: true })
150
+ watch(assistant.sources, scrollToBottom, { deep: true })
151
+ </script>
152
+
153
+ <template>
154
+ <aside class="d-assistant-panel" :class="`d-assistant-panel--${panelTone}`">
155
+ <div
156
+ v-if="resizable"
157
+ class="d-assistant-panel__resizer"
158
+ role="separator"
159
+ aria-orientation="vertical"
160
+ :aria-label="t('assistant.resize')"
161
+ @pointerdown="startResize"
162
+ />
163
+ <header class="d-assistant-panel__header">
164
+ <div class="d-assistant-panel__brand">
165
+ <q-icon name="auto_awesome" size="22px" />
166
+ <strong>{{ title }}</strong>
167
+ </div>
168
+ <div class="d-assistant-panel__header-actions">
169
+ <q-btn v-if="assistant.hasMessages.value"
170
+ dense round
171
+ color="white"
172
+ class="d-assistant-panel__header-action d-assistant-panel__header-action--clear"
173
+ icon="delete_outline"
174
+ text-color="negative"
175
+ :aria-label="t('assistant.clear')"
176
+ @click="assistant.clear"
177
+ >
178
+ <q-tooltip>{{ t('assistant.clear') }}</q-tooltip>
179
+ </q-btn>
180
+ <q-btn
181
+ dense round
182
+ color="white"
183
+ text-color="black"
184
+ class="d-assistant-panel__header-action d-assistant-panel__header-action--close"
185
+ icon="close"
186
+ :aria-label="t('assistant.close')"
187
+ @click="emit('close')"
188
+ >
189
+ <q-tooltip>{{ t('assistant.close') }}</q-tooltip>
190
+ </q-btn>
191
+ </div>
192
+ </header>
193
+
194
+ <q-scroll-area ref="scrollArea" class="d-assistant-panel__body">
195
+ <div v-if="!assistant.hasMessages.value" class="d-assistant-panel__empty">
196
+ <div class="d-assistant-panel__mark">
197
+ <q-icon name="auto_awesome" size="52px" />
198
+ </div>
199
+ <h2>{{ greeting }}</h2>
200
+ <p>{{ subtitle }}</p>
201
+ </div>
202
+
203
+ <div v-else class="d-assistant-panel__messages">
204
+ <div
205
+ v-for="(message, index) in visibleMessages"
206
+ :key="message.id"
207
+ class="d-assistant-message"
208
+ :class="`d-assistant-message--${message.role}`"
209
+ >
210
+ <div
211
+ v-if="message.role === 'assistant'"
212
+ class="content no-padding d-assistant-message__content d-assistant-message__content--markdown"
213
+ >
214
+ <d-page-tokens :id="(index + 1) * 1000" :tokens="renderMessageTokens(message)" />
215
+ </div>
216
+ <div v-else class="d-assistant-message__content">{{ message.content }}</div>
217
+ </div>
218
+
219
+ <div v-if="isThinking" class="d-assistant-message d-assistant-message--assistant">
220
+ <div class="d-assistant-message__content d-assistant-message__thinking">
221
+ <q-spinner-dots size="22px" />
222
+ <span>{{ t('assistant.thinking') }}</span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ <div v-if="assistant.error.value" class="d-assistant-panel__error">
228
+ <q-icon name="error_outline" />
229
+ <span>{{ assistant.error.value }}</span>
230
+ </div>
231
+
232
+ <div v-if="hasSources" class="d-assistant-panel__sources">
233
+ <q-chip
234
+ clickable
235
+ class="d-assistant-sources-chip"
236
+ :ripple="false"
237
+ >
238
+ <span class="d-assistant-sources-chip__avatars">
239
+ <q-avatar
240
+ v-for="(source, index) in sourceAvatars"
241
+ :key="source.id"
242
+ class="d-assistant-sources-chip__avatar"
243
+ :style="{ zIndex: sourceAvatars.length - index }"
244
+ size="24px"
245
+ >
246
+ <img :src="faviconFor(source)" :alt="source.title" loading="lazy">
247
+ </q-avatar>
248
+ </span>
249
+ <span class="d-assistant-sources-chip__label">{{ sourcesLabel }}</span>
250
+ <q-icon name="expand_more" size="16px" />
251
+
252
+ <q-menu
253
+ anchor="top left"
254
+ self="bottom left"
255
+ :offset="[0, 8]"
256
+ class="d-assistant-sources-menu"
257
+ >
258
+ <q-list separator class="d-assistant-sources-menu__list">
259
+ <q-item-label header class="d-assistant-sources-menu__header">
260
+ {{ t('assistant.sources') }}
261
+ </q-item-label>
262
+ <q-item
263
+ v-for="source in assistant.sources.value"
264
+ :key="source.id"
265
+ v-close-popup
266
+ clickable
267
+ tag="a"
268
+ :href="sourceHref(source)"
269
+ target="_blank"
270
+ rel="noopener noreferrer"
271
+ >
272
+ <q-item-section avatar>
273
+ <q-avatar size="28px" class="d-assistant-sources-menu__avatar">
274
+ <img :src="faviconFor(source)" :alt="source.title" loading="lazy">
275
+ </q-avatar>
276
+ </q-item-section>
277
+ <q-item-section>
278
+ <q-item-label lines="1">{{ source.title }}</q-item-label>
279
+ <q-item-label v-if="source.meta" caption lines="1">{{ source.meta }}</q-item-label>
280
+ </q-item-section>
281
+ <q-item-section side>
282
+ <q-icon name="open_in_new" size="16px" />
283
+ </q-item-section>
284
+ </q-item>
285
+ </q-list>
286
+ </q-menu>
287
+ </q-chip>
288
+ </div>
289
+ </q-scroll-area>
290
+
291
+ <footer class="d-assistant-panel__composer">
292
+ <div v-if="!assistant.hasMessages.value" class="d-assistant-panel__prompts">
293
+ <q-btn
294
+ v-for="prompt in prompts"
295
+ :key="prompt"
296
+ dense no-caps unelevated
297
+ class="d-assistant-panel__prompt"
298
+ @click="submit(prompt)"
299
+ >
300
+ {{ prompt }}
301
+ </q-btn>
302
+ </div>
303
+
304
+ <div class="d-assistant-panel__composer-box">
305
+ <q-input
306
+ v-model="input"
307
+ class="d-assistant-panel__input"
308
+ borderless
309
+ autogrow
310
+ dense
311
+ :placeholder="t('assistant.placeholder')"
312
+ :disable="assistant.loading.value"
313
+ @keydown="handleKeydown"
314
+ />
315
+ <div class="d-assistant-panel__composer-row">
316
+ <span class="d-assistant-panel__context">
317
+ <q-icon name="smart_toy" size="16px" />
318
+ {{ t('assistant.context') }}
319
+ </span>
320
+ <q-btn
321
+ no-caps flat dense
322
+ class="d-assistant-panel__send"
323
+ :icon="assistant.loading.value ? 'stop' : 'send'"
324
+ :label="assistant.loading.value ? t('assistant.stop') : t('assistant.send')"
325
+ @click="assistant.loading.value ? assistant.stop() : submit()"
326
+ />
327
+ </div>
328
+ </div>
329
+ </footer>
330
+ </aside>
331
+ </template>
332
+
333
+ <style lang="sass">
334
+ .d-assistant-panel
335
+ height: 100%
336
+ display: flex
337
+ flex-direction: column
338
+ background: #f8fafc
339
+ color: #111827
340
+ overflow: hidden
341
+ position: relative
342
+
343
+ &__resizer
344
+ position: absolute
345
+ top: 0
346
+ left: 0
347
+ width: 7px
348
+ height: 100%
349
+ cursor: col-resize
350
+ z-index: 5
351
+ touch-action: none
352
+
353
+ &::after
354
+ content: ''
355
+ position: absolute
356
+ top: 0
357
+ left: 2px
358
+ width: 2px
359
+ height: 100%
360
+ background: transparent
361
+ transition: background 0.15s ease
362
+
363
+ &:hover::after
364
+ background: var(--q-primary)
365
+
366
+ &--dark
367
+ background: #1b1b1c
368
+ color: rgba(255, 255, 255, 0.86)
369
+
370
+ .d-assistant-panel__composer
371
+ background: linear-gradient(180deg, rgba(27,27,28,0), rgba(41, 24, 20, 0.72) 34%, #1b1b1c 100%)
372
+
373
+ .d-assistant-message--assistant .d-assistant-message__content,
374
+ .d-assistant-panel__prompt,
375
+ .d-assistant-sources-chip
376
+ background: rgba(255, 255, 255, 0.045)
377
+ color: rgba(255, 255, 255, 0.86)
378
+ border-color: rgba(255, 255, 255, 0.12)
379
+
380
+ .d-assistant-sources-chip__avatar
381
+ border-color: #1b1b1c
382
+
383
+ .d-assistant-panel__mark
384
+ color: #ffad98
385
+ background: rgba(76, 35, 28, 0.35)
386
+
387
+ .d-assistant-panel__composer-box
388
+ background: rgba(24, 24, 24, 0.86)
389
+ border-color: rgba(255, 142, 111, 0.74)
390
+ box-shadow: 0 0 0 1px rgba(255, 142, 111, 0.3), 0 14px 36px rgba(0, 0, 0, 0.32)
391
+
392
+ .d-assistant-panel__input
393
+ .q-field__native,
394
+ .q-field__input
395
+ color: rgba(255, 255, 255, 0.88)
396
+
397
+ .q-field__native::placeholder,
398
+ .q-field__input::placeholder
399
+ color: rgba(255, 255, 255, 0.48)
400
+
401
+ &__header
402
+ height: 54px
403
+ flex: 0 0 54px
404
+ display: flex
405
+ align-items: center
406
+ justify-content: space-between
407
+ padding: 0 14px
408
+ border-bottom: 1px solid rgba(125, 125, 125, 0.18)
409
+
410
+ &__brand
411
+ display: flex
412
+ align-items: center
413
+ gap: 8px
414
+ min-width: 0
415
+
416
+ strong
417
+ font-size: 0.94rem
418
+ white-space: nowrap
419
+ overflow: hidden
420
+ text-overflow: ellipsis
421
+
422
+ &__header-actions
423
+ flex: 0 0 auto
424
+ display: flex
425
+ align-items: center
426
+ gap: 2px
427
+
428
+ &__header-action
429
+ background: rgba(15, 23, 42, 0.08)
430
+ color: currentColor
431
+
432
+ &:hover
433
+ background: rgba(15, 23, 42, 0.14)
434
+
435
+ &--clear
436
+ margin-right: 8px
437
+
438
+ &__body
439
+ flex: 1 1 auto
440
+ min-height: 0
441
+ overflow-x: hidden
442
+
443
+ .q-scrollarea__container,
444
+ .q-scrollarea__content
445
+ overflow-x: hidden
446
+ max-width: 100%
447
+
448
+ &__empty
449
+ min-height: 100%
450
+ display: flex
451
+ flex-direction: column
452
+ align-items: center
453
+ justify-content: center
454
+ text-align: center
455
+ padding: 32px 28px 170px
456
+
457
+ h2
458
+ font-size: 1.18rem
459
+ line-height: 1.35rem
460
+ font-weight: 700
461
+ margin: 18px 0 4px
462
+
463
+ p
464
+ margin: 0
465
+ opacity: 0.62
466
+ font-weight: 600
467
+
468
+ &__mark
469
+ width: 120px
470
+ height: 120px
471
+ border-radius: 50%
472
+ display: flex
473
+ align-items: center
474
+ justify-content: center
475
+ color: #0284c7
476
+ background: rgba(14, 165, 233, 0.12)
477
+
478
+ &__messages
479
+ padding: 16px 14px 14px
480
+ overflow-x: hidden
481
+
482
+ &__error
483
+ margin: 0 14px 96px
484
+ padding: 10px 12px
485
+ display: flex
486
+ align-items: flex-start
487
+ gap: 8px
488
+ border: 1px solid rgba(194, 65, 12, 0.25)
489
+ color: #b45309
490
+ background: rgba(251, 191, 36, 0.12)
491
+ border-radius: 8px
492
+
493
+ &__sources
494
+ margin: 0 14px 0
495
+ display: flex
496
+ flex-direction: column
497
+ align-items: flex-start
498
+ gap: 6px
499
+ overflow-x: hidden
500
+
501
+ &__sources-title
502
+ font-size: 0.76rem
503
+ opacity: 0.7
504
+ font-weight: 600
505
+
506
+ &__composer
507
+ flex: 0 0 auto
508
+ padding: 18px 14px 16px
509
+ background: linear-gradient(180deg, rgba(248,250,252,0), rgba(248,250,252,0.94) 28%, #f8fafc 100%)
510
+
511
+ &__prompts
512
+ display: flex
513
+ flex-direction: column
514
+ gap: 9px
515
+ margin-bottom: 10px
516
+
517
+ &__prompt
518
+ justify-content: flex-start
519
+ min-height: 38px
520
+ padding: 0 13px
521
+ border: 1px solid rgba(15, 23, 42, 0.1)
522
+ background: rgba(255, 255, 255, 0.68)
523
+ color: inherit
524
+ border-radius: 10px
525
+ font-weight: 600
526
+
527
+ &__composer-box
528
+ border: 1px solid rgba(15, 23, 42, 0.16)
529
+ border-radius: 18px
530
+ padding: 10px 12px 8px
531
+ background: rgba(255, 255, 255, 0.78)
532
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12)
533
+
534
+ &__input
535
+ .q-field__control
536
+ min-height: 36px
537
+ background: transparent
538
+ padding: 0
539
+
540
+ .q-field__native,
541
+ .q-field__input
542
+ font-weight: 600
543
+
544
+ &__composer-row
545
+ display: flex
546
+ align-items: center
547
+ justify-content: space-between
548
+ gap: 8px
549
+ margin-top: 2px
550
+
551
+ &__context
552
+ display: inline-flex
553
+ align-items: center
554
+ gap: 5px
555
+ min-width: 0
556
+ font-size: 0.76rem
557
+ font-weight: 600
558
+ opacity: 0.7
559
+
560
+ &__send
561
+ flex: 0 0 auto
562
+ color: var(--q-primary)
563
+ font-weight: 700
564
+
565
+ .d-assistant-message
566
+ display: flex
567
+ margin-bottom: 10px
568
+ min-width: 0
569
+ overflow-x: hidden
570
+
571
+ &--user
572
+ justify-content: flex-end
573
+
574
+ .d-assistant-message__content
575
+ color: white
576
+ background: var(--q-primary)
577
+
578
+ &--assistant
579
+ justify-content: flex-start
580
+
581
+ .d-assistant-message__content
582
+ background: rgba(255, 255, 255, 0.78)
583
+ border: 1px solid rgba(15, 23, 42, 0.1)
584
+ padding: 12px 14px !important
585
+
586
+ &__content
587
+ max-width: 88%
588
+ min-width: 0
589
+ padding: 10px 12px
590
+ border-radius: 8px
591
+ white-space: pre-wrap
592
+ overflow-wrap: anywhere
593
+ line-height: 1.45
594
+
595
+ &--markdown
596
+ min-height: 0 !important
597
+ white-space: normal
598
+
599
+ // Visuals come from Docsector's own page token components so the chat
600
+ // stays identical to pages/subpages; only enforce bubble-safe spacing
601
+ // and overflow containment here.
602
+ p
603
+ line-height: 1.6em
604
+
605
+ p,
606
+ ul,
607
+ ol,
608
+ blockquote,
609
+ .d-table-wrapper
610
+ margin-top: 0
611
+ margin-bottom: 0.7em
612
+
613
+ > :first-child
614
+ margin-top: 0
615
+
616
+ > :last-child
617
+ margin-bottom: 0
618
+
619
+ pre,
620
+ table,
621
+ .d-table-wrapper
622
+ max-width: 100%
623
+ overflow-x: auto
624
+
625
+ img
626
+ max-width: 100%
627
+ height: auto
628
+
629
+ &__thinking
630
+ display: flex
631
+ align-items: center
632
+ gap: 9px
633
+ color: inherit
634
+ opacity: 0.78
635
+ font-weight: 600
636
+
637
+ .d-assistant-sources-chip
638
+ max-width: 100%
639
+ height: auto
640
+ min-height: 34px
641
+ padding: 4px 10px 4px 6px
642
+ margin: 0
643
+ border-radius: 999px
644
+ background: rgba(255, 255, 255, 0.78)
645
+ border: 1px solid rgba(15, 23, 42, 0.12)
646
+ font-weight: 600
647
+
648
+ &__avatars
649
+ display: inline-flex
650
+ align-items: center
651
+ padding-left: 6px
652
+
653
+ &__avatar
654
+ display: inline-flex
655
+ align-items: center
656
+ justify-content: center
657
+ flex: 0 0 24px
658
+ position: relative
659
+ background: var(--q-primary)
660
+ border: 2px solid #f8fafc
661
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.18)
662
+
663
+ & + &
664
+ margin-left: -11px
665
+
666
+ img
667
+ width: 100%
668
+ height: 100%
669
+ box-sizing: border-box
670
+ object-fit: contain
671
+ padding: 4px
672
+ background: transparent
673
+
674
+ &__label
675
+ margin: 0 2px 0 8px
676
+ font-size: 0.8rem
677
+ white-space: nowrap
678
+
679
+ .d-assistant-sources-menu
680
+ max-width: min(360px, 90vw)
681
+
682
+ &__header
683
+ padding: 8px 16px 6px
684
+ font-weight: 700
685
+ opacity: 0.7
686
+
687
+ &__avatar
688
+ background: var(--q-primary)
689
+
690
+ &__list
691
+ padding-bottom: 4px
692
+
693
+ .q-item__section--avatar
694
+ padding-left: 6px
695
+
696
+ img
697
+ box-sizing: border-box
698
+ object-fit: contain
699
+ padding: 5px
700
+ background: transparent
701
+ </style>