@docsector/docsector-reader 4.5.6 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,7 +59,7 @@ Transform Markdown content into beautiful, navigable documentation sites — wit
59
59
  - 🔗 **Anchor Navigation** — Right-side source-ordered Table of Contents tree with stable scroll tracking, resize-safe drawer state, auto-scroll to the active section, and active-heading resolution based on the last heading that crossed the content threshold
60
60
  - 🖱️ **Active Menu Item UX** — Active menu entries keep pointer cursor, clear URL hash without redundant navigation, and prevent accidental label text selection
61
61
  - 🔎 **Search** — Menu search across all documentation content and tags
62
- - 💬 **Assistant Chat UX Enhancements** — Long conversations keep focus on recent messages, load earlier history progressively, deduplicate repeated sources, preserve the assistant panel open state across reloads, include per-message copy actions, and show a floating quick return to the bottom
62
+ - 💬 **Assistant Chat UX Enhancements** — Long conversations keep focus on recent messages, load earlier history progressively, deduplicate repeated sources, preserve the assistant panel open state across reloads, include per-message copy actions and hover-revealed message times, and show a floating quick return to the bottom
63
63
  - 📱 **Responsive** — Mobile-friendly with collapsible sidebar and drawers
64
64
  - 🏷️ **Clickable Header Branding** — The configured `branding.logo` and `branding.name` render as a home link in the global header, aligned left on desktop with a compact mobile treatment
65
65
  - 📚 **Book Tabs with Per-State Colors** — Define `*.book.js` tabs with icons, order, and `color.active` / `color.inactive`
@@ -301,7 +301,7 @@ Check `checks.discovery.webMcp.status` equals `"pass"`.
301
301
 
302
302
  Docsector Reader can add an opt-in assistant panel for documentation Q&A. Users open it from the global header while reading pages and subpages; it is not a dedicated documentation route. The drawer posts to a same-origin Cloudflare Pages Function, and that function calls Cloudflare AI Search so secrets, rate-limit strategy, provider errors, and future auth stay server-side.
303
303
 
304
- The panel is disabled by default. When enabled, desktop pages get a dedicated right-side assistant rail that can sit beside the table of contents on wide screens. Mobile uses a fullscreen dialog. Conversations restore at the latest message, reveal earlier history progressively in long chats, deduplicate repeated source links, preserve the panel open state across page reloads, and provide per-message copy actions.
304
+ The panel is disabled by default. When enabled, desktop pages get a dedicated right-side assistant rail that can sit beside the table of contents on wide screens. Mobile uses a fullscreen dialog. Conversations restore at the latest message, reveal earlier history progressively in long chats, deduplicate repeated source links, preserve the panel open state across page reloads, and provide per-message copy actions with hover-revealed message times.
305
305
 
306
306
  ### Configure
307
307
 
@@ -332,8 +332,8 @@ export default {
332
332
  accountIdEnv: 'CLOUDFLARE_ACCOUNT_ID',
333
333
  apiTokenEnv: 'CLOUDFLARE_API_TOKEN',
334
334
  model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
335
- retrievalType: 'hybrid',
336
- maxResults: 6,
335
+ retrievalType: 'vector',
336
+ maxResults: 10,
337
337
  matchThreshold: 0.4,
338
338
  contextExpansion: 1,
339
339
  queryRewrite: { enabled: true },
@@ -886,8 +886,8 @@ export default {
886
886
  instanceNameEnv: 'AI_SEARCH_INSTANCE_NAME',
887
887
  accountIdEnv: 'CLOUDFLARE_ACCOUNT_ID',
888
888
  apiTokenEnv: 'CLOUDFLARE_API_TOKEN',
889
- retrievalType: 'hybrid',
890
- maxResults: 6,
889
+ retrievalType: 'vector',
890
+ maxResults: 10,
891
891
  stream: true
892
892
  }
893
893
  },
package/bin/docsector.js CHANGED
@@ -24,7 +24,7 @@ const packageRoot = resolve(__dirname, '..')
24
24
  const args = process.argv.slice(2)
25
25
  const command = args[0]
26
26
 
27
- const VERSION = '4.5.6'
27
+ const VERSION = '4.6.0'
28
28
  const AUTHORING_SKILL_NAME = 'docsector-documentation-authoring'
29
29
  const AUTHORING_SKILL_DESCRIPTION = 'Author Docsector documentation with Markdown, custom blocks, MCP, and WebMCP.'
30
30
  const AUTHORING_SKILL_PUBLIC_PATH = `/.well-known/agent-skills/${AUTHORING_SKILL_NAME}/SKILL.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docsector/docsector-reader",
3
- "version": "4.5.6",
3
+ "version": "4.6.0",
4
4
  "description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
5
5
  "productName": "Docsector Reader",
6
6
  "author": "Rodrigo de Araujo Vieira",
@@ -57,7 +57,8 @@
57
57
  "url": "https://github.com/docsector/docsector-reader/issues"
58
58
  },
59
59
  "scripts": {
60
- "dev": "npx wrangler pages dev dist/spa",
60
+ "dev": "quasar dev",
61
+ "dev:pages": "npx wrangler pages dev dist/spa",
61
62
  "build": "quasar build",
62
63
  "lint": "eslint --ext .js,.vue ./",
63
64
  "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
@@ -2,6 +2,39 @@ export function hasAssistantMessageContent(message) {
2
2
  return String(message?.content || '').trim().length > 0
3
3
  }
4
4
 
5
+ export function getAssistantMessageTimestamp(message) {
6
+ const timestamp = Number(message?.timestamp)
7
+ if (Number.isFinite(timestamp) && timestamp > 0) {
8
+ return timestamp
9
+ }
10
+
11
+ const id = String(message?.id || '')
12
+ const match = id.match(/^(?:user|assistant)-(\d{12,})-/)
13
+ if (!match) {
14
+ return null
15
+ }
16
+
17
+ const legacyTimestamp = Number(match[1])
18
+ return Number.isFinite(legacyTimestamp) && legacyTimestamp > 0 ? legacyTimestamp : null
19
+ }
20
+
21
+ export function formatAssistantMessageTime(message, locale = 'en-US') {
22
+ const timestamp = getAssistantMessageTimestamp(message)
23
+ if (!timestamp) {
24
+ return ''
25
+ }
26
+
27
+ const date = new Date(timestamp)
28
+ if (Number.isNaN(date.getTime())) {
29
+ return ''
30
+ }
31
+
32
+ return new Intl.DateTimeFormat(locale || 'en-US', {
33
+ hour: '2-digit',
34
+ minute: '2-digit'
35
+ }).format(date)
36
+ }
37
+
5
38
  export function listVisibleAssistantMessages(messages = []) {
6
39
  return messages.filter((message) => {
7
40
  if (message?.role !== 'assistant') {
@@ -12,6 +45,26 @@ export function listVisibleAssistantMessages(messages = []) {
12
45
  })
13
46
  }
14
47
 
48
+ export function hasVisibleAssistantHistoryAfter(messages = [], messageId = '') {
49
+ const targetId = String(messageId || '')
50
+ if (!targetId) {
51
+ return false
52
+ }
53
+
54
+ const targetIndex = messages.findIndex(message => String(message?.id || '') === targetId)
55
+ if (targetIndex === -1) {
56
+ return false
57
+ }
58
+
59
+ return messages.slice(targetIndex + 1).some((message) => {
60
+ if (message?.role === 'assistant') {
61
+ return hasAssistantMessageContent(message)
62
+ }
63
+
64
+ return hasAssistantMessageContent(message)
65
+ })
66
+ }
67
+
15
68
  export const ASSISTANT_MESSAGE_WINDOW_SIZE = 60
16
69
  export const ASSISTANT_MESSAGE_WINDOW_STEP = 40
17
70
 
@@ -1,4 +1,5 @@
1
1
  import { dedupeAssistantSources } from './stream'
2
+ import { getAssistantMessageTimestamp } from './panel'
2
3
 
3
4
  export const ASSISTANT_SESSION_STORAGE_KEY = 'docsector.assistant.session.v1'
4
5
 
@@ -17,6 +18,11 @@ function cleanString (value = '', maxLength = Number.MAX_SAFE_INTEGER) {
17
18
  return String(value || '').slice(0, maxLength)
18
19
  }
19
20
 
21
+ function normalizeMessageTimestamp (message) {
22
+ const timestamp = getAssistantMessageTimestamp(message)
23
+ return timestamp ? { timestamp } : {}
24
+ }
25
+
20
26
  export function normalizeAssistantSession (session = {}) {
21
27
  const messages = (Array.isArray(session?.messages) ? session.messages : [])
22
28
  .map((message, index) => {
@@ -24,11 +30,16 @@ export function normalizeAssistantSession (session = {}) {
24
30
  const content = cleanString(message?.content, MAX_MESSAGE_CONTENT_LENGTH)
25
31
  if (!VALID_ROLES.has(role) || !content.trim()) return null
26
32
 
27
- return {
33
+ const normalizedMessage = {
28
34
  id: cleanString(message?.id || `${role}-${index + 1}`, 160),
29
35
  role,
30
36
  content
31
37
  }
38
+
39
+ return {
40
+ ...normalizedMessage,
41
+ ...normalizeMessageTimestamp(message)
42
+ }
32
43
  })
33
44
  .filter(Boolean)
34
45
  .slice(-MAX_PERSISTED_MESSAGES)
@@ -90,4 +101,4 @@ export function clearAssistantSession ({ storage = null, key = ASSISTANT_SESSION
90
101
  } catch {
91
102
  // Ignore storage failures.
92
103
  }
93
- }
104
+ }
@@ -8,7 +8,9 @@ import useAssistant from '../composables/useAssistant'
8
8
  import {
9
9
  ASSISTANT_MESSAGE_WINDOW_SIZE,
10
10
  ASSISTANT_MESSAGE_WINDOW_STEP,
11
+ formatAssistantMessageTime,
11
12
  getAssistantMessageWindow,
13
+ hasVisibleAssistantHistoryAfter,
12
14
  isAssistantThinkingState
13
15
  } from '../ai-assistant/panel'
14
16
  import DPageTokens from './DPageTokens.vue'
@@ -57,6 +59,8 @@ const scrollArea = ref(null)
57
59
  const visibleMessageLimit = ref(ASSISTANT_MESSAGE_WINDOW_SIZE)
58
60
  const copiedMessageId = ref('')
59
61
  const showScrollToBottom = ref(false)
62
+ const retryHistoryDialogOpen = ref(false)
63
+ const pendingRetryMessageId = ref('')
60
64
  let scrollFrame = 0
61
65
  let copiedMessageTimer = null
62
66
  let revealingOlderMessages = false
@@ -131,6 +135,10 @@ const hasMessageContent = (message) => {
131
135
  return String(message?.content || '').trim().length > 0
132
136
  }
133
137
 
138
+ const messageTime = (message) => {
139
+ return formatAssistantMessageTime(message, locale.value)
140
+ }
141
+
134
142
  const messageHasSources = (message) => {
135
143
  return hasSources.value && String(message?.id || '') === latestAssistantMessageId.value
136
144
  }
@@ -281,6 +289,41 @@ const messageCopyIcon = (message) => {
281
289
  return copiedMessageId.value === String(message?.id || '') ? 'check' : 'content_copy'
282
290
  }
283
291
 
292
+ const runRetryMessage = async (messageId) => {
293
+ const id = String(messageId || '')
294
+ if (!id) return
295
+ await assistant.retryFromUserMessage(id)
296
+ }
297
+
298
+ const retryMessage = async (message) => {
299
+ const id = String(message?.id || '')
300
+ if (!id || assistant.loading.value) return
301
+
302
+ if (hasVisibleAssistantHistoryAfter(assistant.messages.value, id)) {
303
+ pendingRetryMessageId.value = id
304
+ retryHistoryDialogOpen.value = true
305
+ return
306
+ }
307
+
308
+ await runRetryMessage(id)
309
+ }
310
+
311
+ const clearPendingRetryMessage = () => {
312
+ pendingRetryMessageId.value = ''
313
+ }
314
+
315
+ const cancelRetryMessage = () => {
316
+ retryHistoryDialogOpen.value = false
317
+ clearPendingRetryMessage()
318
+ }
319
+
320
+ const confirmRetryMessage = async () => {
321
+ const id = pendingRetryMessageId.value
322
+ retryHistoryDialogOpen.value = false
323
+ clearPendingRetryMessage()
324
+ await runRetryMessage(id)
325
+ }
326
+
284
327
  const submit = async (value = input.value) => {
285
328
  const prompt = String(value || '').trim()
286
329
  if (!prompt) return
@@ -419,7 +462,7 @@ onBeforeUnmount(() => {
419
462
  <q-btn
420
463
  flat dense
421
464
  text-color="primary"
422
- class="d-assistant-message__copy d-assistant-message__copy--assistant"
465
+ class="d-assistant-message__action d-assistant-message__copy d-assistant-message__copy--assistant"
423
466
  :icon="messageCopyIcon(message)"
424
467
  :aria-label="t('assistant.copyMessage')"
425
468
  @click="copyMessage(message)"
@@ -483,6 +526,11 @@ onBeforeUnmount(() => {
483
526
  </q-list>
484
527
  </q-menu>
485
528
  </q-chip>
529
+
530
+ <span
531
+ v-if="messageTime(message)"
532
+ class="d-assistant-message__timestamp"
533
+ >{{ messageTime(message) }}</span>
486
534
  </div>
487
535
 
488
536
  <div
@@ -491,15 +539,30 @@ onBeforeUnmount(() => {
491
539
  >
492
540
  <div class="d-assistant-message__hoverlayer">
493
541
  <q-btn
542
+ v-if="!assistant.loading.value"
494
543
  flat dense
495
544
  text-color="primary"
496
- class="d-assistant-message__copy d-assistant-message__copy--user"
545
+ class="d-assistant-message__action d-assistant-message__retry"
546
+ icon="refresh"
547
+ :aria-label="t('assistant.retryMessage')"
548
+ @click="retryMessage(message)"
549
+ >
550
+ <q-tooltip>{{ t('assistant.retryMessage') }}</q-tooltip>
551
+ </q-btn>
552
+ <q-btn
553
+ flat dense
554
+ text-color="primary"
555
+ class="d-assistant-message__action d-assistant-message__copy d-assistant-message__copy--user"
497
556
  :icon="messageCopyIcon(message)"
498
557
  :aria-label="t('assistant.copyMessage')"
499
558
  @click="copyMessage(message)"
500
559
  >
501
560
  <q-tooltip>{{ t('assistant.copyMessage') }}</q-tooltip>
502
561
  </q-btn>
562
+ <span
563
+ v-if="messageTime(message)"
564
+ class="d-assistant-message__timestamp"
565
+ >{{ messageTime(message) }}</span>
503
566
  </div>
504
567
  </div>
505
568
  </div>
@@ -572,6 +635,33 @@ onBeforeUnmount(() => {
572
635
  </div>
573
636
  </div>
574
637
  </footer>
638
+
639
+ <q-dialog v-model="retryHistoryDialogOpen" @hide="clearPendingRetryMessage">
640
+ <q-card class="d-assistant-retry-dialog">
641
+ <q-card-section class="d-assistant-retry-dialog__header">
642
+ <q-icon name="warning_amber" size="24px" />
643
+ <div>
644
+ <h3>{{ t('assistant.retryHistoryTitle') }}</h3>
645
+ <p>{{ t('assistant.retryHistoryMessage') }}</p>
646
+ </div>
647
+ </q-card-section>
648
+ <q-card-actions align="right" class="d-assistant-retry-dialog__actions">
649
+ <q-btn
650
+ unelevated no-caps
651
+ color="grey-7"
652
+ text-color="white"
653
+ :label="t('assistant.retryHistoryCancel')"
654
+ @click="cancelRetryMessage"
655
+ />
656
+ <q-btn
657
+ unelevated no-caps
658
+ color="primary"
659
+ :label="t('assistant.retryHistoryConfirm')"
660
+ @click="confirmRetryMessage"
661
+ />
662
+ </q-card-actions>
663
+ </q-card>
664
+ </q-dialog>
575
665
  </aside>
576
666
  </template>
577
667
 
@@ -636,7 +726,7 @@ onBeforeUnmount(() => {
636
726
  color: rgba(255, 255, 255, 0.86)
637
727
  border-color: rgba(255, 255, 255, 0.12)
638
728
 
639
- .d-assistant-message__copy
729
+ .d-assistant-message__action
640
730
  color: rgba(255, 255, 255, 0.84)
641
731
 
642
732
  .d-assistant-sources-chip__avatar
@@ -862,10 +952,12 @@ onBeforeUnmount(() => {
862
952
  color: white
863
953
  background: var(--q-primary)
864
954
 
865
- &:hover .d-assistant-message__hoverlayer .d-assistant-message__copy,
866
- &:focus-within .d-assistant-message__hoverlayer .d-assistant-message__copy,
867
- .d-assistant-message__hoverlayer:hover .d-assistant-message__copy,
868
- .d-assistant-message__hoverlayer:focus-within .d-assistant-message__copy
955
+ &:hover .d-assistant-message__hoverlayer .d-assistant-message__action,
956
+ &:focus-within .d-assistant-message__hoverlayer .d-assistant-message__action,
957
+ .d-assistant-message__hoverlayer:hover .d-assistant-message__action,
958
+ .d-assistant-message__hoverlayer:focus-within .d-assistant-message__action,
959
+ &:hover .d-assistant-message__timestamp,
960
+ &:focus-within .d-assistant-message__timestamp
869
961
  opacity: 1
870
962
  pointer-events: auto
871
963
 
@@ -881,6 +973,10 @@ onBeforeUnmount(() => {
881
973
  .d-assistant-message__footer
882
974
  justify-content: flex-start
883
975
 
976
+ &:hover .d-assistant-message__timestamp,
977
+ &:focus-within .d-assistant-message__timestamp
978
+ opacity: 1
979
+
884
980
  &__content
885
981
  max-width: 88%
886
982
  min-width: 0
@@ -935,7 +1031,7 @@ onBeforeUnmount(() => {
935
1031
 
936
1032
  .d-assistant-sources-chip
937
1033
  flex: 0 1 auto
938
- max-width: calc(100% - 38px)
1034
+ max-width: calc(100% - 92px)
939
1035
 
940
1036
  &--user
941
1037
  width: auto
@@ -946,7 +1042,7 @@ onBeforeUnmount(() => {
946
1042
  align-items: center
947
1043
  justify-content: center
948
1044
 
949
- &__copy
1045
+ &__action
950
1046
  flex: 0 0 auto
951
1047
  width: 30px
952
1048
  height: 30px
@@ -960,25 +1056,45 @@ onBeforeUnmount(() => {
960
1056
 
961
1057
  i
962
1058
  font-size: 17px !important
963
- margin-left: 3px
1059
+ margin: 0
964
1060
 
1061
+ &__copy
965
1062
  &--assistant
966
1063
  margin-left: -2px
967
1064
 
1065
+ &__retry
1066
+ color: currentColor
1067
+
968
1068
  &__hoverlayer
969
1069
  display: flex
970
1070
  align-items: center
971
- justify-content: center
972
- width: 30px
1071
+ justify-content: flex-end
1072
+ gap: 6px
1073
+ width: auto
973
1074
  height: 30px
974
1075
  min-width: 30px
975
1076
  min-height: 30px
976
1077
 
977
- .d-assistant-message__copy
1078
+ .d-assistant-message__action
978
1079
  opacity: 0
979
1080
  pointer-events: none
980
1081
  transition: opacity 0.14s ease
981
1082
 
1083
+ &__timestamp
1084
+ flex: 0 0 auto
1085
+ margin-left: auto
1086
+ color: currentColor
1087
+ font-size: 0.72rem
1088
+ font-weight: 700
1089
+ font-variant-numeric: tabular-nums
1090
+ line-height: 30px
1091
+ opacity: 0
1092
+ pointer-events: none
1093
+ transition: opacity 0.14s ease
1094
+
1095
+ &__hoverlayer &__timestamp
1096
+ margin-left: 0
1097
+
982
1098
  &__thinking
983
1099
  display: flex
984
1100
  align-items: center
@@ -987,6 +1103,31 @@ onBeforeUnmount(() => {
987
1103
  opacity: 0.78
988
1104
  font-weight: 600
989
1105
 
1106
+ .d-assistant-retry-dialog
1107
+ width: min(360px, calc(100vw - 32px))
1108
+ border-radius: 8px
1109
+
1110
+ &__header
1111
+ display: flex
1112
+ align-items: flex-start
1113
+ gap: 12px
1114
+ padding: 18px 18px 10px
1115
+
1116
+ h3
1117
+ margin: 0 0 6px
1118
+ font-size: 1rem
1119
+ line-height: 1.3
1120
+ font-weight: 800
1121
+
1122
+ p
1123
+ margin: 0
1124
+ color: currentColor
1125
+ opacity: 0.72
1126
+ line-height: 1.45
1127
+
1128
+ &__actions
1129
+ padding: 8px 12px 14px
1130
+
990
1131
  .d-assistant-sources-chip
991
1132
  max-width: 100%
992
1133
  min-width: 0
@@ -17,10 +17,13 @@ let assistantSessionPersistencePaused = false
17
17
  const ASSISTANT_SESSION_PERSIST_DEBOUNCE = 180
18
18
 
19
19
  function createMessage (role, content = '') {
20
+ const timestamp = Date.now()
21
+
20
22
  return {
21
- id: `${role}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
23
+ id: `${role}-${timestamp}-${Math.random().toString(16).slice(2)}`,
22
24
  role,
23
- content
25
+ content,
26
+ timestamp
24
27
  }
25
28
  }
26
29
 
@@ -121,6 +124,11 @@ export default function useAssistant ({ route, locale, getContext } = {}) {
121
124
  message.content += content
122
125
  }
123
126
 
127
+ const appendAssistantPlaceholder = () => {
128
+ messages.value.push(createMessage('assistant'))
129
+ return messages.value[messages.value.length - 1]
130
+ }
131
+
124
132
  const consumeStream = async (response, assistantMessage) => {
125
133
  const reader = response.body.getReader()
126
134
  const decoder = new TextDecoder()
@@ -175,22 +183,14 @@ export default function useAssistant ({ route, locale, getContext } = {}) {
175
183
  }
176
184
  }
177
185
 
178
- const send = async (content) => {
179
- const prompt = String(content || '').trim()
180
- if (!prompt || loading.value) return
181
-
186
+ const prepareRequest = () => {
182
187
  assistantSessionPersistencePaused = true
183
188
  cancelPersistAssistantSession()
184
189
  error.value = ''
185
190
  sources.value = []
191
+ }
186
192
 
187
- const userMessage = createMessage('user', prompt)
188
- const assistantMessage = createMessage('assistant')
189
- messages.value.push(userMessage, assistantMessage)
190
- // Use the reactive proxy from the array so streamed mutations trigger
191
- // live re-renders (raw object mutations bypass Vue reactivity).
192
- const liveAssistantMessage = messages.value[messages.value.length - 1]
193
-
193
+ const requestAssistantResponse = async (liveAssistantMessage) => {
194
194
  abortController.value = new AbortController()
195
195
  loading.value = true
196
196
 
@@ -249,6 +249,36 @@ export default function useAssistant ({ route, locale, getContext } = {}) {
249
249
  }
250
250
  }
251
251
 
252
+ const send = async (content) => {
253
+ const prompt = String(content || '').trim()
254
+ if (!prompt || loading.value) return
255
+
256
+ prepareRequest()
257
+
258
+ messages.value.push(createMessage('user', prompt))
259
+ const liveAssistantMessage = appendAssistantPlaceholder()
260
+
261
+ await requestAssistantResponse(liveAssistantMessage)
262
+ }
263
+
264
+ const retryFromUserMessage = async (messageId) => {
265
+ const targetId = String(messageId || '')
266
+ if (!targetId || loading.value) return
267
+
268
+ const targetIndex = messages.value.findIndex(message => String(message?.id || '') === targetId)
269
+ const targetMessage = messages.value[targetIndex]
270
+ if (targetIndex === -1 || targetMessage?.role !== 'user' || !String(targetMessage?.content || '').trim()) {
271
+ return
272
+ }
273
+
274
+ prepareRequest()
275
+
276
+ messages.value = messages.value.slice(0, targetIndex + 1)
277
+ const liveAssistantMessage = appendAssistantPlaceholder()
278
+
279
+ await requestAssistantResponse(liveAssistantMessage)
280
+ }
281
+
252
282
  return {
253
283
  config: assistantConfig,
254
284
  messages,
@@ -257,6 +287,7 @@ export default function useAssistant ({ route, locale, getContext } = {}) {
257
287
  error,
258
288
  hasMessages,
259
289
  send,
290
+ retryFromUserMessage,
260
291
  stop,
261
292
  clear
262
293
  }
@@ -84,6 +84,11 @@ const engineDefaults = {
84
84
  sources: 'Sources',
85
85
  sourcesCount: '{count} sources',
86
86
  copyMessage: 'Copy message',
87
+ retryMessage: 'Reload message',
88
+ retryHistoryTitle: 'Reload from this message?',
89
+ retryHistoryMessage: 'Messages after this question will be removed from the conversation.',
90
+ retryHistoryConfirm: 'Reload',
91
+ retryHistoryCancel: 'Cancel',
87
92
  copied: 'Message copied',
88
93
  loadEarlier: 'Load earlier messages',
89
94
  thinking: 'Searching the docs…',
@@ -157,6 +162,11 @@ const engineDefaults = {
157
162
  sources: 'Fontes',
158
163
  sourcesCount: '{count} fontes',
159
164
  copyMessage: 'Copiar mensagem',
165
+ retryMessage: 'Reenviar mensagem',
166
+ retryHistoryTitle: 'Reenviar desta mensagem?',
167
+ retryHistoryMessage: 'As mensagens depois desta pergunta serão removidas da conversa.',
168
+ retryHistoryConfirm: 'Reenviar',
169
+ retryHistoryCancel: 'Cancelar',
160
170
  copied: 'Mensagem copiada',
161
171
  loadEarlier: 'Carregar mensagens anteriores',
162
172
  thinking: 'Consultando a documentação…',
@@ -119,6 +119,11 @@
119
119
  sources: 'Sources',
120
120
  sourcesCount: '{count} sources',
121
121
  copyMessage: 'Copy message',
122
+ retryMessage: 'Reload message',
123
+ retryHistoryTitle: 'Reload from this message?',
124
+ retryHistoryMessage: 'Messages after this question will be removed from the conversation.',
125
+ retryHistoryConfirm: 'Reload',
126
+ retryHistoryCancel: 'Cancel',
122
127
  copied: 'Message copied',
123
128
  loadEarlier: 'Load earlier messages',
124
129
  thinking: 'Searching the docs…',
@@ -118,6 +118,11 @@
118
118
  sources: 'Fontes',
119
119
  sourcesCount: '{count} fontes',
120
120
  copyMessage: 'Copiar mensagem',
121
+ retryMessage: 'Reenviar mensagem',
122
+ retryHistoryTitle: 'Reenviar desta mensagem?',
123
+ retryHistoryMessage: 'As mensagens depois desta pergunta serão removidas da conversa.',
124
+ retryHistoryConfirm: 'Reenviar',
125
+ retryHistoryCancel: 'Cancelar',
121
126
  copied: 'Mensagem copiada',
122
127
  loadEarlier: 'Carregar mensagens anteriores',
123
128
  thinking: 'Consultando a documentação…',